From c2854e75f34c472dbfc8f793f94ed59b2ce30a7a Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 7 Aug 2025 05:44:19 +0000 Subject: [PATCH 01/72] Add multi-identifier support for preference upload --- pnpm-lock.yaml | 7889 ++++++++--------- .../consent/upload-preferences/command.ts | 14 +- .../consent/upload-preferences/impl.ts | 16 +- src/lib/graphql/fetchIdentifiers.ts | 2 + src/lib/graphql/gqls/identifier.ts | 1 + src/lib/preference-management/codecs.ts | 14 +- .../parsePreferenceAndPurposeValuesFromCsv.ts | 4 +- .../parsePreferenceIdentifiersFromCsv.ts | 191 +- .../parsePreferenceManagementCsv.ts | 86 +- .../parsePreferenceTimestampsFromCsv.ts | 16 +- ...ferenceManagementPreferencesInteractive.ts | 28 +- 11 files changed, 4053 insertions(+), 4208 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5ac3fd7..4239d69d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -82,7 +82,7 @@ importers: version: 2.2.22(fp-ts@2.16.10) io-ts-types: specifier: ^0.5.16 - version: 0.5.19(fp-ts@2.16.10)(io-ts@2.2.22(fp-ts@2.16.10))(monocle-ts@2.3.13(fp-ts@2.16.10))(newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10))) + version: 0.5.19(fp-ts@2.16.10)(io-ts@2.2.22)(monocle-ts@2.3.13)(newtype-ts@0.3.5) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -97,7 +97,7 @@ importers: version: 2.3.13(fp-ts@2.16.10) newtype-ts: specifier: ^0.3.5 - version: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)) + version: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13) query-string: specifier: '=7.0.0' version: 7.0.0 @@ -113,7 +113,7 @@ importers: devDependencies: '@types/JSONStream': specifier: npm:@types/jsonstream@^0.8.33 - version: '@types/jsonstream@0.8.33' + version: /@types/jsonstream@0.8.33 '@types/cli-progress': specifier: ^3.11.0 version: 3.11.6 @@ -146,7 +146,7 @@ importers: version: 4.17.12 '@types/node': specifier: ^18.15.11 - version: 18.19.115 + version: 18.19.121 '@types/semver': specifier: ^7 version: 7.7.0 @@ -155,10 +155,10 @@ importers: version: 21.0.3 '@typescript-eslint/eslint-plugin': specifier: ^5.58.0 - version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.0.4) '@typescript-eslint/parser': specifier: ^5.58.0 - version: 5.62.0(eslint@8.57.1)(typescript@5.8.3) + version: 5.62.0(eslint@8.57.1)(typescript@5.0.4) depcheck: specifier: ^1.4.3 version: 1.4.7 @@ -179,13 +179,13 @@ importers: version: 3.2.0(eslint@8.57.1) eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + version: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsdoc: specifier: ^41.1.1 version: 41.1.2(eslint@8.57.1) fdir: specifier: ^6.4.6 - version: 6.4.6(picomatch@4.0.2) + version: 6.4.6(picomatch@4.0.3) prettier: specifier: ^2.8.7 version: 2.8.8 @@ -197,3510 +197,872 @@ importers: version: 3.1.0 tsup: specifier: ^8.5.0 - version: 8.5.0(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3) + version: 8.5.0(tsx@4.20.3)(typescript@5.0.4) tsx: specifier: ^4.20.3 version: 4.20.3 typescript: specifier: ^5.0.4 - version: 5.8.3 + version: 5.0.4 vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)) + version: 5.1.4(typescript@5.0.4) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@18.19.115)(tsx@4.20.3) + version: 3.2.4(@types/node@18.19.121)(tsx@4.20.3) + + examples/code-scanning/test-gradle/test-nested-package-json: + dependencies: + dd-trace: + specifier: 2.45.1 + version: 2.45.1 + fast-csv: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + typescript: + specifier: ^5.0.4 + version: 5.0.4 + + examples/code-scanning/test-package-json: + dependencies: + dd-trace: + specifier: 2.45.1 + version: 2.45.1 + fast-csv: + specifier: ^4.3.6 + version: 4.3.6 + sequelize: + specifier: ^6.37.3 + version: 6.37.3 + sequelize-mock: + specifier: ^0.10.2 + version: 0.10.2 + devDependencies: + '@types/sequelize': + specifier: ^4.28.20 + version: 4.28.20 + typescript: + specifier: ^5.0.4 + version: 5.0.4 packages: - '@babel/code-frame@7.27.1': + /@babel/code-frame@7.27.1: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + dev: true - '@babel/generator@7.28.0': + /@babel/generator@7.28.0: resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + dev: true - '@babel/helper-globals@7.28.0': + /@babel/helper-globals@7.28.0: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-string-parser@7.27.1': + /@babel/helper-string-parser@7.27.1: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-validator-identifier@7.27.1': + /@babel/helper-validator-identifier@7.27.1: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + dev: true - '@babel/parser@7.28.0': + /@babel/parser@7.28.0: resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} engines: {node: '>=6.0.0'} hasBin: true + dependencies: + '@babel/types': 7.28.2 + dev: true - '@babel/template@7.27.2': + /@babel/template@7.27.2: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + dev: true - '@babel/traverse@7.28.0': + /@babel/traverse@7.28.0: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + dev: true - '@babel/types@7.28.0': - resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + /@babel/types@7.28.2: + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + dev: true + + /@datadog/native-appsec@3.2.0: + resolution: {integrity: sha512-biAa7EFfuavjSWgSQaCit9CqGzr6Af5nhzfNNGJ38Y/Y387hDvLivAR374kK1z6XoxGZEOa+XPbVogmV/2Bcjw==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/native-iast-rewriter@2.0.1: + resolution: {integrity: sha512-Mm+FG3XxEbPrAfJQPOMHts7iZZXRvg9gnGeeFRGkyirmRcQcOpZO4wFe/8K61DUVa5pXpgAJQ2ZkBGYF1O9STg==} + engines: {node: '>= 10'} + dependencies: + node-gyp-build: 4.8.4 + dev: false + + /@datadog/native-iast-taint-tracking@1.5.0: + resolution: {integrity: sha512-SOWIk1M6PZH0osNB191Voz2rKBPoF5hISWVSK9GiJPrD40+xjib1Z/bFDV7EkDn3kjOyordSBdNPG5zOqZJdyg==} + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/native-metrics@1.6.0: + resolution: {integrity: sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + node-gyp-build: 3.9.0 + dev: false + + /@datadog/pprof@3.1.0: + resolution: {integrity: sha512-Bg8O8yrHeL2KKHXhLoAAT33ZfzLnZ6rWfOjy8PkcNhUJy3UwNVLbUoApf+99EyLjqpzpk/kZXrIAMBzMMB8ilg==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + delay: 5.0.0 + node-gyp-build: 3.9.0 + p-limit: 3.1.0 + pprof-format: 2.1.0 + source-map: 0.7.4 + dev: false - '@emnapi/core@1.4.4': - resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==} + /@datadog/sketches-js@2.1.1: + resolution: {integrity: sha512-d5RjycE+MObE/hU+8OM5Zp4VjTwiPLRa8299fj7muOmR16fb942z8byoMbCErnGh0lBevvgkGrLclQDvINbIyg==} + dev: false - '@emnapi/runtime@1.4.4': - resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==} + /@emnapi/core@1.4.5: + resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + requiresBuild: true + dependencies: + '@emnapi/wasi-threads': 1.0.4 + tslib: 2.8.1 + dev: true + optional: true + + /@emnapi/runtime@1.4.5: + resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: true + optional: true - '@emnapi/wasi-threads@1.0.3': - resolution: {integrity: sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==} + /@emnapi/wasi-threads@1.0.4: + resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: true + optional: true - '@es-joy/jsdoccomment@0.37.1': + /@es-joy/jsdoccomment@0.37.1: resolution: {integrity: sha512-5vxWJ1gEkEF0yRd0O+uK6dHJf7adrxwQSX8PuRiPfFSAbNLnY0ZJfXaZucoz14Jj2N11xn2DnlEPwWRpYpvRjg==} engines: {node: ^14 || ^16 || ^17 || ^18 || ^19 || ^20} + dependencies: + comment-parser: 1.3.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: true - '@esbuild/aix-ppc64@0.25.6': - resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} + /@esbuild/aix-ppc64@0.25.8: + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-arm64@0.25.6': - resolution: {integrity: sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==} + /@esbuild/android-arm64@0.25.8: + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} engines: {node: '>=18'} cpu: [arm64] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-arm@0.25.6': - resolution: {integrity: sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==} + /@esbuild/android-arm@0.25.8: + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} engines: {node: '>=18'} cpu: [arm] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-x64@0.25.6': - resolution: {integrity: sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==} + /@esbuild/android-x64@0.25.8: + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} engines: {node: '>=18'} cpu: [x64] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/darwin-arm64@0.25.6': - resolution: {integrity: sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==} + /@esbuild/darwin-arm64@0.25.8: + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + requiresBuild: true + dev: true + optional: true - '@esbuild/darwin-x64@0.25.6': - resolution: {integrity: sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==} + /@esbuild/darwin-x64@0.25.8: + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + requiresBuild: true + dev: true + optional: true - '@esbuild/freebsd-arm64@0.25.6': - resolution: {integrity: sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==} + /@esbuild/freebsd-arm64@0.25.8: + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/freebsd-x64@0.25.6': - resolution: {integrity: sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==} + /@esbuild/freebsd-x64@0.25.8: + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-arm64@0.25.6': - resolution: {integrity: sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==} + /@esbuild/linux-arm64@0.25.8: + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-arm@0.25.6': - resolution: {integrity: sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==} + /@esbuild/linux-arm@0.25.8: + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-ia32@0.25.6': - resolution: {integrity: sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==} + /@esbuild/linux-ia32@0.25.8: + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-loong64@0.25.6': - resolution: {integrity: sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==} + /@esbuild/linux-loong64@0.25.8: + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-mips64el@0.25.6': - resolution: {integrity: sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==} + /@esbuild/linux-mips64el@0.25.8: + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-ppc64@0.25.6': - resolution: {integrity: sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==} + /@esbuild/linux-ppc64@0.25.8: + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-riscv64@0.25.6': - resolution: {integrity: sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==} + /@esbuild/linux-riscv64@0.25.8: + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-s390x@0.25.6': - resolution: {integrity: sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==} + /@esbuild/linux-s390x@0.25.8: + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-x64@0.25.6': - resolution: {integrity: sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==} + /@esbuild/linux-x64@0.25.8: + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/netbsd-arm64@0.25.6': - resolution: {integrity: sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==} + /@esbuild/netbsd-arm64@0.25.8: + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/netbsd-x64@0.25.6': - resolution: {integrity: sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==} + /@esbuild/netbsd-x64@0.25.8: + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/openbsd-arm64@0.25.6': - resolution: {integrity: sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==} + /@esbuild/openbsd-arm64@0.25.8: + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/openbsd-x64@0.25.6': - resolution: {integrity: sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==} + /@esbuild/openbsd-x64@0.25.8: + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/openharmony-arm64@0.25.6': - resolution: {integrity: sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==} + /@esbuild/openharmony-arm64@0.25.8: + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + requiresBuild: true + dev: true + optional: true - '@esbuild/sunos-x64@0.25.6': - resolution: {integrity: sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==} + /@esbuild/sunos-x64@0.25.8: + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-arm64@0.25.6': - resolution: {integrity: sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==} + /@esbuild/win32-arm64@0.25.8: + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-ia32@0.25.6': - resolution: {integrity: sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==} + /@esbuild/win32-ia32@0.25.8: + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-x64@0.25.6': - resolution: {integrity: sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==} + /@esbuild/win32-x64@0.25.8: + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} engines: {node: '>=18'} cpu: [x64] os: [win32] + requiresBuild: true + dev: true + optional: true - '@eslint-community/eslint-utils@4.7.0': + /@eslint-community/eslint-utils@4.7.0(eslint@8.57.1): resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + dev: true - '@eslint-community/regexpp@4.12.1': + /@eslint-community/regexpp@4.12.1: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true - '@eslint/eslintrc@2.1.4': + /@eslint/eslintrc@2.1.4: resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true - '@eslint/js@8.57.1': + /@eslint/js@8.57.1: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true - '@fast-csv/format@4.3.5': + /@fast-csv/format@4.3.5: resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + dev: false - '@fast-csv/parse@4.3.6': + /@fast-csv/parse@4.3.6: resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + dev: false - '@fastify/busboy@2.1.1': + /@fastify/busboy@2.1.1: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - '@graphql-typed-document-node/core@3.2.0': - resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.12': - resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - - '@jridgewell/trace-mapping@0.3.29': - resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - - '@napi-rs/wasm-runtime@0.2.11': - resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@nolyfill/is-core-module@1.0.39': - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@publint/pack@0.1.2': - resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} - engines: {node: '>=18'} - - '@rollup/rollup-android-arm-eabi@4.44.2': - resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.44.2': - resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.44.2': - resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.44.2': - resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.44.2': - resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.44.2': - resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.44.2': - resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.44.2': - resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.44.2': - resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.44.2': - resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.44.2': - resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': - resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.44.2': - resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.44.2': - resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.44.2': - resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.44.2': - resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.44.2': - resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.44.2': - resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.44.2': - resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.44.2': - resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} - cpu: [x64] - os: [win32] - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - - '@stricli/auto-complete@1.2.0': - resolution: {integrity: sha512-r9/msiloVmTF95mdhe04Uzqei1B0ZofhYRLeiPqpJ1W1RMCC8p9iW7kqBZEbALl2aRL5ZK9OEW3Q1cIejH7KEQ==} - hasBin: true - - '@stricli/core@1.2.0': - resolution: {integrity: sha512-5b+npntDY0TAB7wAw0daGlh3/R2sf0TDLyrB1By2jCNH+C+lmcSqMtJXOMLVtEGSkIOvqAgIWpLMSs1PXqzt3w==} - - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - - '@textlint/ast-node-types@12.6.1': - resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} - - '@textlint/markdown-to-ast@12.6.1': - resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} - - '@transcend-io/airgap.js-types@12.12.2': - resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} - - '@transcend-io/handlebars-utils@1.1.0': - resolution: {integrity: sha512-mDbKm9JObd9mioNlEYGa2zfTBqli1KAAM+iRHSqi6s2l7MAYENxjlfvXN5uC97T9/90vfWlxNbHjLpUuQVURaA==} - - '@transcend-io/internationalization@1.7.4': - resolution: {integrity: sha512-kg6c2Bjr823hBWrx62074ZlyBcW1FvK1dj77+lN7E70EyYxj6o5PordsOzxbLmTV1iZ0GLKGu+QiNl4xVOWfLA==} - - '@transcend-io/persisted-state@1.0.4': - resolution: {integrity: sha512-jEDN64pr5Q1fniHMERa2+DKhuHbwdB1zzGdHoWgq4EIEx/ZJQxraiNfYoCpf5hpuiKxtXcIN41SC2/MI6MraoQ==} - - '@transcend-io/privacy-types@4.128.0': - resolution: {integrity: sha512-iOFVTpdM+ZFX+YxmqtK5mDJNYAlwVa7JhPg4Yg0pUSFcck7LvZlxFVNwpHVoGGdguy6Ejsh8hfPYKHhBOgBqMQ==} - - '@transcend-io/secret-value@1.2.1': - resolution: {integrity: sha512-pb2thX9znGJ9GOm6A2kgIzwEchB3E6Zn1KTLIBJx+T+K7XkqRwQJsQL0vKGiJVmZZ0iB3bLSAABHoWwNAZcFxA==} - - '@transcend-io/type-utils@1.8.4': - resolution: {integrity: sha512-J1/T+q2Bs5znAyxQpQjD0wzN+h7Wi1xRsqa99uAu3G8VcKb5nRLnaUyY7Ut8N0YU1FHAxNhyXz9MVakYUigWuw==} - - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - - '@types/cacheable-request@6.0.3': - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/cli-progress@3.11.6': - resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} - - '@types/colors@1.2.4': - resolution: {integrity: sha512-oSQxEVIDcYisAzWLa+wr50GSIPu8ml4PsKNJzgrDX3SmEHVBBqbaUurqsUceFauNlCRxNtENKkQm3yOe3m3nfg==} - deprecated: This is a stub types definition. colors provides its own type definitions, so you do not need this installed. - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/fuzzysearch@1.0.2': - resolution: {integrity: sha512-G2M0M7acg75BzTtUv1qmPFMe7nTwX1K2AEKNeBO8zW0o+H2oTmyir7IJ2v0UW8pZkY4nHgHALvgpWQpB9WXPpg==} - - '@types/global-agent@2.1.3': - resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} - - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - - '@types/inquirer-autocomplete-prompt@3.0.3': - resolution: {integrity: sha512-OQCW09mEECgvhcppbQRgZSmWskWv58l+WwyUvWB1oxTu3CZj8keYSDZR9U8owUzJ5Zeux5kacN9iVPJLXcoLXg==} - - '@types/inquirer@7.3.3': - resolution: {integrity: sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==} - - '@types/js-yaml@4.0.9': - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - - '@types/jsonstream@0.8.33': - resolution: {integrity: sha512-yhg1SNOgJ8y2nOkvAQ1zZ1Z2xibxgFs7984+EeBPuWgo/TbuYo79+rj2wUVch3KF4GhhcwAi/AlJcehmLCXb3g==} - - '@types/jsonwebtoken@9.0.10': - resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - - '@types/lodash-es@4.17.12': - resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - - '@types/lodash@4.17.20': - resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} - - '@types/mdast@3.0.15': - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} - - '@types/minimatch@3.0.5': - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@14.18.63': - resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - - '@types/node@18.19.115': - resolution: {integrity: sha512-kNrFiTgG4a9JAn1LMQeLOv3MvXIPokzXziohMrMsvpYgLpdEt/mMiVYc4sGKtDfyxM5gIDF4VgrPRyCw4fHOYg==} - - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - - '@types/semver@7.7.0': - resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} - - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@typescript-eslint/eslint-plugin@5.62.0': - resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@5.62.0': - resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@5.62.0': - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@typescript-eslint/type-utils@5.62.0': - resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@5.62.0': - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@typescript-eslint/typescript-estree@5.62.0': - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@5.62.0': - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - - '@typescript-eslint/visitor-keys@5.62.0': - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - - '@unrs/resolver-binding-android-arm-eabi@1.11.0': - resolution: {integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw==} - cpu: [arm] - os: [android] - - '@unrs/resolver-binding-android-arm64@1.11.0': - resolution: {integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ==} - cpu: [arm64] - os: [android] - - '@unrs/resolver-binding-darwin-arm64@1.11.0': - resolution: {integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w==} - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.11.0': - resolution: {integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA==} - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.11.0': - resolution: {integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg==} - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': - resolution: {integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': - resolution: {integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': - resolution: {integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-musl@1.11.0': - resolution: {integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': - resolution: {integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ==} - cpu: [ppc64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': - resolution: {integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': - resolution: {integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': - resolution: {integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ==} - cpu: [s390x] - os: [linux] - - '@unrs/resolver-binding-linux-x64-gnu@1.11.0': - resolution: {integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-linux-x64-musl@1.11.0': - resolution: {integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-wasm32-wasi@1.11.0': - resolution: {integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': - resolution: {integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng==} - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': - resolution: {integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA==} - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.11.0': - resolution: {integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag==} - cpu: [x64] - os: [win32] - - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - - '@vue/compiler-core@3.5.17': - resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==} - - '@vue/compiler-dom@3.5.17': - resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==} - - '@vue/compiler-sfc@3.5.17': - resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==} - - '@vue/compiler-ssr@3.5.17': - resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==} - - '@vue/shared@3.5.17': - resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} - - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - anchor-markdown-header@0.6.0: - resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - are-docs-informative@0.0.2: - resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} - engines: {node: '>=14'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-differ@3.0.0: - resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} - engines: {node: '>=8'} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - bail@1.0.5: - resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bl@1.2.3: - resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} - - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - buffer-alloc-unsafe@1.1.0: - resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} - - buffer-alloc@1.2.0: - resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} - - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - - buffer-fill@1.0.0: - resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - - cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsite@1.0.0: - resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} - - ccount@1.1.0: - resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} - - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - change-case@4.1.2: - resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} - - character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - - character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} - - character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-progress@3.12.0: - resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} - engines: {node: '>=4'} - - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - comment-parser@1.3.1: - resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} - engines: {node: '>= 12.0.0'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confusing-browser-globals@1.0.11: - resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - constant-case@3.0.4: - resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - - cross-fetch@3.2.0: - resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - csv-parse@5.6.0: - resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - decompress-tar@4.1.1: - resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} - engines: {node: '>=4'} - - decompress-tarbz2@4.1.1: - resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} - engines: {node: '>=4'} - - decompress-targz@4.1.1: - resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} - engines: {node: '>=4'} - - decompress-unzip@4.0.1: - resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} - engines: {node: '>=4'} - - decompress@4.2.1: - resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} - engines: {node: '>=4'} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - depcheck@1.4.7: - resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} - engines: {node: '>=10'} - hasBin: true - - deps-regex@0.2.0: - resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} - - detect-file@1.0.0: - resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} - engines: {node: '>=0.10.0'} - - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - doctoc@2.2.1: - resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==} - hasBin: true - - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - emoji-regex@10.1.0: - resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - - entities@3.0.1: - resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} - engines: {node: '>=0.12'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - envalid@8.1.0: - resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} - engines: {node: '>=18'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - - esbuild@0.25.6: - resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-airbnb-base@15.0.0: - resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} - engines: {node: ^10.12.0 || >=12.0.0} - peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.2 - - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.10.1: - resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-eslint-comments@3.2.0: - resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} - engines: {node: '>=6.5.0'} - peerDependencies: - eslint: '>=4.19.1' - - eslint-plugin-import@2.27.5: - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jsdoc@41.1.2: - resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} - engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - expand-tilde@2.0.2: - resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} - engines: {node: '>=0.10.0'} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - extract-files@9.0.0: - resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==} - engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0} - - fast-csv@4.3.6: - resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} - engines: {node: '>=10.0.0'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} - - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - file-type@3.9.0: - resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} - engines: {node: '>=0.10.0'} - - file-type@5.2.0: - resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} - engines: {node: '>=4'} - - file-type@6.2.0: - resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} - engines: {node: '>=4'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} - engines: {node: '>=0.10.0'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - findup-sync@5.0.0: - resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} - engines: {node: '>= 10.13.0'} - - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data@3.0.3: - resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==} - engines: {node: '>= 6'} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - fp-ts@2.16.10: - resolution: {integrity: sha512-vuROzbNVfCmUkZSUbnWSltR1sbheyQbTzug7LB/46fEa1c0EucLeBaCEUE0gF3ZGUGBt9lVUiziGOhhj6K1ORA==} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - fuzzysearch@1.0.3: - resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@2.3.1: - resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} - engines: {node: '>=0.10.0'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} - - global-modules@1.0.0: - resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} - engines: {node: '>=0.10.0'} - - global-prefix@1.0.2: - resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} - engines: {node: '>=0.10.0'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - graphql-request@5.2.0: - resolution: {integrity: sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ==} - peerDependencies: - graphql: 14 - 16 - - graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - has@1.0.4: - resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} - engines: {node: '>= 0.4.0'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - header-case@2.0.4: - resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} - - homedir-polyfill@1.0.3: - resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} - engines: {node: '>=0.10.0'} - - htmlparser2@7.2.0: - resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} - - http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - - http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - inquirer-autocomplete-prompt@1.3.0: - resolution: {integrity: sha512-zvAc+A6SZdcN+earG5SsBu1RnQdtBS4o8wZ/OqJiCfL34cfOx+twVRq7wumYix6Rkdjn1N2nVCcO3wHqKqgdGg==} - engines: {node: '>=10'} - peerDependencies: - inquirer: ^5.0.0 || ^6.0.0 || ^7.0.0 - - inquirer@7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - io-ts-types@0.5.19: - resolution: {integrity: sha512-kQOYYDZG5vKre+INIDZbLeDJe+oM+4zLpUkjXyTMyUfoCpjJNyi29ZLkuEAwcPufaYo3yu/BsemZtbdD+NtRfQ==} - peerDependencies: - fp-ts: ^2.0.0 - io-ts: ^2.0.0 - monocle-ts: ^2.0.0 - newtype-ts: ^0.3.2 - - io-ts@2.2.22: - resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} - peerDependencies: - fp-ts: ^2.5.0 - - is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} - - is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - - is-bun-module@2.0.0: - resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-natural-number@4.0.1: - resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsdoc-type-pratt-parser@4.0.0: - resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} - engines: {node: '>=12.0.0'} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.2: - resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - - lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - - lodash.groupby@4.6.0: - resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} - - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - - lodash.isfunction@3.0.9: - resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnil@4.0.0: - resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.isundefined@3.0.1: - resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - longest-streak@2.0.4: - resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} - - loupe@3.1.4: - resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - make-dir@1.3.0: - resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} - engines: {node: '>=4'} - - markdown-table@2.0.0: - resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} - - matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mdast-util-find-and-replace@1.1.1: - resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} - - mdast-util-footnote@0.1.7: - resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==} - - mdast-util-from-markdown@0.8.5: - resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} - - mdast-util-frontmatter@0.2.0: - resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==} - - mdast-util-gfm-autolink-literal@0.1.3: - resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==} - - mdast-util-gfm-strikethrough@0.2.3: - resolution: {integrity: sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==} - - mdast-util-gfm-table@0.1.6: - resolution: {integrity: sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==} - - mdast-util-gfm-task-list-item@0.1.6: - resolution: {integrity: sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==} - - mdast-util-gfm@0.1.2: - resolution: {integrity: sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==} - - mdast-util-to-markdown@0.6.5: - resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} - - mdast-util-to-string@2.0.0: - resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromark-extension-footnote@0.3.2: - resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==} - - micromark-extension-frontmatter@0.2.2: - resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==} - - micromark-extension-gfm-autolink-literal@0.5.7: - resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==} - - micromark-extension-gfm-strikethrough@0.6.5: - resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==} - - micromark-extension-gfm-table@0.4.3: - resolution: {integrity: sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==} - - micromark-extension-gfm-tagfilter@0.3.0: - resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==} - - micromark-extension-gfm-task-list-item@0.3.3: - resolution: {integrity: sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==} - - micromark-extension-gfm@0.3.3: - resolution: {integrity: sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==} - - micromark@2.11.4: - resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@7.4.6: - resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} - engines: {node: '>=10'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - mlly@1.7.4: - resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} - - monocle-ts@2.3.13: - resolution: {integrity: sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==} - peerDependencies: - fp-ts: ^2.5.0 - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multimatch@5.0.0: - resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} - engines: {node: '>=10'} - - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - napi-postinstall@0.3.0: - resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - - natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - newtype-ts@0.3.5: - resolution: {integrity: sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==} - peerDependencies: - fp-ts: ^2.0.0 - monocle-ts: ^2.0.0 - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - package-manager-detector@1.3.0: - resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} - - param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@2.0.0: - resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-passwd@1.0.0: - resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} - engines: {node: '>=0.10.0'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-case@3.0.4: - resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - - pinkie-promise@2.0.1: - resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} - engines: {node: '>=0.10.0'} - - pinkie@2.0.4: - resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - please-upgrade-node@3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - publint@0.3.12: - resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} - engines: {node: '>=18'} - hasBin: true - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - query-string@7.0.0: - resolution: {integrity: sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - remark-footnotes@3.0.0: - resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} - - remark-frontmatter@3.0.0: - resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==} - - remark-gfm@1.0.0: - resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==} - - remark-parse@9.0.0: - resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-package-name@2.0.1: - resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} - - resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - - resolve-dir@1.0.1: - resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} - engines: {node: '>=0.10.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - - responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - - rollup@4.44.2: - resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} - - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - seek-bzip@1.0.6: - resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} - hasBin: true - - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - sentence-case@3.0.4: - resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} - - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shellcheck@3.1.0: - resolution: {integrity: sha512-C6IM1sziNIhCLyVszKZ/mKHQN2/CZ8qZ3sFt8mFOmL0ApoaXLTqyeEVfo+t4MlTVw+hS+kIqSaaGDDrrS0nKBA==} - engines: {node: '>=18.12.0'} - hasBin: true - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} - - split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} - engines: {node: '>=6'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - - stable-hash@0.0.5: - resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-dirs@2.1.0: - resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - tar-stream@1.6.2: - resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} - engines: {node: '>= 0.8.0'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - to-buffer@1.2.1: - resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} - engines: {node: '>= 0.4'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - traverse@0.6.11: - resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} - engines: {node: '>= 0.4'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - trough@1.0.5: - resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsup@8.5.0: - resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - - tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typedarray.prototype.slice@1.0.5: - resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} - engines: {node: '>= 0.4'} - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - - underscore@1.13.7: - resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} - - unified@9.2.2: - resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} - - unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - - unist-util-stringify-position@2.0.3: - resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} - - unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} - - unrs-resolver@1.11.0: - resolution: {integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg==} - - update-section@0.3.3: - resolution: {integrity: sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==} - - upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} - - upper-case@2.0.2: - resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vfile-message@2.0.4: - resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} - - vfile@4.2.1: - resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@7.0.2: - resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zwitch@1.0.5: - resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} - -snapshots: - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/generator@7.28.0': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - jsesc: 3.1.0 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/parser@7.28.0': - dependencies: - '@babel/types': 7.28.0 - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.0 - - '@babel/traverse@7.28.0': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/types': 7.28.0 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@emnapi/core@1.4.4': - dependencies: - '@emnapi/wasi-threads': 1.0.3 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.4': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@es-joy/jsdoccomment@0.37.1': - dependencies: - comment-parser: 1.3.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.0.0 - - '@esbuild/aix-ppc64@0.25.6': - optional: true - - '@esbuild/android-arm64@0.25.6': - optional: true - - '@esbuild/android-arm@0.25.6': - optional: true - - '@esbuild/android-x64@0.25.6': - optional: true - - '@esbuild/darwin-arm64@0.25.6': - optional: true - - '@esbuild/darwin-x64@0.25.6': - optional: true - - '@esbuild/freebsd-arm64@0.25.6': - optional: true - - '@esbuild/freebsd-x64@0.25.6': - optional: true - - '@esbuild/linux-arm64@0.25.6': - optional: true - - '@esbuild/linux-arm@0.25.6': - optional: true - - '@esbuild/linux-ia32@0.25.6': - optional: true - - '@esbuild/linux-loong64@0.25.6': - optional: true - - '@esbuild/linux-mips64el@0.25.6': - optional: true - - '@esbuild/linux-ppc64@0.25.6': - optional: true - - '@esbuild/linux-riscv64@0.25.6': - optional: true - - '@esbuild/linux-s390x@0.25.6': - optional: true - - '@esbuild/linux-x64@0.25.6': - optional: true - - '@esbuild/netbsd-arm64@0.25.6': - optional: true - - '@esbuild/netbsd-x64@0.25.6': - optional: true - - '@esbuild/openbsd-arm64@0.25.6': - optional: true - - '@esbuild/openbsd-x64@0.25.6': - optional: true - - '@esbuild/openharmony-arm64@0.25.6': - optional: true - - '@esbuild/sunos-x64@0.25.6': - optional: true - - '@esbuild/win32-arm64@0.25.6': - optional: true - - '@esbuild/win32-ia32@0.25.6': - optional: true - - '@esbuild/win32-x64@0.25.6': - optional: true - - '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@fast-csv/format@4.3.5': - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.isboolean: 3.0.3 - lodash.isequal: 4.5.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - - '@fast-csv/parse@4.3.6': - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.groupby: 4.6.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - lodash.isundefined: 3.0.1 - lodash.uniq: 4.5.0 - - '@fastify/busboy@2.1.1': {} + engines: {node: '>=14'} + dev: false - '@graphql-typed-document-node/core@3.2.0(graphql@16.11.0)': + /@graphql-typed-document-node/core@3.2.0(graphql@16.11.0): + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: graphql: 16.11.0 + dev: false - '@humanwhocodes/config-array@0.13.0': + /@humanwhocodes/config-array@0.13.0: + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true - '@humanwhocodes/module-importer@1.0.1': {} + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true - '@humanwhocodes/object-schema@2.0.3': {} + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + dev: true - '@isaacs/cliui@8.0.2': + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} dependencies: string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 + string-width-cjs: /string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true - '@jridgewell/gen-mapping@0.3.12': + /@jridgewell/gen-mapping@0.3.12: + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} dependencies: '@jridgewell/sourcemap-codec': 1.5.4 '@jridgewell/trace-mapping': 0.3.29 + dev: true - '@jridgewell/resolve-uri@3.1.2': {} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true - '@jridgewell/sourcemap-codec@1.5.4': {} + /@jridgewell/sourcemap-codec@1.5.4: + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + dev: true - '@jridgewell/trace-mapping@0.3.29': + /@jridgewell/trace-mapping@0.3.29: + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + dev: true - '@napi-rs/wasm-runtime@0.2.11': + /@napi-rs/wasm-runtime@0.2.12: + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + requiresBuild: true dependencies: - '@emnapi/core': 1.4.4 - '@emnapi/runtime': 1.4.4 - '@tybys/wasm-util': 0.9.0 + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@tybys/wasm-util': 0.10.0 + dev: true optional: true - '@nodelib/fs.scandir@2.1.5': + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - '@nodelib/fs.walk@1.2.8': + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nolyfill/is-core-module@1.0.39': {} + /@nolyfill/is-core-module@1.0.39: + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + dev: true + + /@opentelemetry/api@1.9.0: + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + dev: false + + /@opentelemetry/core@1.3.1(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-k7lOC86N7WIyUZsUuSKZfFIrUtINtlauMGQsC1r7jNmcr0vVJGqK1ROBvt7WWMxLbpMnt1q2pXJO8tKu0b9auA==} + engines: {node: '>=8.12.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.2.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.3.1 + dev: false + + /@opentelemetry/semantic-conventions@1.3.1: + resolution: {integrity: sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA==} + engines: {node: '>=8.12.0'} + dev: false - '@pkgjs/parseargs@0.11.0': + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true optional: true - '@publint/pack@0.1.2': {} + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false - '@rollup/rollup-android-arm-eabi@4.44.2': + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + + /@publint/pack@0.1.2: + resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} + engines: {node: '>=18'} + dev: true + + /@rollup/rollup-android-arm-eabi@4.46.2: + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-android-arm64@4.44.2': + /@rollup/rollup-android-arm64@4.46.2: + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-darwin-arm64@4.44.2': + /@rollup/rollup-darwin-arm64@4.46.2: + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-darwin-x64@4.44.2': + /@rollup/rollup-darwin-x64@4.46.2: + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-freebsd-arm64@4.44.2': + /@rollup/rollup-freebsd-arm64@4.46.2: + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-freebsd-x64@4.44.2': + /@rollup/rollup-freebsd-x64@4.46.2: + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.44.2': + /@rollup/rollup-linux-arm-gnueabihf@4.46.2: + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm-musleabihf@4.44.2': + /@rollup/rollup-linux-arm-musleabihf@4.46.2: + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm64-gnu@4.44.2': + /@rollup/rollup-linux-arm64-gnu@4.46.2: + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-arm64-musl@4.44.2': + /@rollup/rollup-linux-arm64-musl@4.46.2: + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.44.2': + /@rollup/rollup-linux-loongarch64-gnu@4.46.2: + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': + /@rollup/rollup-linux-ppc64-gnu@4.46.2: + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-riscv64-gnu@4.44.2': + /@rollup/rollup-linux-riscv64-gnu@4.46.2: + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-riscv64-musl@4.44.2': + /@rollup/rollup-linux-riscv64-musl@4.46.2: + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-s390x-gnu@4.44.2': + /@rollup/rollup-linux-s390x-gnu@4.46.2: + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-x64-gnu@4.44.2': + /@rollup/rollup-linux-x64-gnu@4.46.2: + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-linux-x64-musl@4.44.2': + /@rollup/rollup-linux-x64-musl@4.46.2: + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-arm64-msvc@4.44.2': + /@rollup/rollup-win32-arm64-msvc@4.46.2: + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-ia32-msvc@4.44.2': + /@rollup/rollup-win32-ia32-msvc@4.46.2: + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@rollup/rollup-win32-x64-msvc@4.44.2': + /@rollup/rollup-win32-x64-msvc@4.46.2: + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@sindresorhus/is@4.6.0': {} + /@sindresorhus/is@4.6.0: + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + dev: false - '@stricli/auto-complete@1.2.0': + /@stricli/auto-complete@1.2.0: + resolution: {integrity: sha512-r9/msiloVmTF95mdhe04Uzqei1B0ZofhYRLeiPqpJ1W1RMCC8p9iW7kqBZEbALl2aRL5ZK9OEW3Q1cIejH7KEQ==} + hasBin: true dependencies: '@stricli/core': 1.2.0 + dev: false - '@stricli/core@1.2.0': {} + /@stricli/core@1.2.0: + resolution: {integrity: sha512-5b+npntDY0TAB7wAw0daGlh3/R2sf0TDLyrB1By2jCNH+C+lmcSqMtJXOMLVtEGSkIOvqAgIWpLMSs1PXqzt3w==} + dev: false - '@szmarczak/http-timer@4.0.6': + /@szmarczak/http-timer@4.0.6: + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} dependencies: defer-to-connect: 2.0.1 + dev: false - '@textlint/ast-node-types@12.6.1': {} + /@textlint/ast-node-types@12.6.1: + resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} + dev: true - '@textlint/markdown-to-ast@12.6.1': + /@textlint/markdown-to-ast@12.6.1: + resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} dependencies: '@textlint/ast-node-types': 12.6.1 debug: 4.4.1 @@ -3713,196 +1075,343 @@ snapshots: unified: 9.2.2 transitivePeerDependencies: - supports-color + dev: true - '@transcend-io/airgap.js-types@12.12.2': + /@transcend-io/airgap.js-types@12.12.2: + resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) + dev: false - '@transcend-io/handlebars-utils@1.1.0': + /@transcend-io/handlebars-utils@1.1.0: + resolution: {integrity: sha512-mDbKm9JObd9mioNlEYGa2zfTBqli1KAAM+iRHSqi6s2l7MAYENxjlfvXN5uC97T9/90vfWlxNbHjLpUuQVURaA==} dependencies: '@transcend-io/type-utils': 1.8.4 change-case: 4.1.2 handlebars: 4.7.8 lodash: 4.17.21 pluralize: 8.0.0 + dev: false - '@transcend-io/internationalization@1.7.4': {} + /@transcend-io/internationalization@1.7.4: + resolution: {integrity: sha512-kg6c2Bjr823hBWrx62074ZlyBcW1FvK1dj77+lN7E70EyYxj6o5PordsOzxbLmTV1iZ0GLKGu+QiNl4xVOWfLA==} + dev: false - '@transcend-io/persisted-state@1.0.4': + /@transcend-io/persisted-state@1.0.4: + resolution: {integrity: sha512-jEDN64pr5Q1fniHMERa2+DKhuHbwdB1zzGdHoWgq4EIEx/ZJQxraiNfYoCpf5hpuiKxtXcIN41SC2/MI6MraoQ==} dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) mkdirp: 1.0.4 + dev: false - '@transcend-io/privacy-types@4.128.0': + /@transcend-io/privacy-types@4.128.0: + resolution: {integrity: sha512-iOFVTpdM+ZFX+YxmqtK5mDJNYAlwVa7JhPg4Yg0pUSFcck7LvZlxFVNwpHVoGGdguy6Ejsh8hfPYKHhBOgBqMQ==} dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) + dev: false - '@transcend-io/secret-value@1.2.1': + /@transcend-io/secret-value@1.2.1: + resolution: {integrity: sha512-pb2thX9znGJ9GOm6A2kgIzwEchB3E6Zn1KTLIBJx+T+K7XkqRwQJsQL0vKGiJVmZZ0iB3bLSAABHoWwNAZcFxA==} dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) + dev: false - '@transcend-io/type-utils@1.8.4': + /@transcend-io/type-utils@1.8.4: + resolution: {integrity: sha512-J1/T+q2Bs5znAyxQpQjD0wzN+h7Wi1xRsqa99uAu3G8VcKb5nRLnaUyY7Ut8N0YU1FHAxNhyXz9MVakYUigWuw==} dependencies: fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) + dev: false - '@tybys/wasm-util@0.9.0': + /@tybys/wasm-util@0.10.0: + resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + requiresBuild: true dependencies: tslib: 2.8.1 + dev: true optional: true - '@types/cacheable-request@6.0.3': + /@types/bluebird@3.5.42: + resolution: {integrity: sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==} + dev: true + + /@types/cacheable-request@6.0.3: + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 18.19.115 + '@types/node': 18.19.121 '@types/responselike': 1.0.3 + dev: false - '@types/chai@5.2.2': + /@types/chai@5.2.2: + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} dependencies: '@types/deep-eql': 4.0.2 + dev: true - '@types/cli-progress@3.11.6': + /@types/cli-progress@3.11.6: + resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} dependencies: - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: true - '@types/colors@1.2.4': + /@types/colors@1.2.4: + resolution: {integrity: sha512-oSQxEVIDcYisAzWLa+wr50GSIPu8ml4PsKNJzgrDX3SmEHVBBqbaUurqsUceFauNlCRxNtENKkQm3yOe3m3nfg==} + deprecated: This is a stub types definition. colors provides its own type definitions, so you do not need this installed. dependencies: colors: 1.4.0 + dev: true + + /@types/continuation-local-storage@3.2.7: + resolution: {integrity: sha512-Q7dPOymVpRG5Zpz90/o26+OAqOG2Sw+FED7uQmTrJNCF/JAPTylclZofMxZKd6W7g1BDPmT9/C/jX0ZcSNTQwQ==} + dependencies: + '@types/node': 18.19.121 + dev: true + + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + dependencies: + '@types/ms': 2.1.0 + dev: false - '@types/deep-eql@4.0.2': {} + /@types/deep-eql@4.0.2: + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + dev: true - '@types/estree@1.0.8': {} + /@types/estree@1.0.8: + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + dev: true - '@types/fuzzysearch@1.0.2': {} + /@types/fuzzysearch@1.0.2: + resolution: {integrity: sha512-G2M0M7acg75BzTtUv1qmPFMe7nTwX1K2AEKNeBO8zW0o+H2oTmyir7IJ2v0UW8pZkY4nHgHALvgpWQpB9WXPpg==} + dev: true - '@types/global-agent@2.1.3': {} + /@types/global-agent@2.1.3: + resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} + dev: true - '@types/http-cache-semantics@4.0.4': {} + /@types/http-cache-semantics@4.0.4: + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + dev: false - '@types/inquirer-autocomplete-prompt@3.0.3': + /@types/inquirer-autocomplete-prompt@3.0.3: + resolution: {integrity: sha512-OQCW09mEECgvhcppbQRgZSmWskWv58l+WwyUvWB1oxTu3CZj8keYSDZR9U8owUzJ5Zeux5kacN9iVPJLXcoLXg==} dependencies: '@types/inquirer': 7.3.3 + dev: true - '@types/inquirer@7.3.3': + /@types/inquirer@7.3.3: + resolution: {integrity: sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==} dependencies: '@types/through': 0.0.33 rxjs: 6.6.7 + dev: true - '@types/js-yaml@4.0.9': {} + /@types/js-yaml@4.0.9: + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + dev: true - '@types/json-schema@7.0.15': {} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true - '@types/json5@0.0.29': {} + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true - '@types/jsonstream@0.8.33': + /@types/jsonstream@0.8.33: + resolution: {integrity: sha512-yhg1SNOgJ8y2nOkvAQ1zZ1Z2xibxgFs7984+EeBPuWgo/TbuYo79+rj2wUVch3KF4GhhcwAi/AlJcehmLCXb3g==} dependencies: - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: true - '@types/jsonwebtoken@9.0.10': + /@types/jsonwebtoken@9.0.10: + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} dependencies: '@types/ms': 2.1.0 - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: true - '@types/keyv@3.1.4': + /@types/keyv@3.1.4: + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: false - '@types/lodash-es@4.17.12': + /@types/lodash-es@4.17.12: + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} dependencies: '@types/lodash': 4.17.20 + dev: true - '@types/lodash@4.17.20': {} + /@types/lodash@4.17.20: + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + dev: true - '@types/mdast@3.0.15': + /@types/mdast@3.0.15: + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} dependencies: '@types/unist': 2.0.11 + dev: true + + /@types/minimatch@3.0.5: + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + dev: true - '@types/minimatch@3.0.5': {} + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/ms@2.1.0': {} + /@types/node@14.18.63: + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + dev: false - '@types/node@14.18.63': {} + /@types/node@18.11.19: + resolution: {integrity: sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==} + dev: false - '@types/node@18.19.115': + /@types/node@18.19.121: + resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==} dependencies: undici-types: 5.26.5 - '@types/parse-json@4.0.2': {} + /@types/parse-json@4.0.2: + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: true - '@types/responselike@1.0.3': + /@types/responselike@1.0.3: + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: false - '@types/semver@7.7.0': {} + /@types/semver@7.7.0: + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + dev: true + + /@types/sequelize@4.28.20: + resolution: {integrity: sha512-XaGOKRhdizC87hDgQ0u3btxzbejlF+t6Hhvkek1HyphqCI4y7zVBIVAGmuc4cWJqGpxusZ1RiBToHHnNK/Edlw==} + dependencies: + '@types/bluebird': 3.5.42 + '@types/continuation-local-storage': 3.2.7 + '@types/lodash': 4.17.20 + '@types/validator': 13.15.2 + dev: true - '@types/through@0.0.33': + /@types/through@0.0.33: + resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} dependencies: - '@types/node': 18.19.115 + '@types/node': 18.19.121 + dev: true + + /@types/unist@2.0.11: + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + dev: true - '@types/unist@2.0.11': {} + /@types/validator@13.15.2: + resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} - '@types/yargs-parser@21.0.3': {} + /@types/yargs-parser@21.0.3: + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + dev: true - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.0.4): + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) debug: 4.4.1 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.8.3) - optionalDependencies: - typescript: 5.8.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3)': + /@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.0.4): + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) debug: 4.4.1 eslint: 8.57.1 - optionalDependencies: - typescript: 5.8.3 + typescript: 5.0.4 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/scope-manager@5.62.0': + /@typescript-eslint/scope-manager@5.62.0: + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 + dev: true - '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': + /@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.0.4): + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) debug: 4.4.1 eslint: 8.57.1 - tsutils: 3.21.0(typescript@5.8.3) - optionalDependencies: - typescript: 5.8.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/types@5.62.0': {} + /@typescript-eslint/types@5.62.0: + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.0.4): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -3910,221 +1419,408 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.8.3) - optionalDependencies: - typescript: 5.8.3 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': + /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.0.4): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) eslint: 8.57.1 eslint-scope: 5.1.1 semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript + dev: true - '@typescript-eslint/visitor-keys@5.62.0': + /@typescript-eslint/visitor-keys@5.62.0: + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 + dev: true - '@ungap/structured-clone@1.3.0': {} + /@ungap/structured-clone@1.3.0: + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + dev: true - '@unrs/resolver-binding-android-arm-eabi@1.11.0': + /@unrs/resolver-binding-android-arm-eabi@1.11.1: + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-android-arm64@1.11.0': + /@unrs/resolver-binding-android-arm64@1.11.1: + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-darwin-arm64@1.11.0': + /@unrs/resolver-binding-darwin-arm64@1.11.1: + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-darwin-x64@1.11.0': + /@unrs/resolver-binding-darwin-x64@1.11.1: + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-freebsd-x64@1.11.0': + /@unrs/resolver-binding-freebsd-x64@1.11.1: + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': + /@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1: + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': + /@unrs/resolver-binding-linux-arm-musleabihf@1.11.1: + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': + /@unrs/resolver-binding-linux-arm64-gnu@1.11.1: + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.11.0': + /@unrs/resolver-binding-linux-arm64-musl@1.11.1: + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': + /@unrs/resolver-binding-linux-ppc64-gnu@1.11.1: + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': + /@unrs/resolver-binding-linux-riscv64-gnu@1.11.1: + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': + /@unrs/resolver-binding-linux-riscv64-musl@1.11.1: + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': + /@unrs/resolver-binding-linux-s390x-gnu@1.11.1: + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.11.0': + /@unrs/resolver-binding-linux-x64-gnu@1.11.1: + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-linux-x64-musl@1.11.0': + /@unrs/resolver-binding-linux-x64-musl@1.11.1: + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-wasm32-wasi@1.11.0': + /@unrs/resolver-binding-wasm32-wasi@1.11.1: + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + requiresBuild: true dependencies: - '@napi-rs/wasm-runtime': 0.2.11 + '@napi-rs/wasm-runtime': 0.2.12 + dev: true optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': + /@unrs/resolver-binding-win32-arm64-msvc@1.11.1: + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': + /@unrs/resolver-binding-win32-ia32-msvc@1.11.1: + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.0': + /@unrs/resolver-binding-win32-x64-msvc@1.11.1: + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true - '@vitest/expect@3.2.4': + /@vitest/expect@3.2.4: + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} dependencies: '@types/chai': 5.2.2 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.0 + chai: 5.2.1 tinyrainbow: 2.0.0 + dev: true - '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3))': + /@vitest/mocker@3.2.4(vite@7.0.6): + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 - optionalDependencies: - vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) + vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) + dev: true - '@vitest/pretty-format@3.2.4': + /@vitest/pretty-format@3.2.4: + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} dependencies: tinyrainbow: 2.0.0 + dev: true - '@vitest/runner@3.2.4': + /@vitest/runner@3.2.4: + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 + dev: true - '@vitest/snapshot@3.2.4': + /@vitest/snapshot@3.2.4: + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 + dev: true - '@vitest/spy@3.2.4': + /@vitest/spy@3.2.4: + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} dependencies: tinyspy: 4.0.3 + dev: true - '@vitest/utils@3.2.4': + /@vitest/utils@3.2.4: + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} dependencies: '@vitest/pretty-format': 3.2.4 - loupe: 3.1.4 + loupe: 3.2.0 tinyrainbow: 2.0.0 + dev: true - '@vue/compiler-core@3.5.17': + /@vue/compiler-core@3.5.18: + resolution: {integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==} dependencies: '@babel/parser': 7.28.0 - '@vue/shared': 3.5.17 + '@vue/shared': 3.5.18 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 + dev: true - '@vue/compiler-dom@3.5.17': + /@vue/compiler-dom@3.5.18: + resolution: {integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==} dependencies: - '@vue/compiler-core': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/compiler-core': 3.5.18 + '@vue/shared': 3.5.18 + dev: true - '@vue/compiler-sfc@3.5.17': + /@vue/compiler-sfc@3.5.18: + resolution: {integrity: sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==} dependencies: '@babel/parser': 7.28.0 - '@vue/compiler-core': 3.5.17 - '@vue/compiler-dom': 3.5.17 - '@vue/compiler-ssr': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/compiler-core': 3.5.18 + '@vue/compiler-dom': 3.5.18 + '@vue/compiler-ssr': 3.5.18 + '@vue/shared': 3.5.18 estree-walker: 2.0.2 magic-string: 0.30.17 postcss: 8.5.6 source-map-js: 1.2.1 + dev: true - '@vue/compiler-ssr@3.5.17': + /@vue/compiler-ssr@3.5.18: + resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==} dependencies: - '@vue/compiler-dom': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/compiler-dom': 3.5.18 + '@vue/shared': 3.5.18 + dev: true - '@vue/shared@3.5.17': {} + /@vue/shared@3.5.18: + resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} + dev: true - JSONStream@1.3.5: + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true dependencies: jsonparse: 1.3.1 through: 2.3.8 + dev: false + + /acorn-import-attributes@1.9.5(acorn@8.15.0): + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.15.0 + dev: false - acorn-jsx@5.3.2(acorn@8.15.0): + /acorn-jsx@5.3.2(acorn@8.15.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.15.0 + dev: true - acorn@8.15.0: {} + /acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true - ajv@6.12.6: + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + dev: true - anchor-markdown-header@0.6.0: + /anchor-markdown-header@0.6.0: + resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==} dependencies: emoji-regex: 10.1.0 + dev: true - ansi-escapes@4.3.2: + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: false - ansi-regex@5.0.1: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - ansi-regex@6.1.0: {} + /ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + dev: true - ansi-styles@4.3.0: + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true - any-promise@1.3.0: {} + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true - are-docs-informative@0.0.2: {} + /are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + dev: true - argparse@1.0.10: + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 + dev: true - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.2: + /array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 + dev: true - array-differ@3.0.0: {} + /array-differ@3.0.0: + resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} + engines: {node: '>=8'} + dev: true - array-includes@3.1.9: + /array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -4134,24 +1830,36 @@ snapshots: get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 + dev: true - array-union@2.1.0: {} + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true - array.prototype.flat@1.3.3: + /array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 + dev: true - array.prototype.flatmap@1.3.3: + /array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 + dev: true - arraybuffer.prototype.slice@1.0.4: + /arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 @@ -4160,73 +1868,133 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + dev: true - arrify@2.0.1: {} + /arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} + dev: true - assertion-error@2.0.1: {} + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + dev: true - async-function@1.0.0: {} + /async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + dev: true - asynckit@0.4.0: {} + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false - available-typed-arrays@1.0.7: + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} dependencies: possible-typed-array-names: 1.1.0 + dev: true - bail@1.0.5: {} + /bail@1.0.5: + resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + dev: true - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true - base64-js@1.5.1: {} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true - bl@1.2.3: + /bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} dependencies: readable-stream: 2.3.8 safe-buffer: 5.2.1 + dev: true + + /bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: false - boolean@3.2.0: {} + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - brace-expansion@1.1.12: + /brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true - brace-expansion@2.0.2: + /brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} dependencies: balanced-match: 1.0.2 + dev: true - braces@3.0.3: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - buffer-alloc-unsafe@1.1.0: {} + /buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + dev: true - buffer-alloc@1.2.0: + /buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} dependencies: buffer-alloc-unsafe: 1.1.0 buffer-fill: 1.0.0 + dev: true - buffer-crc32@0.2.13: {} + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true - buffer-equal-constant-time@1.0.1: {} + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false - buffer-fill@1.0.0: {} + /buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + dev: true - buffer@5.7.1: + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true - bundle-require@5.1.0(esbuild@0.25.6): + /bundle-require@5.1.0(esbuild@0.25.8): + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' dependencies: - esbuild: 0.25.6 + esbuild: 0.25.8 load-tsconfig: 0.2.5 + dev: true - cac@6.7.14: {} + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true - cacheable-lookup@5.0.4: {} + /cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + dev: false - cacheable-request@7.0.4: + /cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} dependencies: clone-response: 1.0.3 get-stream: 5.2.0 @@ -4235,57 +2003,86 @@ snapshots: lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 + dev: false - call-bind-apply-helpers@1.0.2: + /call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 get-intrinsic: 1.3.0 set-function-length: 1.2.2 + dev: true - call-bound@1.0.4: + /call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + dev: true - callsite@1.0.0: {} + /callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + dev: true - callsites@3.1.0: {} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true - camel-case@4.1.2: + /camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 tslib: 2.8.1 + dev: false - camelcase@6.3.0: {} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true - capital-case@1.0.4: + /capital-case@1.0.4: + resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case-first: 2.0.2 + dev: false - ccount@1.1.0: {} + /ccount@1.1.0: + resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} + dev: true - chai@5.2.0: + /chai@5.2.1: + resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} + engines: {node: '>=18'} dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.4 + loupe: 3.2.0 pathval: 2.0.1 + dev: true - chalk@4.1.2: + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - change-case@4.1.2: + /change-case@4.1.2: + resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} dependencies: camel-case: 4.1.2 capital-case: 1.0.4 @@ -4299,157 +2096,315 @@ snapshots: sentence-case: 3.0.4 snake-case: 3.0.4 tslib: 2.8.1 + dev: false - character-entities-legacy@1.1.4: {} + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: true - character-entities@1.2.4: {} + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: true - character-reference-invalid@1.1.4: {} + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: true - chardet@0.7.0: {} + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: false - check-error@2.1.1: {} + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + dev: true - chokidar@4.0.3: + /chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} dependencies: readdirp: 4.1.2 + dev: true - cli-cursor@3.1.0: + /cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + dev: false + + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: false - cli-progress@3.12.0: + /cli-progress@3.12.0: + resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} + engines: {node: '>=4'} dependencies: string-width: 4.2.3 + dev: false - cli-width@3.0.0: {} + /cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: false - cliui@7.0.4: + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + dev: true - clone-response@1.0.3: + /clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: mimic-response: 1.0.1 + dev: false - color-convert@2.0.1: + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - color-name@1.1.4: {} + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colors@1.4.0: {} + /colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} - combined-stream@1.0.8: + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: false - commander@2.20.3: {} + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: true - commander@4.1.1: {} + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true - comment-parser@1.3.1: {} + /comment-parser@1.3.1: + resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} + engines: {node: '>= 12.0.0'} + dev: true - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true - confbox@0.1.8: {} + /confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + dev: true - confusing-browser-globals@1.0.11: {} + /confusing-browser-globals@1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + dev: true - consola@3.4.2: {} + /consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: true - constant-case@3.0.4: + /constant-case@3.0.4: + resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case: 2.0.2 + dev: false - core-util-is@1.0.3: {} + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true - cosmiconfig@7.1.0: + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} dependencies: '@types/parse-json': 4.0.2 import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 + dev: true - cross-fetch@3.2.0: + /cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} dependencies: node-fetch: 2.7.0 transitivePeerDependencies: - encoding + dev: false - cross-spawn@7.0.6: + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + dev: true + + /crypto-randomuuid@1.0.0: + resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} + dev: false - csv-parse@5.6.0: {} + /csv-parse@5.6.0: + resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} + dev: false - data-view-buffer@1.0.2: + /data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true - data-view-byte-length@1.0.2: + /data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true - data-view-byte-offset@1.0.1: + /data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + dev: true + + /dd-trace@2.45.1: + resolution: {integrity: sha512-FZu1kvYRFwqR1LHR3iBlSU60R33rdWD+Wtd9yQG5VIlkbrNFTIB0IXSC4fjUusPmvkUUxo4nP0roQBwyxHdnZA==} + engines: {node: '>=12'} + deprecated: The v2.x release line of dd-trace reached End of Life on 2023-08-15! Please upgrade to the latest version of dd-trace to continue receiving updates. + requiresBuild: true + dependencies: + '@datadog/native-appsec': 3.2.0 + '@datadog/native-iast-rewriter': 2.0.1 + '@datadog/native-iast-taint-tracking': 1.5.0 + '@datadog/native-metrics': 1.6.0 + '@datadog/pprof': 3.1.0 + '@datadog/sketches-js': 2.1.1 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.3.1(@opentelemetry/api@1.9.0) + '@types/node': 18.11.19 + crypto-randomuuid: 1.0.0 + diagnostics_channel: 1.1.0 + ignore: 5.3.2 + import-in-the-middle: 1.14.2 + int64-buffer: 0.1.10 + ipaddr.js: 2.2.0 + istanbul-lib-coverage: 3.2.0 + koalas: 1.0.2 + limiter: 1.1.5 + lodash.kebabcase: 4.1.1 + lodash.pick: 4.4.0 + lodash.sortby: 4.7.0 + lodash.uniq: 4.5.0 + lru-cache: 7.18.3 + methods: 1.1.2 + module-details-from-path: 1.0.4 + msgpack-lite: 0.1.26 + node-abort-controller: 3.1.1 + opentracing: 0.14.7 + path-to-regexp: 0.1.12 + protobufjs: 7.5.3 + retry: 0.13.1 + semver: 7.7.2 + dev: false - debug@3.2.7: + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 + dev: true - debug@4.4.1: + /debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - decode-uri-component@0.2.2: {} + /decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dev: false - decompress-response@6.0.0: + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} dependencies: mimic-response: 3.1.0 + dev: false - decompress-tar@4.1.1: + /decompress-tar@4.1.1: + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} dependencies: file-type: 5.2.0 is-stream: 1.1.0 tar-stream: 1.6.2 + dev: true - decompress-tarbz2@4.1.1: + /decompress-tarbz2@4.1.1: + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} dependencies: decompress-tar: 4.1.1 file-type: 6.2.0 is-stream: 1.1.0 seek-bzip: 1.0.6 unbzip2-stream: 1.4.3 + dev: true - decompress-targz@4.1.1: + /decompress-targz@4.1.1: + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} dependencies: decompress-tar: 4.1.1 file-type: 5.2.0 is-stream: 1.1.0 + dev: true - decompress-unzip@4.0.1: + /decompress-unzip@4.0.1: + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} dependencies: file-type: 3.9.0 get-stream: 2.3.1 pify: 2.3.0 yauzl: 2.10.0 + dev: true - decompress@4.2.1: + /decompress@4.2.1: + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} dependencies: decompress-tar: 4.1.1 decompress-tarbz2: 4.1.1 @@ -4459,32 +2414,56 @@ snapshots: make-dir: 1.3.0 pify: 2.3.0 strip-dirs: 2.1.0 + dev: true - deep-eql@5.0.2: {} + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + dev: true - deep-is@0.1.4: {} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true - defer-to-connect@2.0.1: {} + /defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + dev: false - define-data-property@1.1.4: + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - define-properties@1.2.1: + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} + /delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + dev: false + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false - depcheck@1.4.7: + /depcheck@1.4.7: + resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} + engines: {node: '>=10'} + hasBin: true dependencies: '@babel/parser': 7.28.0 '@babel/traverse': 7.28.0 - '@vue/compiler-sfc': 3.5.17 + '@vue/compiler-sfc': 3.5.18 callsite: 1.0.0 camelcase: 6.3.0 cosmiconfig: 7.1.0 @@ -4507,18 +2486,35 @@ snapshots: yargs: 16.2.0 transitivePeerDependencies: - supports-color + dev: true - deps-regex@0.2.0: {} + /deps-regex@0.2.0: + resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} + dev: true + + /detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + dev: true - detect-file@1.0.0: {} + /detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - detect-node@2.1.0: {} + /diagnostics_channel@1.1.0: + resolution: {integrity: sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==} + engines: {node: '>=4'} + dev: false - dir-glob@3.0.1: + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dependencies: path-type: 4.0.0 + dev: true - doctoc@2.2.1: + /doctoc@2.2.1: + resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==} + hasBin: true dependencies: '@textlint/markdown-to-ast': 12.6.1 anchor-markdown-header: 0.6.0 @@ -4528,75 +2524,124 @@ snapshots: update-section: 0.3.3 transitivePeerDependencies: - supports-color + dev: true - doctrine@2.1.0: + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dependencies: esutils: 2.0.3 + dev: true - doctrine@3.0.0: + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true - dom-serializer@1.4.1: + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 entities: 2.2.0 + dev: true - domelementtype@2.3.0: {} + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true - domhandler@4.3.1: + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} dependencies: domelementtype: 2.3.0 + dev: true - domutils@2.8.0: + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} dependencies: dom-serializer: 1.4.1 domelementtype: 2.3.0 domhandler: 4.3.1 + dev: true - dot-case@3.0.4: + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 tslib: 2.8.1 + dev: false - dunder-proto@1.0.1: + /dottie@2.0.6: + resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} + dev: false + + /dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true - ecdsa-sig-formatter@1.0.11: + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: safe-buffer: 5.2.1 + dev: false - emoji-regex@10.1.0: {} + /emoji-regex@10.1.0: + resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} + dev: true - emoji-regex@8.0.0: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: {} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true - end-of-stream@1.4.5: + /end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} dependencies: once: 1.4.0 - entities@2.2.0: {} + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: true - entities@3.0.1: {} + /entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + dev: true - entities@4.5.0: {} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true - envalid@8.1.0: + /envalid@8.1.0: + resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} + engines: {node: '>=18'} dependencies: tslib: 2.8.1 + dev: true - error-ex@1.3.2: + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true - es-abstract@1.24.0: + /es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -4652,122 +2697,204 @@ snapshots: typed-array-length: 1.0.7 unbox-primitive: 1.1.0 which-typed-array: 1.1.19 + dev: true - es-define-property@1.0.1: {} + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} - es-errors@1.3.0: {} + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: {} + /es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + dev: true - es-object-atoms@1.1.1: + /es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.1.0: + /es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - es-shim-unscopables@1.1.0: + /es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - es-to-primitive@1.3.0: + /es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 + dev: true - es6-error@4.1.1: {} + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.25.6: + /esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.25.6 - '@esbuild/android-arm': 0.25.6 - '@esbuild/android-arm64': 0.25.6 - '@esbuild/android-x64': 0.25.6 - '@esbuild/darwin-arm64': 0.25.6 - '@esbuild/darwin-x64': 0.25.6 - '@esbuild/freebsd-arm64': 0.25.6 - '@esbuild/freebsd-x64': 0.25.6 - '@esbuild/linux-arm': 0.25.6 - '@esbuild/linux-arm64': 0.25.6 - '@esbuild/linux-ia32': 0.25.6 - '@esbuild/linux-loong64': 0.25.6 - '@esbuild/linux-mips64el': 0.25.6 - '@esbuild/linux-ppc64': 0.25.6 - '@esbuild/linux-riscv64': 0.25.6 - '@esbuild/linux-s390x': 0.25.6 - '@esbuild/linux-x64': 0.25.6 - '@esbuild/netbsd-arm64': 0.25.6 - '@esbuild/netbsd-x64': 0.25.6 - '@esbuild/openbsd-arm64': 0.25.6 - '@esbuild/openbsd-x64': 0.25.6 - '@esbuild/openharmony-arm64': 0.25.6 - '@esbuild/sunos-x64': 0.25.6 - '@esbuild/win32-arm64': 0.25.6 - '@esbuild/win32-ia32': 0.25.6 - '@esbuild/win32-x64': 0.25.6 - - escalade@3.2.0: {} - - escape-string-regexp@1.0.5: {} - - escape-string-regexp@4.0.0: {} - - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.27.5)(eslint@8.57.1): + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + dev: true + + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.27.5)(eslint@8.57.1): + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) object.assign: 4.1.7 object.entries: 1.1.9 semver: 6.3.1 + dev: true - eslint-import-resolver-node@0.3.9: + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: debug: 3.2.7 is-core-module: 2.16.1 resolve: 1.22.10 transitivePeerDependencies: - supports-color + dev: true - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1): + /eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1): + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 eslint: 8.57.1 + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.14 - unrs-resolver: 1.11.0 - optionalDependencies: - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + unrs-resolver: 1.11.1 transitivePeerDependencies: - supports-color + dev: true - eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + /eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1) transitivePeerDependencies: - supports-color + dev: true - eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' dependencies: escape-string-regexp: 1.0.5 eslint: 8.57.1 ignore: 5.3.2 + dev: true - eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) array-includes: 3.1.9 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 @@ -4775,7 +2902,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) has: 1.0.4 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4784,14 +2911,17 @@ snapshots: resolve: 1.22.10 semver: 6.3.1 tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true - eslint-plugin-jsdoc@41.1.2(eslint@8.57.1): + /eslint-plugin-jsdoc@41.1.2(eslint@8.57.1): + resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} + engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 dependencies: '@es-joy/jsdoccomment': 0.37.1 are-docs-informative: 0.0.2 @@ -4804,20 +2934,34 @@ snapshots: spdx-expression-parse: 3.0.1 transitivePeerDependencies: - supports-color + dev: true - eslint-scope@5.1.1: + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 + dev: true - eslint-scope@7.2.2: + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true - eslint-visitor-keys@3.4.3: {} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true - eslint@8.57.1: + /eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@eslint-community/regexpp': 4.12.1 @@ -4859,59 +3003,111 @@ snapshots: text-table: 0.2.0 transitivePeerDependencies: - supports-color + dev: true - espree@9.6.1: + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 + dev: true - esprima@4.0.1: {} + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true - esquery@1.6.0: + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true - esrecurse@4.3.0: + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 + dev: true - estraverse@4.3.0: {} + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true - estraverse@5.3.0: {} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true - estree-walker@2.0.2: {} + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true - estree-walker@3.0.3: + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.8 + dev: true - esutils@2.0.3: {} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true - expand-tilde@2.0.2: + /event-lite@0.1.3: + resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==} + dev: false + + /expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} dependencies: homedir-polyfill: 1.0.3 + dev: true - expect-type@1.2.2: {} + /expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + dev: true - extend@3.0.2: {} + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true - external-editor@3.1.0: + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + dev: false - extract-files@9.0.0: {} + /extract-files@9.0.0: + resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==} + engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0} + dev: false - fast-csv@4.3.6: + /fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} dependencies: '@fast-csv/format': 4.3.5 '@fast-csv/parse': 4.3.6 + dev: false - fast-deep-equal@3.1.3: {} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true - fast-glob@3.3.3: + /fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -4919,102 +3115,178 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true - fast-levenshtein@2.0.6: {} + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true - fastq@1.19.1: + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} dependencies: reusify: 1.1.0 - fault@1.0.4: + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} dependencies: format: 0.2.2 + dev: true - fd-slicer@1.1.0: + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: pend: 1.2.0 + dev: true - fdir@6.4.6(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 + /fdir@6.4.6(picomatch@4.0.3): + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.3 + dev: true - figures@3.2.0: + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} dependencies: escape-string-regexp: 1.0.5 + dev: false - file-entry-cache@6.0.1: + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.2.0 + dev: true - file-type@3.9.0: {} + /file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + dev: true - file-type@5.2.0: {} + /file-type@5.2.0: + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} + dev: true - file-type@6.2.0: {} + /file-type@6.2.0: + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} + dev: true - fill-range@7.1.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - filter-obj@1.1.0: {} + /filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + dev: false - find-up@5.0.0: + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + dev: true - findup-sync@5.0.0: + /findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} + engines: {node: '>= 10.13.0'} dependencies: detect-file: 1.0.0 is-glob: 4.0.3 micromatch: 4.0.8 resolve-dir: 1.0.1 + dev: true - fix-dts-default-cjs-exports@1.0.1: + /fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} dependencies: magic-string: 0.30.17 mlly: 1.7.4 - rollup: 4.44.2 + rollup: 4.46.2 + dev: true - flat-cache@3.2.0: + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.3.3 keyv: 4.5.4 rimraf: 3.0.2 + dev: true - flatted@3.3.3: {} + /flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + dev: true - for-each@0.3.5: + /for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 + dev: true - foreground-child@3.3.1: + /foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + dev: true - form-data@3.0.3: + /form-data@3.0.4: + resolution: {integrity: sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 + dev: false - format@0.2.2: {} + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: true - fp-ts@2.16.10: {} + /fp-ts@2.16.10: + resolution: {integrity: sha512-vuROzbNVfCmUkZSUbnWSltR1sbheyQbTzug7LB/46fEa1c0EucLeBaCEUE0gF3ZGUGBt9lVUiziGOhhj6K1ORA==} + dev: false - fs-constants@1.0.0: {} + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: true - fs.realpath@1.0.0: {} + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true optional: true - function-bind@1.1.2: {} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: + /function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -5022,14 +3294,24 @@ snapshots: functions-have-names: 1.2.3 hasown: 2.0.2 is-callable: 1.2.7 + dev: true - functions-have-names@1.2.3: {} + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true - fuzzysearch@1.0.3: {} + /fuzzysearch@1.0.3: + resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} + dev: false - get-caller-file@2.0.5: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true - get-intrinsic@1.3.0: + /get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -5042,39 +3324,59 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-proto@1.0.1: + /get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@2.3.1: + /get-stream@2.3.1: + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} dependencies: object-assign: 4.1.1 pinkie-promise: 2.0.1 + dev: true - get-stream@5.2.0: + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} dependencies: pump: 3.0.3 + dev: false - get-symbol-description@1.1.0: + /get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 + dev: true - get-tsconfig@4.10.1: + /get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} dependencies: resolve-pkg-maps: 1.0.0 + dev: true - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true - glob@10.4.5: + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 @@ -5082,8 +3384,11 @@ snapshots: minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + dev: true - glob@7.2.3: + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -5091,8 +3396,11 @@ snapshots: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true - global-agent@3.0.0: + /global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} dependencies: boolean: 3.2.0 es6-error: 4.1.1 @@ -5101,30 +3409,43 @@ snapshots: semver: 7.7.2 serialize-error: 7.0.1 - global-modules@1.0.0: + /global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} dependencies: global-prefix: 1.0.2 is-windows: 1.0.2 resolve-dir: 1.0.1 + dev: true - global-prefix@1.0.2: + /global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} dependencies: expand-tilde: 2.0.2 homedir-polyfill: 1.0.3 ini: 1.3.8 is-windows: 1.0.2 which: 1.3.1 + dev: true - globals@13.24.0: + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true - globalthis@1.0.4: + /globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} dependencies: define-properties: 1.2.1 gopd: 1.2.0 - globby@11.1.0: + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -5132,12 +3453,19 @@ snapshots: ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 + dev: true - globrex@0.1.2: {} + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true - gopd@1.2.0: {} + /gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} - got@11.8.6: + /got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} dependencies: '@sindresorhus/is': 4.6.0 '@szmarczak/http-timer': 4.0.6 @@ -5150,24 +3478,39 @@ snapshots: lowercase-keys: 2.0.0 p-cancelable: 2.1.1 responselike: 2.0.1 + dev: false - graceful-fs@4.2.11: {} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true - graphemer@1.4.0: {} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true - graphql-request@5.2.0(graphql@16.11.0): + /graphql-request@5.2.0(graphql@16.11.0): + resolution: {integrity: sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ==} + peerDependencies: + graphql: 14 - 16 dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.11.0) cross-fetch: 3.2.0 extract-files: 9.0.0 - form-data: 3.0.3 + form-data: 3.0.4 graphql: 16.11.0 transitivePeerDependencies: - encoding + dev: false - graphql@16.11.0: {} + /graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: false - handlebars@4.7.8: + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -5175,79 +3518,147 @@ snapshots: wordwrap: 1.0.0 optionalDependencies: uglify-js: 3.19.3 + dev: false - has-bigints@1.1.0: {} + /has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + dev: true - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - has-property-descriptors@1.0.2: + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: es-define-property: 1.0.1 - has-proto@1.2.0: + /has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 + dev: true - has-symbols@1.1.0: {} + /has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.1.0 - has@1.0.4: {} + /has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + dev: true - hasown@2.0.2: + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - header-case@2.0.4: + /header-case@2.0.4: + resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: capital-case: 1.0.4 tslib: 2.8.1 + dev: false - homedir-polyfill@1.0.3: + /homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} dependencies: parse-passwd: 1.0.0 + dev: true - htmlparser2@7.2.0: + /htmlparser2@7.2.0: + resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 domutils: 2.8.0 entities: 3.0.1 + dev: true - http-cache-semantics@4.2.0: {} + /http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + dev: false - http2-wrapper@1.0.3: + /http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + dev: false - iconv-lite@0.4.24: + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: false - ieee754@1.2.1: {} + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: {} + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} - import-fresh@3.3.1: + /import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: true + + /import-in-the-middle@1.14.2: + resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + dev: false + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true - imurmurhash@0.1.4: {} + /inflection@1.13.4: + resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} + engines: {'0': node >= 0.4.0} + dev: false - inflight@1.0.6: + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 + dev: true - inherits@2.0.4: {} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true - ini@1.3.8: {} + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true - inquirer-autocomplete-prompt@1.3.0(inquirer@7.3.3): + /inquirer-autocomplete-prompt@1.3.0(inquirer@7.3.3): + resolution: {integrity: sha512-zvAc+A6SZdcN+earG5SsBu1RnQdtBS4o8wZ/OqJiCfL34cfOx+twVRq7wumYix6Rkdjn1N2nVCcO3wHqKqgdGg==} + engines: {node: '>=10'} + peerDependencies: + inquirer: ^5.0.0 || ^6.0.0 || ^7.0.0 dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -5255,8 +3666,11 @@ snapshots: inquirer: 7.3.3 run-async: 2.4.1 rxjs: 6.6.7 + dev: false - inquirer@7.3.3: + /inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -5271,212 +3685,390 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 + dev: false - internal-slot@1.1.0: + /int64-buffer@0.1.10: + resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} + dev: false + + /internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 + dev: true - io-ts-types@0.5.19(fp-ts@2.16.10)(io-ts@2.2.22(fp-ts@2.16.10))(monocle-ts@2.3.13(fp-ts@2.16.10))(newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10))): + /io-ts-types@0.5.19(fp-ts@2.16.10)(io-ts@2.2.22)(monocle-ts@2.3.13)(newtype-ts@0.3.5): + resolution: {integrity: sha512-kQOYYDZG5vKre+INIDZbLeDJe+oM+4zLpUkjXyTMyUfoCpjJNyi29ZLkuEAwcPufaYo3yu/BsemZtbdD+NtRfQ==} + peerDependencies: + fp-ts: ^2.0.0 + io-ts: ^2.0.0 + monocle-ts: ^2.0.0 + newtype-ts: ^0.3.2 dependencies: fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) monocle-ts: 2.3.13(fp-ts@2.16.10) - newtype-ts: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)) + newtype-ts: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13) + dev: false - io-ts@2.2.22(fp-ts@2.16.10): + /io-ts@2.2.22(fp-ts@2.16.10): + resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} + peerDependencies: + fp-ts: ^2.5.0 dependencies: fp-ts: 2.16.10 + dev: false - is-alphabetical@1.0.4: {} + /ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + dev: false + + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: true - is-alphanumerical@1.0.4: + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} dependencies: is-alphabetical: 1.0.4 is-decimal: 1.0.4 + dev: true - is-array-buffer@3.0.5: + /is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 + dev: true - is-arrayish@0.2.1: {} + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true - is-async-function@2.1.1: + /is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} dependencies: async-function: 1.0.0 call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + dev: true - is-bigint@1.1.0: + /is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} dependencies: has-bigints: 1.1.0 + dev: true - is-boolean-object@1.2.2: + /is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-buffer@2.0.5: {} + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: true - is-bun-module@2.0.0: + /is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} dependencies: semver: 7.7.2 + dev: true - is-callable@1.2.7: {} + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true - is-core-module@2.16.1: + /is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - is-data-view@1.0.2: + /is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-typed-array: 1.1.15 + dev: true - is-date-object@1.1.0: + /is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-decimal@1.0.4: {} + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: true - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - is-finalizationregistry@1.1.1: + /is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-fullwidth-code-point@3.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - is-generator-function@1.1.0: + /is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + dev: true - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - is-hexadecimal@1.0.4: {} + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: true - is-map@2.0.3: {} + /is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + dev: true - is-natural-number@4.0.1: {} + /is-natural-number@4.0.1: + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} + dev: true - is-negative-zero@2.0.3: {} + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + dev: true - is-number-object@1.1.1: + /is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-number@7.0.0: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: {} + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true - is-plain-obj@2.1.0: {} + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true - is-regex@1.2.1: + /is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 + dev: true - is-set@2.0.3: {} + /is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + dev: true - is-shared-array-buffer@1.0.4: + /is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-stream@1.1.0: {} + /is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + dev: true - is-string@1.1.1: + /is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + dev: true - is-symbol@1.1.1: + /is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 + dev: true - is-typed-array@1.1.15: + /is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} dependencies: which-typed-array: 1.1.19 + dev: true - is-weakmap@2.0.2: {} + /is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + dev: true - is-weakref@1.1.1: + /is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 + dev: true - is-weakset@2.0.4: + /is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 + dev: true + + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true - is-windows@1.0.2: {} + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isarray@1.0.0: {} + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true - isarray@2.0.5: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true - isexe@2.0.0: {} + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + dev: false - jackspeak@3.4.3: + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true - joycon@3.1.1: {} + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true - js-tokens@4.0.0: {} + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true - js-tokens@9.0.1: {} + /js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + dev: true - js-yaml@3.14.1: + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 + dev: true - js-yaml@4.1.0: + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@4.0.0: {} + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: true - jsesc@3.1.0: {} + /jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + dev: true - json-buffer@3.0.1: {} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: {} + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true - json-schema-traverse@0.4.1: {} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true - json-stable-stringify-without-jsonify@1.0.1: {} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true - json-stringify-safe@5.0.1: {} + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true dependencies: minimist: 1.2.8 + dev: true - json5@2.2.3: {} + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true - jsonparse@1.3.1: {} + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: false - jsonwebtoken@9.0.2: + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -5488,117 +4080,226 @@ snapshots: lodash.once: 4.1.1 ms: 2.1.3 semver: 7.7.2 + dev: false - jwa@1.4.2: + /jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + dev: false - jws@3.2.2: + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: jwa: 1.4.2 safe-buffer: 5.2.1 + dev: false - keyv@4.5.4: + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 - levn@0.4.1: + /koalas@1.0.2: + resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} + engines: {node: '>=0.10.0'} + dev: false + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true + + /lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + dev: true - lilconfig@3.1.3: {} + /limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + dev: false - lines-and-columns@1.2.4: {} + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true - load-tsconfig@0.2.5: {} + /load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true - locate-path@6.0.0: + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: p-locate: 5.0.0 + dev: true + + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + + /lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + dev: false + + /lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + dev: false - lodash-es@4.17.21: {} + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false - lodash.escaperegexp@4.1.2: {} + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false - lodash.groupby@4.6.0: {} + /lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + dev: false - lodash.includes@4.3.0: {} + /lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + dev: false - lodash.isboolean@3.0.3: {} + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false - lodash.isequal@4.5.0: {} + /lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + dev: false - lodash.isfunction@3.0.9: {} + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false - lodash.isinteger@4.0.4: {} + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false - lodash.isnil@4.0.0: {} + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false - lodash.isnumber@3.0.3: {} + /lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + dev: false - lodash.isplainobject@4.0.6: {} + /lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + dev: false - lodash.isstring@4.0.1: {} + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true - lodash.isundefined@3.0.1: {} + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false - lodash.merge@4.6.2: {} + /lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + deprecated: This package is deprecated. Use destructuring assignment syntax instead. + dev: false - lodash.once@4.1.1: {} + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash.sortby@4.7.0: {} + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: false - lodash.uniq@4.5.0: {} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lodash@4.17.21: {} + /long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + dev: false - longest-streak@2.0.4: {} + /longest-streak@2.0.4: + resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} + dev: true - loupe@3.1.4: {} + /loupe@3.2.0: + resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} + dev: true - lower-case@2.0.2: + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: tslib: 2.8.1 + dev: false + + /lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + dev: false - lowercase-keys@2.0.0: {} + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true - lru-cache@10.4.3: {} + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false - magic-string@0.30.17: + /magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + dev: true - make-dir@1.3.0: + /make-dir@1.3.0: + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} dependencies: pify: 3.0.0 + dev: true - markdown-table@2.0.0: + /markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} dependencies: repeat-string: 1.6.1 + dev: true - matcher@3.0.0: + /matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} dependencies: escape-string-regexp: 4.0.0 - math-intrinsics@1.1.0: {} + /math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} - mdast-util-find-and-replace@1.1.1: + /mdast-util-find-and-replace@1.1.1: + resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} dependencies: escape-string-regexp: 4.0.0 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 + dev: true - mdast-util-footnote@0.1.7: + /mdast-util-footnote@0.1.7: + resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==} dependencies: mdast-util-to-markdown: 0.6.5 micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - mdast-util-from-markdown@0.8.5: + /mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} dependencies: '@types/mdast': 3.0.15 mdast-util-to-string: 2.0.0 @@ -5607,33 +4308,45 @@ snapshots: unist-util-stringify-position: 2.0.3 transitivePeerDependencies: - supports-color + dev: true - mdast-util-frontmatter@0.2.0: + /mdast-util-frontmatter@0.2.0: + resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==} dependencies: micromark-extension-frontmatter: 0.2.2 + dev: true - mdast-util-gfm-autolink-literal@0.1.3: + /mdast-util-gfm-autolink-literal@0.1.3: + resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==} dependencies: ccount: 1.1.0 mdast-util-find-and-replace: 1.1.1 micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - mdast-util-gfm-strikethrough@0.2.3: + /mdast-util-gfm-strikethrough@0.2.3: + resolution: {integrity: sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==} dependencies: mdast-util-to-markdown: 0.6.5 + dev: true - mdast-util-gfm-table@0.1.6: + /mdast-util-gfm-table@0.1.6: + resolution: {integrity: sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==} dependencies: markdown-table: 2.0.0 mdast-util-to-markdown: 0.6.5 + dev: true - mdast-util-gfm-task-list-item@0.1.6: + /mdast-util-gfm-task-list-item@0.1.6: + resolution: {integrity: sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==} dependencies: mdast-util-to-markdown: 0.6.5 + dev: true - mdast-util-gfm@0.1.2: + /mdast-util-gfm@0.1.2: + resolution: {integrity: sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==} dependencies: mdast-util-gfm-autolink-literal: 0.1.3 mdast-util-gfm-strikethrough: 0.2.3 @@ -5642,8 +4355,10 @@ snapshots: mdast-util-to-markdown: 0.6.5 transitivePeerDependencies: - supports-color + dev: true - mdast-util-to-markdown@0.6.5: + /mdast-util-to-markdown@0.6.5: + resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} dependencies: '@types/unist': 2.0.11 longest-streak: 2.0.4 @@ -5651,48 +4366,73 @@ snapshots: parse-entities: 2.0.0 repeat-string: 1.6.1 zwitch: 1.0.5 + dev: true + + /mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + dev: true - mdast-util-to-string@2.0.0: {} + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} - merge2@1.4.1: {} + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false - micromark-extension-footnote@0.3.2: + /micromark-extension-footnote@0.3.2: + resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==} dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - micromark-extension-frontmatter@0.2.2: + /micromark-extension-frontmatter@0.2.2: + resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==} dependencies: fault: 1.0.4 + dev: true - micromark-extension-gfm-autolink-literal@0.5.7: + /micromark-extension-gfm-autolink-literal@0.5.7: + resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==} dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - micromark-extension-gfm-strikethrough@0.6.5: + /micromark-extension-gfm-strikethrough@0.6.5: + resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==} dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - micromark-extension-gfm-table@0.4.3: + /micromark-extension-gfm-table@0.4.3: + resolution: {integrity: sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==} dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - micromark-extension-gfm-tagfilter@0.3.0: {} + /micromark-extension-gfm-tagfilter@0.3.0: + resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==} + dev: true - micromark-extension-gfm-task-list-item@0.3.3: + /micromark-extension-gfm-task-list-item@0.3.3: + resolution: {integrity: sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==} dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color + dev: true - micromark-extension-gfm@0.3.3: + /micromark-extension-gfm@0.3.3: + resolution: {integrity: sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==} dependencies: micromark: 2.11.4 micromark-extension-gfm-autolink-literal: 0.5.7 @@ -5702,113 +4442,246 @@ snapshots: micromark-extension-gfm-task-list-item: 0.3.3 transitivePeerDependencies: - supports-color + dev: true - micromark@2.11.4: + /micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} dependencies: debug: 4.4.1 parse-entities: 2.0.0 transitivePeerDependencies: - supports-color + dev: true - micromatch@4.0.8: + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false - mime-types@2.1.35: + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: false - mimic-fn@2.1.0: {} + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: false - mimic-response@1.0.1: {} + /mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + dev: false - mimic-response@3.1.0: {} + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.12 + dev: true - minimatch@7.4.6: + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} dependencies: brace-expansion: 2.0.2 + dev: true - minimatch@9.0.5: + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.2 + dev: true - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: {} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true - mkdirp@1.0.4: {} + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: false - mlly@1.7.4: + /mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} dependencies: acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.1 + dev: true + + /module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + dev: false + + /moment-timezone@0.5.48: + resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} + dependencies: + moment: 2.30.1 + dev: false + + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: false - monocle-ts@2.3.13(fp-ts@2.16.10): + /monocle-ts@2.3.13(fp-ts@2.16.10): + resolution: {integrity: sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==} + peerDependencies: + fp-ts: ^2.5.0 dependencies: fp-ts: 2.16.10 + dev: false + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true - mri@1.2.0: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - ms@2.1.3: {} + /msgpack-lite@0.1.26: + resolution: {integrity: sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==} + hasBin: true + dependencies: + event-lite: 0.1.3 + ieee754: 1.2.1 + int64-buffer: 0.1.10 + isarray: 1.0.0 + dev: false - multimatch@5.0.0: + /multimatch@5.0.0: + resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} + engines: {node: '>=10'} dependencies: '@types/minimatch': 3.0.5 array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 minimatch: 3.1.2 + dev: true - mute-stream@0.0.8: {} + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: false - mz@2.7.0: + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: true - nanoid@3.3.11: {} + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true - napi-postinstall@0.3.0: {} + /napi-postinstall@0.3.2: + resolution: {integrity: sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + dev: true - natural-compare-lite@1.4.0: {} + /natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true - natural-compare@1.4.0: {} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true - neo-async@2.6.2: {} + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: false - newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)): + /newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13): + resolution: {integrity: sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==} + peerDependencies: + fp-ts: ^2.0.0 + monocle-ts: ^2.0.0 dependencies: fp-ts: 2.16.10 monocle-ts: 2.3.13(fp-ts@2.16.10) + dev: false - no-case@3.0.4: + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 tslib: 2.8.1 + dev: false - node-fetch@2.7.0: + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true dependencies: whatwg-url: 5.0.0 + dev: false + + /node-gyp-build@3.9.0: + resolution: {integrity: sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==} + hasBin: true + dev: false + + /node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + dev: false - normalize-url@6.1.0: {} + /normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + dev: false - object-assign@4.1.1: {} + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true - object-inspect@1.13.4: {} + /object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + dev: true - object-keys@1.1.1: {} + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} - object.assign@4.1.7: + /object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -5816,30 +4689,48 @@ snapshots: es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 + dev: true - object.entries@1.1.9: + /object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - object.values@1.2.1: + /object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - once@1.4.0: + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - onetime@5.1.2: + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: false + + /opentracing@0.14.7: + resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} + engines: {node: '>=0.10'} + dev: false - optionator@0.9.4: + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -5847,39 +4738,64 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 + dev: true - os-tmpdir@1.0.2: {} + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: false - own-keys@1.0.1: + /own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 + dev: true - p-cancelable@2.1.1: {} + /p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + dev: false - p-limit@3.1.0: + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 - p-locate@5.0.0: + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: p-limit: 3.1.0 + dev: true - package-json-from-dist@1.0.1: {} + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: true - package-manager-detector@1.3.0: {} + /package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + dev: true - param-case@3.0.4: + /param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 tslib: 2.8.1 + dev: false - parent-module@1.0.1: + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: true - parse-entities@2.0.0: + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} dependencies: character-entities: 1.2.4 character-entities-legacy: 1.1.4 @@ -5887,124 +4803,266 @@ snapshots: is-alphanumerical: 1.0.4 is-decimal: 1.0.4 is-hexadecimal: 1.0.4 + dev: true - parse-json@5.2.0: + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true - parse-passwd@1.0.0: {} + /parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + dev: true - pascal-case@3.1.2: + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 tslib: 2.8.1 + dev: false - path-case@3.0.4: + /path-case@3.0.4: + resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} dependencies: dot-case: 3.0.4 tslib: 2.8.1 + dev: false - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true - path-is-absolute@1.0.1: {} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true - path-key@3.1.1: {} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true - path-parse@1.0.7: {} + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true - path-scurry@1.11.1: + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 minipass: 7.1.2 + dev: true + + /path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + dev: false + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true - path-type@4.0.0: {} + /pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + dev: true - pathe@2.0.3: {} + /pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + dev: true - pathval@2.0.1: {} + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true - pend@1.2.0: {} + /pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + dev: false - picocolors@1.1.1: {} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true - picomatch@2.3.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - picomatch@4.0.2: {} + /picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + dev: true - pify@2.3.0: {} + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true - pify@3.0.0: {} + /pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true - pinkie-promise@2.0.1: + /pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} dependencies: pinkie: 2.0.4 + dev: true - pinkie@2.0.4: {} + /pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + dev: true - pirates@4.0.7: {} + /pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + dev: true - pkg-types@1.3.1: + /pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} dependencies: confbox: 0.1.8 mlly: 1.7.4 pathe: 2.0.3 + dev: true - please-upgrade-node@3.2.0: + /please-upgrade-node@3.2.0: + resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} dependencies: semver-compare: 1.0.0 + dev: true - pluralize@8.0.0: {} + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: false - possible-typed-array-names@1.1.0: {} + /possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + dev: true - postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.20.3): + /postcss-load-config@6.0.1(tsx@4.20.3): + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true dependencies: lilconfig: 3.1.3 - optionalDependencies: - postcss: 8.5.6 tsx: 4.20.3 + dev: true - postcss@8.5.6: + /postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + dev: true - prelude-ls@1.2.1: {} + /pprof-format@2.1.0: + resolution: {integrity: sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw==} + dev: false - prettier@2.8.8: {} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true - process-nextick-args@2.0.1: {} + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true - publint@0.3.12: + /protobufjs@7.5.3: + resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.19.121 + long: 5.3.2 + dev: false + + /publint@0.3.12: + resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} + engines: {node: '>=18'} + hasBin: true dependencies: '@publint/pack': 0.1.2 package-manager-detector: 1.3.0 picocolors: 1.1.1 sade: 1.8.1 + dev: true - pump@3.0.3: + /pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} dependencies: end-of-stream: 1.4.5 once: 1.4.0 + dev: false - punycode@2.3.1: {} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true - query-string@7.0.0: + /query-string@7.0.0: + resolution: {integrity: sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==} + engines: {node: '>=6'} dependencies: decode-uri-component: 0.2.2 filter-obj: 1.1.0 split-on-first: 1.1.0 strict-uri-encode: 2.0.0 + dev: false - queue-microtask@1.2.3: {} + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@5.1.1: {} + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: false - readable-stream@2.3.8: + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -6013,14 +5071,23 @@ snapshots: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 + dev: true - readdirp@3.6.0: + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true - readdirp@4.1.2: {} + /readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + dev: true - reflect.getprototypeof@1.0.10: + /reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -6030,8 +5097,11 @@ snapshots: get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 + dev: true - regexp.prototype.flags@1.5.4: + /regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -6039,73 +5109,129 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 + dev: true - remark-footnotes@3.0.0: + /remark-footnotes@3.0.0: + resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} dependencies: mdast-util-footnote: 0.1.7 micromark-extension-footnote: 0.3.2 transitivePeerDependencies: - supports-color + dev: true - remark-frontmatter@3.0.0: + /remark-frontmatter@3.0.0: + resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==} dependencies: mdast-util-frontmatter: 0.2.0 micromark-extension-frontmatter: 0.2.2 + dev: true - remark-gfm@1.0.0: + /remark-gfm@1.0.0: + resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==} dependencies: mdast-util-gfm: 0.1.2 micromark-extension-gfm: 0.3.3 transitivePeerDependencies: - supports-color + dev: true - remark-parse@9.0.0: + /remark-parse@9.0.0: + resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} dependencies: mdast-util-from-markdown: 0.8.5 transitivePeerDependencies: - supports-color + dev: true - repeat-string@1.6.1: {} + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true - require-directory@2.1.1: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true - require-package-name@2.0.1: {} + /require-package-name@2.0.1: + resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} + dev: true - resolve-alpn@1.2.1: {} + /resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + dev: false - resolve-dir@1.0.1: + /resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} dependencies: expand-tilde: 2.0.2 global-modules: 1.0.0 + dev: true - resolve-from@4.0.0: {} + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true - resolve-from@5.0.0: {} + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true - resolve-pkg-maps@1.0.0: {} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true - resolve@1.22.10: + /resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: true - responselike@2.0.1: + /responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} dependencies: lowercase-keys: 2.0.0 + dev: false - restore-cursor@3.1.0: + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: false + + /retry-as-promised@7.1.1: + resolution: {integrity: sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==} + dev: false - reusify@1.1.0: {} + /retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + dev: false + + /reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true dependencies: glob: 7.2.3 + dev: true - roarr@2.15.4: + /roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} dependencies: boolean: 3.2.0 detect-node: 2.1.0 @@ -6114,92 +5240,201 @@ snapshots: semver-compare: 1.0.0 sprintf-js: 1.1.3 - rollup@4.44.2: + /rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.2 - '@rollup/rollup-android-arm64': 4.44.2 - '@rollup/rollup-darwin-arm64': 4.44.2 - '@rollup/rollup-darwin-x64': 4.44.2 - '@rollup/rollup-freebsd-arm64': 4.44.2 - '@rollup/rollup-freebsd-x64': 4.44.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 - '@rollup/rollup-linux-arm-musleabihf': 4.44.2 - '@rollup/rollup-linux-arm64-gnu': 4.44.2 - '@rollup/rollup-linux-arm64-musl': 4.44.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 - '@rollup/rollup-linux-riscv64-gnu': 4.44.2 - '@rollup/rollup-linux-riscv64-musl': 4.44.2 - '@rollup/rollup-linux-s390x-gnu': 4.44.2 - '@rollup/rollup-linux-x64-gnu': 4.44.2 - '@rollup/rollup-linux-x64-musl': 4.44.2 - '@rollup/rollup-win32-arm64-msvc': 4.44.2 - '@rollup/rollup-win32-ia32-msvc': 4.44.2 - '@rollup/rollup-win32-x64-msvc': 4.44.2 + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 fsevents: 2.3.3 + dev: true - run-async@2.4.1: {} + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: false - run-parallel@1.2.0: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - rxjs@6.6.7: + /rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} dependencies: tslib: 1.14.1 - sade@1.8.1: + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} dependencies: mri: 1.2.0 + dev: true - safe-array-concat@1.1.3: + /safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 + dev: true - safe-buffer@5.1.2: {} + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true - safe-buffer@5.2.1: {} + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: + /safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 isarray: 2.0.5 + dev: true - safe-regex-test@1.1.0: + /safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 + dev: true - safer-buffer@2.1.2: {} + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false - seek-bzip@1.0.6: + /seek-bzip@1.0.6: + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} + hasBin: true dependencies: commander: 2.20.3 + dev: true - semver-compare@1.0.0: {} + /semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - semver@6.3.1: {} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true - semver@7.7.2: {} + /semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true - sentence-case@3.0.4: + /sentence-case@3.0.4: + resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case-first: 2.0.2 + dev: false + + /sequelize-mock@0.10.2: + resolution: {integrity: sha512-Vu95by/Bmhcx9PHKlZe+w7/7zw1AycV/SeevxQ5lDokAb50H7Kaf2SkjK5mqKxHWX6y/ICZ8JEfyMOg0nd1M2w==} + dependencies: + bluebird: 3.7.2 + inflection: 1.13.4 + lodash: 4.17.21 + dev: false + + /sequelize-pool@7.1.0: + resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} + engines: {node: '>= 10.0.0'} + dev: false + + /sequelize@6.37.3: + resolution: {integrity: sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==} + engines: {node: '>=10.0.0'} + peerDependencies: + ibm_db: '*' + mariadb: '*' + mysql2: '*' + oracledb: '*' + pg: '*' + pg-hstore: '*' + snowflake-sdk: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + ibm_db: + optional: true + mariadb: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-hstore: + optional: true + snowflake-sdk: + optional: true + sqlite3: + optional: true + tedious: + optional: true + dependencies: + '@types/debug': 4.1.12 + '@types/validator': 13.15.2 + debug: 4.4.1 + dottie: 2.0.6 + inflection: 1.13.4 + lodash: 4.17.21 + moment: 2.30.1 + moment-timezone: 0.5.48 + pg-connection-string: 2.9.1 + retry-as-promised: 7.1.1 + semver: 7.7.2 + sequelize-pool: 7.1.0 + toposort-class: 1.0.1 + uuid: 8.3.2 + validator: 13.15.15 + wkx: 0.5.0 + transitivePeerDependencies: + - supports-color + dev: false - serialize-error@7.0.1: + /serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} dependencies: type-fest: 0.13.1 - set-function-length@1.2.2: + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -6207,122 +5442,208 @@ snapshots: get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 + dev: true - set-function-name@2.0.2: + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + dev: true - set-proto@1.0.0: + /set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 + dev: true - shebang-command@2.0.0: + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 + dev: true - shebang-regex@3.0.0: {} + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true - shellcheck@3.1.0: + /shellcheck@3.1.0: + resolution: {integrity: sha512-C6IM1sziNIhCLyVszKZ/mKHQN2/CZ8qZ3sFt8mFOmL0ApoaXLTqyeEVfo+t4MlTVw+hS+kIqSaaGDDrrS0nKBA==} + engines: {node: '>=18.12.0'} + hasBin: true dependencies: decompress: 4.2.1 envalid: 8.1.0 global-agent: 3.0.0 + dev: true - side-channel-list@1.0.0: + /side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 + dev: true - side-channel-map@1.0.1: + /side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 + dev: true - side-channel-weakmap@1.0.2: + /side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 + dev: true - side-channel@1.1.0: + /side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + dev: true - siginfo@2.0.0: {} + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true - signal-exit@3.0.7: {} + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: false - signal-exit@4.1.0: {} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true - slash@3.0.0: {} + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true - snake-case@3.0.4: + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 tslib: 2.8.1 + dev: false + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: true - source-map-js@1.2.1: {} + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false - source-map@0.6.1: {} + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: false - source-map@0.8.0-beta.0: + /source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} dependencies: whatwg-url: 7.1.0 + dev: true - spdx-exceptions@2.5.0: {} + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + dev: true - spdx-expression-parse@3.0.1: + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 + spdx-license-ids: 3.0.22 + dev: true - spdx-license-ids@3.0.21: {} + /spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + dev: true - split-on-first@1.1.0: {} + /split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + dev: false - sprintf-js@1.0.3: {} + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true - sprintf-js@1.1.3: {} + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - stable-hash@0.0.5: {} + /stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + dev: true - stackback@0.0.2: {} + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true - std-env@3.9.0: {} + /std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + dev: true - stop-iteration-iterator@1.1.0: + /stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + dev: true - strict-uri-encode@2.0.0: {} + /strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + dev: false - string-width@4.2.3: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 + dev: true - string.prototype.trim@1.2.10: + /string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -6331,45 +5652,72 @@ snapshots: es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 + dev: true - string.prototype.trimend@1.0.9: + /string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - string.prototype.trimstart@1.0.8: + /string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.1.1 + dev: true - string_decoder@1.1.1: + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 + dev: true - strip-ansi@6.0.1: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.1.0 + dev: true - strip-bom@3.0.0: {} + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true - strip-dirs@2.1.0: + /strip-dirs@2.1.0: + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} dependencies: is-natural-number: 4.0.1 + dev: true - strip-json-comments@3.1.1: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true - strip-literal@3.0.0: + /strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} dependencies: js-tokens: 9.0.1 + dev: true - sucrase@3.35.0: + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true dependencies: '@jridgewell/gen-mapping': 0.3.12 commander: 4.1.1 @@ -6378,14 +5726,22 @@ snapshots: mz: 2.7.0 pirates: 4.0.7 ts-interface-checker: 0.1.13 + dev: true - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true - tar-stream@1.6.2: + /tar-stream@1.6.2: + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} dependencies: bl: 1.2.3 buffer-alloc: 1.2.0 @@ -6394,146 +5750,254 @@ snapshots: readable-stream: 2.3.8 to-buffer: 1.2.1 xtend: 4.0.2 + dev: true - text-table@0.2.0: {} + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true - thenify-all@1.6.0: + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: true - thenify@3.3.1: + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: true - through@2.3.8: {} + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.9.0: {} + /tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + dev: true - tinyexec@0.3.2: {} + /tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + dev: true - tinyglobby@0.2.14: + /tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + dev: true - tinypool@1.1.1: {} + /tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + dev: true - tinyrainbow@2.0.0: {} + /tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + dev: true - tinyspy@4.0.3: {} + /tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + dev: true - tmp@0.0.33: + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 + dev: false - to-buffer@1.2.1: + /to-buffer@1.2.1: + resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} + engines: {node: '>= 0.4'} dependencies: isarray: 2.0.5 safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 + dev: true - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - tr46@0.0.3: {} + /toposort-class@1.0.1: + resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} + dev: false + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false - tr46@1.0.1: + /tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: punycode: 2.3.1 + dev: true - traverse@0.6.11: + /traverse@0.6.11: + resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} + engines: {node: '>= 0.4'} dependencies: gopd: 1.2.0 typedarray.prototype.slice: 1.0.5 which-typed-array: 1.1.19 + dev: true - tree-kill@1.2.2: {} + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true - trough@1.0.5: {} + /trough@1.0.5: + resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + dev: true - ts-interface-checker@0.1.13: {} + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true - tsconfck@3.1.6(typescript@5.8.3): - optionalDependencies: - typescript: 5.8.3 + /tsconfck@3.1.6(typescript@5.0.4): + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.0.4 + dev: true - tsconfig-paths@3.15.0: + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + dev: true - tslib@1.14.1: {} + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.8.1: {} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.5.0(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3): + /tsup@8.5.0(tsx@4.20.3)(typescript@5.0.4): + resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true dependencies: - bundle-require: 5.1.0(esbuild@0.25.6) + bundle-require: 5.1.0(esbuild@0.25.8) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 debug: 4.4.1 - esbuild: 0.25.6 + esbuild: 0.25.8 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.20.3) + postcss-load-config: 6.0.1(tsx@4.20.3) resolve-from: 5.0.0 - rollup: 4.44.2 + rollup: 4.46.2 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.5.6 - typescript: 5.8.3 + typescript: 5.0.4 transitivePeerDependencies: - jiti - supports-color - tsx - yaml + dev: true - tsutils@3.21.0(typescript@5.8.3): + /tsutils@3.21.0(typescript@5.0.4): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.8.3 + typescript: 5.0.4 + dev: true - tsx@4.20.3: + /tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true dependencies: - esbuild: 0.25.6 + esbuild: 0.25.8 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 + dev: true - type-check@0.4.0: + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true - type-fest@0.13.1: {} + /type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} - type-fest@0.20.2: {} + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true - type-fest@0.21.3: {} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: false - typed-array-buffer@1.0.3: + /typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 + dev: true - typed-array-byte-length@1.0.3: + /typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 + dev: true - typed-array-byte-offset@1.0.4: + /typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -6542,8 +6006,11 @@ snapshots: has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 + dev: true - typed-array-length@1.0.7: + /typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 for-each: 0.3.5 @@ -6551,8 +6018,11 @@ snapshots: is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + dev: true - typedarray.prototype.slice@1.0.5: + /typedarray.prototype.slice@1.0.5: + resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -6562,35 +6032,59 @@ snapshots: math-intrinsics: 1.1.0 typed-array-buffer: 1.0.3 typed-array-byte-offset: 1.0.4 + dev: true - typescript@5.8.3: {} + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + dev: true - ufo@1.6.1: {} + /ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + dev: true - uglify-js@3.19.3: + /uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: false optional: true - unbox-primitive@1.1.0: + /unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + dev: true - unbzip2-stream@1.4.3: + /unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} dependencies: buffer: 5.7.1 through: 2.3.8 + dev: true - underscore@1.13.7: {} + /underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + dev: true - undici-types@5.26.5: {} + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.29.0: + /undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} dependencies: '@fastify/busboy': 2.1.1 + dev: false - unified@9.2.2: + /unified@9.2.2: + resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} dependencies: '@types/unist': 2.0.11 bail: 1.0.5 @@ -6599,77 +6093,114 @@ snapshots: is-plain-obj: 2.1.0 trough: 1.0.5 vfile: 4.2.1 + dev: true - unist-util-is@4.1.0: {} + /unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + dev: true - unist-util-stringify-position@2.0.3: + /unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} dependencies: '@types/unist': 2.0.11 + dev: true - unist-util-visit-parents@3.1.1: + /unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} dependencies: '@types/unist': 2.0.11 unist-util-is: 4.1.0 + dev: true - unrs-resolver@1.11.0: + /unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + requiresBuild: true dependencies: - napi-postinstall: 0.3.0 + napi-postinstall: 0.3.2 optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.0 - '@unrs/resolver-binding-android-arm64': 1.11.0 - '@unrs/resolver-binding-darwin-arm64': 1.11.0 - '@unrs/resolver-binding-darwin-x64': 1.11.0 - '@unrs/resolver-binding-freebsd-x64': 1.11.0 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.0 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.0 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.0 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.0 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.0 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.0 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.0 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.0 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.0 - '@unrs/resolver-binding-linux-x64-musl': 1.11.0 - '@unrs/resolver-binding-wasm32-wasi': 1.11.0 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.0 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.0 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.0 - - update-section@0.3.3: {} - - upper-case-first@2.0.2: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + dev: true + + /update-section@0.3.3: + resolution: {integrity: sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==} + dev: true + + /upper-case-first@2.0.2: + resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} dependencies: tslib: 2.8.1 + dev: false - upper-case@2.0.2: + /upper-case@2.0.2: + resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} dependencies: tslib: 2.8.1 + dev: false - uri-js@4.4.1: + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 + dev: true - util-deprecate@1.0.2: {} + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + + /validator@13.15.15: + resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + engines: {node: '>= 0.10'} + dev: false - vfile-message@2.0.4: + /vfile-message@2.0.4: + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} dependencies: '@types/unist': 2.0.11 unist-util-stringify-position: 2.0.3 + dev: true - vfile@4.2.1: + /vfile@4.2.1: + resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} dependencies: '@types/unist': 2.0.11 is-buffer: 2.0.5 unist-util-stringify-position: 2.0.3 vfile-message: 2.0.4 + dev: true - vite-node@3.2.4(@types/node@18.19.115)(tsx@4.20.3): + /vite-node@3.2.4(@types/node@18.19.121)(tsx@4.20.3): + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) + vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -6683,58 +6214,128 @@ snapshots: - terser - tsx - yaml + dev: true - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)): + /vite-tsconfig-paths@5.1.4(typescript@5.0.4): + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true dependencies: debug: 4.4.1 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.8.3) - optionalDependencies: - vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) + tsconfck: 3.1.6(typescript@5.0.4) transitivePeerDependencies: - supports-color - typescript + dev: true - vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3): + /vite@7.0.6(@types/node@18.19.121)(tsx@4.20.3): + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true dependencies: - esbuild: 0.25.6 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + '@types/node': 18.19.121 + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.44.2 + rollup: 4.46.2 tinyglobby: 0.2.14 + tsx: 4.20.3 optionalDependencies: - '@types/node': 18.19.115 fsevents: 2.3.3 - tsx: 4.20.3 + dev: true - vitest@3.2.4(@types/node@18.19.115)(tsx@4.20.3): + /vitest@3.2.4(@types/node@18.19.121)(tsx@4.20.3): + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true dependencies: '@types/chai': 5.2.2 + '@types/node': 18.19.121 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)) + '@vitest/mocker': 3.2.4(vite@7.0.6) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.0 + chai: 5.2.1 debug: 4.4.1 expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 - picomatch: 4.0.2 + picomatch: 4.0.3 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@18.19.115)(tsx@4.20.3) + vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@18.19.121)(tsx@4.20.3) why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 18.19.115 transitivePeerDependencies: - jiti - less @@ -6748,31 +6349,45 @@ snapshots: - terser - tsx - yaml + dev: true - webidl-conversions@3.0.1: {} + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false - webidl-conversions@4.0.2: {} + /webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true - whatwg-url@5.0.0: + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + dev: false - whatwg-url@7.1.0: + /whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 + dev: true - which-boxed-primitive@1.1.1: + /which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} dependencies: is-bigint: 1.1.0 is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 + dev: true - which-builtin-type@1.2.1: + /which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 function.prototype.name: 1.1.8 @@ -6787,15 +6402,21 @@ snapshots: which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.19 + dev: true - which-collection@1.0.2: + /which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.4 + dev: true - which-typed-array@1.1.19: + /which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -6804,49 +6425,96 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 + dev: true - which@1.3.1: + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true dependencies: isexe: 2.0.0 + dev: true - which@2.0.2: + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 + dev: true - why-is-node-running@2.3.0: + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true dependencies: siginfo: 2.0.0 stackback: 0.0.2 + dev: true + + /wkx@0.5.0: + resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} + dependencies: + '@types/node': 18.19.121 + dev: false - word-wrap@1.2.5: {} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true - wordwrap@1.0.0: {} + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: false - wrap-ansi@7.0.0: + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true - wrap-ansi@8.1.0: + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 + dev: true - wrappy@1.0.2: {} + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - xtend@4.0.2: {} + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true - y18n@5.0.8: {} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true - yaml@1.10.2: {} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true - yargs-parser@20.2.9: {} + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true - yargs-parser@21.1.1: {} + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: false - yargs@16.2.0: + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -6855,12 +6523,19 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 + dev: true - yauzl@2.10.0: + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + dev: true - yocto-queue@0.1.0: {} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} - zwitch@1.0.5: {} + /zwitch@1.0.5: + resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} + dev: true diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index ae2538e8..bdad578f 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -97,7 +97,19 @@ export const uploadPreferencesCommand = buildCommand({ kind: 'parsed', parse: numberParser, brief: 'The concurrency to use when uploading in parallel', - default: '10', + default: '1', + }, + allowedIdentifierNames: { + kind: 'parsed', + parse: (value: string) => value.split(',').map((s) => s.trim()), + brief: + 'Identifiers configured for the run. Comma-separated list of identifier names.', + }, + identifierColumns: { + kind: 'parsed', + parse: (value: string) => value.split(',').map((s) => s.trim()), + brief: + 'Columns in the CSV that should be used as identifiers. Comma-separated list of column names.', }, }, }, diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index c3c63ed5..4cb4e61a 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -26,6 +26,8 @@ export interface UploadPreferencesCommandFlags { attributes: string; receiptFilepath: string; concurrency: number; + allowedIdentifierNames: string[]; + identifierColumns: string[]; } export async function uploadPreferences( @@ -46,6 +48,8 @@ export async function uploadPreferences( isSilent, attributes, concurrency, + allowedIdentifierNames, + identifierColumns, }: UploadPreferencesCommandFlags, ): Promise { if (!!directory && !!file) { @@ -121,8 +125,15 @@ export async function uploadPreferences( await map( files, - async (filePath) => { + async (filePath, index) => { const fileName = basename(filePath).replace('.csv', ''); + const oldReceiptFilepath = + index > 0 + ? join( + receiptFileDir, + `${basename(files[0]).replace('.csv', '')}-receipts.json`, + ) + : undefined; await uploadPreferenceManagementPreferencesInteractive({ receiptFilepath: join(receiptFileDir, `${fileName}-receipts.json`), auth, @@ -137,6 +148,9 @@ export async function uploadPreferences( dryRun, attributes: splitCsvToList(attributes), forceTriggerWorkflows, + allowedIdentifierNames, + identifierColumns, + oldReceiptFilepath, }); }, { concurrency }, diff --git a/src/lib/graphql/fetchIdentifiers.ts b/src/lib/graphql/fetchIdentifiers.ts index 8a778fc6..ee9cef4a 100644 --- a/src/lib/graphql/fetchIdentifiers.ts +++ b/src/lib/graphql/fetchIdentifiers.ts @@ -42,6 +42,8 @@ export interface Identifier { }; /** Display order */ displayOrder: number; + /** does this identifier uniquely identify a consent record */ + isUniqueOnPreferenceStore: boolean; } const PAGE_SIZE = 20; diff --git a/src/lib/graphql/gqls/identifier.ts b/src/lib/graphql/gqls/identifier.ts index e28fa776..93c2079a 100644 --- a/src/lib/graphql/gqls/identifier.ts +++ b/src/lib/graphql/gqls/identifier.ts @@ -32,6 +32,7 @@ export const IDENTIFIERS = gql` defaultMessage } displayOrder + isUniqueOnPreferenceStore } } } diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index 0b07c507..514fc80a 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -72,12 +72,20 @@ export const FileMetadataState = t.intersection([ * their preferences are already in the store */ skippedUpdates: t.record(t.string, t.record(t.string, t.string)), + /** The column name that maps to the identifier */ + columnToIdentifier: t.record( + t.string, + t.type({ + /** The identifier name */ + name: t.string, + /** Is unique on preference store */ + isUniqueOnPreferenceStore: t.boolean, + }), + ), }), t.partial({ - /** Determine which column name in file maps to consent record identifier to upload on */ - identifierColumn: t.string, /** Determine which column name in file maps to the timestamp */ - timestampColum: t.string, + timestampColumn: t.string, }), ]); diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index 7c81c206..f1ed9a53 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -39,8 +39,8 @@ export async function parsePreferenceAndPurposeValuesFromCsv( // Determine the columns that could potentially be used for identifier const otherColumns = difference(columnNames, [ - ...(currentState.identifierColumn ? [currentState.identifierColumn] : []), - ...(currentState.timestampColum ? [currentState.timestampColum] : []), + ...Object.keys(currentState.columnToIdentifier), + ...(currentState.timestampColumn ? [currentState.timestampColumn] : []), ]); if (otherColumns.length === 0) { if (forceTriggerWorkflows) { diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 0c65ccf2..ceec16de 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -1,9 +1,13 @@ -import { uniq, groupBy, difference } from 'lodash-es'; +// groupBy +import { uniq, keyBy } from 'lodash-es'; import colors from 'colors'; import inquirer from 'inquirer'; import { FileMetadataState } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; +import { mapSeries } from '../bluebird-replace'; +import type { Identifier } from '../graphql'; +import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; /* eslint-disable no-param-reassign */ @@ -15,68 +19,124 @@ import { inquirerConfirmBoolean } from '../helpers'; * * @param preferences - List of preferences * @param currentState - The current file metadata state for parsing this list + * @param orgIdentifiers - The list of identifiers configured for the org + * @param allowedIdentifierNames - The list of identifier names that are allowed for this upload + * @param identifierColumns - The columns in the CSV that should be used as identifiers * @returns The updated file metadata state */ export async function parsePreferenceIdentifiersFromCsv( preferences: Record[], currentState: FileMetadataState, + orgIdentifiers: Identifier[], + allowedIdentifierNames: string[], + identifierColumns: string[], ): Promise<{ /** The updated state */ currentState: FileMetadataState; /** The updated preferences */ preferences: Record[]; }> { + const columnNames = uniq( + preferences.map((x) => Object.keys(x)).flat(), + ).filter((col) => identifierColumns.includes(col)); // Determine columns to map - const columnNames = uniq(preferences.map((x) => Object.keys(x)).flat()); + const orgIdentifiersByName = keyBy(orgIdentifiers, 'name'); + const filteredOrgIdentifiers = allowedIdentifierNames + .map((name) => orgIdentifiersByName[name]) + .filter(Boolean); + if (filteredOrgIdentifiers.length !== allowedIdentifierNames.length) { + const missingIdentifiers = allowedIdentifierNames.filter( + (name) => !orgIdentifiersByName[name], + ); + throw new Error( + `No identifier configuration found for "${missingIdentifiers.join( + '","', + )}"`, + ); + } + if (columnNames.length !== identifierColumns.length) { + const missingColumns = identifierColumns.filter( + (col) => !columnNames.includes(col), + ); + throw new Error( + `The following identifier columns are missing from the CSV: "${missingColumns.join( + '","', + )}"`, + ); + } - // Determine the columns that could potentially be used for identifier - const remainingColumnsForIdentifier = difference(columnNames, [ - ...(currentState.identifierColumn ? [currentState.identifierColumn] : []), - ...Object.keys(currentState.columnToPurposeName), - ]); + if ( + filteredOrgIdentifiers.filter( + (identifier) => identifier.isUniqueOnPreferenceStore, + ).length === 0 + ) { + throw new Error( + 'No unique identifier we provided as part of allowedIdentifierNames. Please ensure that at least one of the allowed ' + + 'identifiers is configured as unique on the preference store.', + ); + } - // Determine the identifier column to work off of - if (!currentState.identifierColumn) { + // Determine the columns that could potentially be used for identifiers + await mapSeries(identifierColumns, async (col) => { + // Map the column to an identifier + const identifierMapping = currentState.columnToIdentifier[col]; + if (identifierMapping) { + logger.info( + colors.magenta( + `Column "${col}" is associated with identifier "${identifierMapping.name}"`, + ), + ); + return; + } + // If the column is not mapped, ask the user to map it const { identifierName } = await inquirer.prompt<{ /** Identifier name */ identifierName: string; }>([ { name: 'identifierName', - message: - 'Choose the column that will be used as the identifier to upload consent preferences by', + message: `Choose the identifier name for column "${col}"`, type: 'list', - default: - remainingColumnsForIdentifier.find((col) => - col.toLowerCase().includes('email'), - ) || remainingColumnsForIdentifier[0], - choices: remainingColumnsForIdentifier, + // Default to the first allowed identifier name + default: allowedIdentifierNames.find((x) => x.startsWith(col)), + choices: allowedIdentifierNames, }, ]); - currentState.identifierColumn = identifierName; - } - logger.info( - colors.magenta( - `Using identifier column "${currentState.identifierColumn}"`, - ), - ); + currentState.columnToIdentifier[col] = { + name: identifierName, + isUniqueOnPreferenceStore: + orgIdentifiersByName[identifierName].isUniqueOnPreferenceStore, + }; + }); - // Validate that the identifier column is present for all rows and unique - const identifierColumnsMissing = preferences - .map((pref, ind) => (pref[currentState.identifierColumn!] ? null : [ind])) + const uniqueIdentifierColumns = Object.entries( + currentState.columnToIdentifier, + ) + .filter( + ([, identifierMapping]) => identifierMapping.isUniqueOnPreferenceStore, + ) + .map(([col]) => col); + + // Validate that the at least 1 unique identifier column is present + const uniqueIdentifierMissingIndexes = preferences + .map((pref, ind) => + uniqueIdentifierColumns.some((col) => !!pref[col]) ? null : [ind], + ) .filter((x): x is number[] => !!x) .flat(); - if (identifierColumnsMissing.length > 0) { - const msg = `The identifier column "${ - currentState.identifierColumn - }" is missing a value for the following rows: ${identifierColumnsMissing.join( + + if (uniqueIdentifierMissingIndexes.length > 0) { + const msg = ` + The following rows ${uniqueIdentifierMissingIndexes.join( ', ', - )}`; + )} do not have any unique identifier values for the columns "${uniqueIdentifierColumns.join( + '", "', + )}".`; logger.warn(colors.yellow(msg)); // Ask user if they would like to skip rows missing an identifier const skip = await inquirerConfirmBoolean({ - message: 'Would you like to skip rows missing an identifier?', + message: 'Would you like to skip rows missing unique identifiers?', }); if (!skip) { throw new Error(msg); @@ -85,54 +145,47 @@ export async function parsePreferenceIdentifiersFromCsv( // Filter out rows missing an identifier const previous = preferences.length; preferences = preferences.filter( - (pref) => pref[currentState.identifierColumn!], + (pref, index) => !uniqueIdentifierMissingIndexes.includes(index), ); logger.info( colors.yellow( - `Skipped ${previous - preferences.length} rows missing an identifier`, + `Skipped ${ + previous - preferences.length + } rows missing unique identifiers`, ), ); } logger.info( colors.magenta( - `The identifier column "${currentState.identifierColumn}" is present for all rows`, + `At least one unique identifier column is present for all ${preferences.length} rows.`, ), ); - // Validate that all identifiers are unique - const rowsByUserId = groupBy(preferences, currentState.identifierColumn); - const duplicateIdentifiers = Object.entries(rowsByUserId).filter( - ([, rows]) => rows.length > 1, - ); - if (duplicateIdentifiers.length > 0) { - const msg = `The identifier column "${ - currentState.identifierColumn - }" has duplicate values for the following rows: ${duplicateIdentifiers - .slice(0, 10) - .map(([userId, rows]) => `${userId} (${rows.length})`) - .join('\n')}`; - logger.warn(colors.yellow(msg)); - - // Ask user if they would like to take the most recent update - // for each duplicate identifier - const skip = await inquirerConfirmBoolean({ - message: 'Would you like to automatically take the latest update?', - }); - if (!skip) { - throw new Error(msg); - } - preferences = Object.entries(rowsByUserId) - .map(([, rows]) => { - const sorted = rows.sort( - (a, b) => - new Date(b[currentState.timestampColum!]).getTime() - - new Date(a[currentState.timestampColum!]).getTime(), - ); - return sorted[0]; - }) - .filter((x) => x); - } - return { currentState, preferences }; } /* eslint-enable no-param-reassign */ + +/** + * Helper function to get the identifiers payload from a row + * + * @param options - Options + * @param options.row - The current row from CSV file + * @param options.currentState - The current file metadata state + * @returns The updated preferences with identifiers payload + */ +export function getPreferenceIdentifiersFromRow({ + row, + columnToIdentifier, +}: { + /** The current row from CSV file */ + row: Record; + /** The current file metadata state */ + columnToIdentifier: FileMetadataState['columnToIdentifier']; +}): PreferenceStoreIdentifier[] { + return Object.entries(columnToIdentifier) + .filter(([col]) => !!row[col]) + .map(([col, identifierMapping]) => ({ + name: identifierMapping.name, + value: row[col], + })); +} diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 10b36831..728d590a 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -7,7 +7,7 @@ import { FileMetadataState, PreferenceState } from './codecs'; import { logger } from '../../logger'; import { readCsv } from '../requests'; import { getPreferencesForIdentifiers } from './getPreferencesForIdentifiers'; -import { PreferenceTopic } from '../graphql'; +import { PreferenceTopic, type Identifier } from '../graphql'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { parsePreferenceTimestampsFromCsv } from './parsePreferenceTimestampsFromCsv'; import { parsePreferenceIdentifiersFromCsv } from './parsePreferenceIdentifiersFromCsv'; @@ -32,6 +32,10 @@ export async function parsePreferenceManagementCsvWithCache( partitionKey, skipExistingRecordCheck, forceTriggerWorkflows, + orgIdentifiers, + allowedIdentifierNames, + oldReceiptFilepath, + identifierColumns, }: { /** File to parse */ file: string; @@ -45,11 +49,35 @@ export async function parsePreferenceManagementCsvWithCache( partitionKey: string; /** Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD */ skipExistingRecordCheck: boolean; - /** Wheather to force workflow triggers */ + /** Whether to force workflow triggers */ forceTriggerWorkflows: boolean; + /** Identifiers configured for the org */ + orgIdentifiers: Identifier[]; + /** allowed identifiers names */ + allowedIdentifierNames: string[]; + /** Old receipt file path to restore from */ + oldReceiptFilepath?: string; + /** Identifier columns on the CSV file */ + identifierColumns: string[]; }, cache: PersistedState, ): Promise { + // Restore the old file metadata if it exists + let oldFileMetadata: FileMetadataState | undefined; + if (oldReceiptFilepath) { + const oldPreferenceState = new PersistedState( + oldReceiptFilepath, + PreferenceState, + { + fileMetadata: {}, + failingUpdates: {}, + pendingUpdates: {}, + }, + ); + const oldGlobalMetadata = await oldPreferenceState.getValue('fileMetadata'); + const startFileKey = Object.keys(oldGlobalMetadata)[0]; + oldFileMetadata = oldGlobalMetadata[startFileKey]; + } // Start the timer const t0 = new Date().getTime(); @@ -62,10 +90,14 @@ export async function parsePreferenceManagementCsvWithCache( // start building the cache, can use previous cache as well let currentState: FileMetadataState = { - columnToPurposeName: {}, + columnToPurposeName: oldFileMetadata?.columnToPurposeName || {}, pendingSafeUpdates: {}, pendingConflictUpdates: {}, skippedUpdates: {}, + columnToIdentifier: oldFileMetadata?.columnToIdentifier || {}, + ...(oldFileMetadata?.timestampColumn && { + timestampColumn: oldFileMetadata.timestampColumn, + }), // Load in the last fetched time ...((fileMetadata[file] || {}) as Partial), lastFetchedAt: new Date().toISOString(), @@ -83,6 +115,9 @@ export async function parsePreferenceManagementCsvWithCache( const result = await parsePreferenceIdentifiersFromCsv( preferences, currentState, + orgIdentifiers, + allowedIdentifierNames, + identifierColumns, ); currentState = result.currentState; preferences = result.preferences; @@ -104,13 +139,24 @@ export async function parsePreferenceManagementCsvWithCache( await cache.setValue(fileMetadata, 'fileMetadata'); // Grab existing preference store records - const identifiers = preferences.map( - (pref) => pref[currentState.identifierColumn!], + const identifiers = preferences.flatMap((pref) => + Object.keys(currentState.columnToIdentifier) + .filter( + (col) => + pref[col] && + currentState.columnToIdentifier[col] && + currentState.columnToIdentifier[col].isUniqueOnPreferenceStore, + ) + .map((col) => ({ + name: currentState.columnToIdentifier[col].name, + value: pref[col], + })), ); + const existingConsentRecords = skipExistingRecordCheck ? [] : await getPreferencesForIdentifiers(sombra, { - identifiers: identifiers.map((x) => ({ value: x })), + identifiers, partitionKey, }); const consentRecordByIdentifier = keyBy(existingConsentRecords, 'userId'); @@ -122,8 +168,15 @@ export async function parsePreferenceManagementCsvWithCache( // Process each row preferences.forEach((pref) => { - // Grab unique Id for the user - const userId = pref[currentState.identifierColumn!]; + // Get the userIds that could be the primary key of the consent record + const possiblePrimaryKeys = Object.keys(currentState.columnToIdentifier) + .filter( + (col) => + pref[col] && + currentState.columnToIdentifier[col] && + currentState.columnToIdentifier[col].isUniqueOnPreferenceStore, + ) + .map((col) => pref[col]); // determine updates for user const pendingUpdates = getPreferenceUpdatesFromRow({ @@ -134,10 +187,17 @@ export async function parsePreferenceManagementCsvWithCache( }); // Grab current state of the update - const currentConsentRecord = consentRecordByIdentifier[userId]; + const currentConsentRecord = possiblePrimaryKeys + .map((primaryKey) => consentRecordByIdentifier[primaryKey]) + .find((record) => record); + + // If consent record is found use it, otherwise use the first unique identifier + const primaryKey = currentConsentRecord?.userId || possiblePrimaryKeys[0]; if (forceTriggerWorkflows && !currentConsentRecord) { throw new Error( - `No existing consent record found for user with id: ${userId}. + `No existing consent record found for user with ids: ${possiblePrimaryKeys.join( + ', ', + )}. When 'forceTriggerWorkflows' is set all the user identifiers should contain a consent record`, ); } @@ -153,7 +213,7 @@ export async function parsePreferenceManagementCsvWithCache( }) && !forceTriggerWorkflows ) { - currentState.skippedUpdates[userId] = pref; + currentState.skippedUpdates[primaryKey] = pref; return; } @@ -166,7 +226,7 @@ export async function parsePreferenceManagementCsvWithCache( preferenceTopics, }) ) { - currentState.pendingConflictUpdates[userId] = { + currentState.pendingConflictUpdates[primaryKey] = { row: pref, record: currentConsentRecord, }; @@ -174,7 +234,7 @@ export async function parsePreferenceManagementCsvWithCache( } // Add to pending updates - currentState.pendingSafeUpdates[userId] = pref; + currentState.pendingSafeUpdates[primaryKey] = pref; }); // Read in the file diff --git a/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts b/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts index 6df3ac05..4e166f1c 100644 --- a/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts @@ -29,12 +29,12 @@ export async function parsePreferenceTimestampsFromCsv( // Determine the columns that could potentially be used for timestamp const remainingColumnsForTimestamp = difference(columnNames, [ - ...(currentState.identifierColumn ? [currentState.identifierColumn] : []), + ...Object.keys(currentState.columnToIdentifier), ...Object.keys(currentState.columnToPurposeName), ]); // Determine the timestamp column to work off of - if (!currentState.timestampColum) { + if (!currentState.timestampColumn) { const { timestampName } = await inquirer.prompt<{ /** timestamp name */ timestampName: string; @@ -55,22 +55,22 @@ export async function parsePreferenceTimestampsFromCsv( choices: [...remainingColumnsForTimestamp, NONE_PREFERENCE_MAP], }, ]); - currentState.timestampColum = timestampName; + currentState.timestampColumn = timestampName; } logger.info( - colors.magenta(`Using timestamp column "${currentState.timestampColum}"`), + colors.magenta(`Using timestamp column "${currentState.timestampColumn}"`), ); // Validate that all rows have valid timestamp - if (currentState.timestampColum !== NONE_PREFERENCE_MAP) { + if (currentState.timestampColumn !== NONE_PREFERENCE_MAP) { const timestampColumnsMissing = preferences - .map((pref, ind) => (pref[currentState.timestampColum!] ? null : [ind])) + .map((pref, ind) => (pref[currentState.timestampColumn!] ? null : [ind])) .filter((x): x is number[] => !!x) .flat(); if (timestampColumnsMissing.length > 0) { throw new Error( `The timestamp column "${ - currentState.timestampColum + currentState.timestampColumn }" is missing a value for the following rows: ${timestampColumnsMissing.join( '\n', )}`, @@ -78,7 +78,7 @@ export async function parsePreferenceTimestampsFromCsv( } logger.info( colors.magenta( - `The timestamp column "${currentState.timestampColum}" is present for all row`, + `The timestamp column "${currentState.timestampColumn}" is present for all row`, ), ); } diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index ba9d8cc3..5a71d575 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -5,6 +5,7 @@ import { fetchAllPreferenceTopics, PreferenceTopic, Purpose, + fetchAllIdentifiers, } from '../graphql'; import colors from 'colors'; import { map } from '../bluebird-replace'; @@ -19,6 +20,7 @@ import { PreferenceUpdateItem } from '@transcend-io/privacy-types'; import { apply } from '@transcend-io/type-utils'; import { NONE_PREFERENCE_MAP } from './parsePreferenceTimestampsFromCsv'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; +import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; /** * Upload a set of consent preferences @@ -29,6 +31,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ auth, sombraAuth, receiptFilepath, + oldReceiptFilepath, file, partition, isSilent = true, @@ -39,6 +42,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ attributes = [], transcendUrl, forceTriggerWorkflows = false, + allowedIdentifierNames, + identifierColumns, }: { /** The Transcend API key */ auth: string; @@ -48,6 +53,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ partition: string; /** File where to store receipt and continue from where left off */ receiptFilepath: string; + /** Old receipt file path to restore from */ + oldReceiptFilepath?: string; /** The file to process */ file: string; /** API URL for Transcend backend */ @@ -69,6 +76,10 @@ export async function uploadPreferenceManagementPreferencesInteractive({ skipExistingRecordCheck?: boolean; /** Whether to force trigger workflows */ forceTriggerWorkflows?: boolean; + /** identifiers configured for the run */ + allowedIdentifierNames: string[]; + /** identifier columns on the CSV file */ + identifierColumns: string[]; }): Promise { // Parse out the extra attributes to apply to all requests uploaded const parsedAttributes = parseAttributesFromString(attributes); @@ -104,7 +115,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ // Create GraphQL client to connect to Transcend backend const client = buildTranscendGraphQLClient(transcendUrl, auth); - const [sombra, purposes, preferenceTopics] = await Promise.all([ + const [sombra, purposes, preferenceTopics, identifiers] = await Promise.all([ // Create sombra instance to communicate with createSombraGotInstance(transcendUrl, auth, sombraAuth), // get all purposes and topics @@ -114,6 +125,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ forceTriggerWorkflows ? Promise.resolve([] as PreferenceTopic[]) : fetchAllPreferenceTopics(client), + fetchAllIdentifiers(client), ]); // Process the file @@ -126,6 +138,10 @@ export async function uploadPreferenceManagementPreferencesInteractive({ partitionKey: partition, skipExistingRecordCheck, forceTriggerWorkflows, + orgIdentifiers: identifiers, + allowedIdentifierNames, + oldReceiptFilepath, + identifierColumns, }, preferenceState, ); @@ -166,9 +182,9 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }).forEach(([userId, update]) => { // Determine timestamp const timestamp = - metadata.timestampColum === NONE_PREFERENCE_MAP + metadata.timestampColumn === NONE_PREFERENCE_MAP ? new Date() - : new Date(update[metadata.timestampColum!]); + : new Date(update[metadata.timestampColumn!]); // Determine updates const updates = getPreferenceUpdatesFromRow({ @@ -177,8 +193,12 @@ export async function uploadPreferenceManagementPreferencesInteractive({ preferenceTopics, purposeSlugs: purposes.map((x) => x.trackingType), }); + const identifiers = getPreferenceIdentifiersFromRow({ + row: update, + columnToIdentifier: metadata.columnToIdentifier, + }); pendingUpdates[userId] = { - userId, + identifiers, partition, timestamp: timestamp.toISOString(), purposes: Object.entries(updates).map(([purpose, value]) => ({ From 6750f10bd1206e662d3418a332ad6fb283112c41 Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 7 Aug 2025 05:44:30 +0000 Subject: [PATCH 02/72] Add multi-identifier support for preference upload --- examples/pm-test/cli-upload-preferences-example.csv | 5 +++++ examples/pm-test/cli-upload-preferences-example2.csv | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 examples/pm-test/cli-upload-preferences-example.csv create mode 100644 examples/pm-test/cli-upload-preferences-example2.csv diff --git a/examples/pm-test/cli-upload-preferences-example.csv b/examples/pm-test/cli-upload-preferences-example.csv new file mode 100644 index 00000000..6c267757 --- /dev/null +++ b/examples/pm-test/cli-upload-preferences-example.csv @@ -0,0 +1,5 @@ +email_id,person_id,member_id,timestamp,Sales +test-acme1@gmail.com,p1,m1,2025-08-07T05:09:40.317Z,true +test-acme2@gmail.com,p2,m2,2025-08-07T17:49:21.192Z,true +test-acme3@gmail.com,p3,m3,2025-08-07T18:49:21.192Z,false +test-acme4@gmail.com,p4,m4,2025-08-07T17:49:21.192Z,false diff --git a/examples/pm-test/cli-upload-preferences-example2.csv b/examples/pm-test/cli-upload-preferences-example2.csv new file mode 100644 index 00000000..2a61f2a9 --- /dev/null +++ b/examples/pm-test/cli-upload-preferences-example2.csv @@ -0,0 +1,5 @@ +email_id,person_id,member_id,timestamp,Sales +test-acme5@gmail.com,p5,m5,2025-08-07T05:09:40.317Z,true +test-acme6@gmail.com,p6,m6,2025-08-07T17:49:21.192Z,true +test-acme7@gmail.com,p7,m7,2025-08-07T18:49:21.192Z,false +test-acme8@gmail.com,p8,m8,2025-08-07T17:49:21.192Z,false From c190974c099431776695bd10dfc485339b78af7b Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 7 Aug 2025 05:48:30 +0000 Subject: [PATCH 03/72] rever lock changes --- pnpm-lock.yaml | 7899 +++++++++++++++++++++++++----------------------- 1 file changed, 4112 insertions(+), 3787 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4239d69d..f5ac3fd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '9.0' settings: autoInstallPeers: true @@ -82,7 +82,7 @@ importers: version: 2.2.22(fp-ts@2.16.10) io-ts-types: specifier: ^0.5.16 - version: 0.5.19(fp-ts@2.16.10)(io-ts@2.2.22)(monocle-ts@2.3.13)(newtype-ts@0.3.5) + version: 0.5.19(fp-ts@2.16.10)(io-ts@2.2.22(fp-ts@2.16.10))(monocle-ts@2.3.13(fp-ts@2.16.10))(newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10))) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -97,7 +97,7 @@ importers: version: 2.3.13(fp-ts@2.16.10) newtype-ts: specifier: ^0.3.5 - version: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13) + version: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)) query-string: specifier: '=7.0.0' version: 7.0.0 @@ -113,7 +113,7 @@ importers: devDependencies: '@types/JSONStream': specifier: npm:@types/jsonstream@^0.8.33 - version: /@types/jsonstream@0.8.33 + version: '@types/jsonstream@0.8.33' '@types/cli-progress': specifier: ^3.11.0 version: 3.11.6 @@ -146,7 +146,7 @@ importers: version: 4.17.12 '@types/node': specifier: ^18.15.11 - version: 18.19.121 + version: 18.19.115 '@types/semver': specifier: ^7 version: 7.7.0 @@ -155,10 +155,10 @@ importers: version: 21.0.3 '@typescript-eslint/eslint-plugin': specifier: ^5.58.0 - version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.0.4) + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) '@typescript-eslint/parser': specifier: ^5.58.0 - version: 5.62.0(eslint@8.57.1)(typescript@5.0.4) + version: 5.62.0(eslint@8.57.1)(typescript@5.8.3) depcheck: specifier: ^1.4.3 version: 1.4.7 @@ -179,13 +179,13 @@ importers: version: 3.2.0(eslint@8.57.1) eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + version: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsdoc: specifier: ^41.1.1 version: 41.1.2(eslint@8.57.1) fdir: specifier: ^6.4.6 - version: 6.4.6(picomatch@4.0.3) + version: 6.4.6(picomatch@4.0.2) prettier: specifier: ^2.8.7 version: 2.8.8 @@ -197,872 +197,3510 @@ importers: version: 3.1.0 tsup: specifier: ^8.5.0 - version: 8.5.0(tsx@4.20.3)(typescript@5.0.4) + version: 8.5.0(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3) tsx: specifier: ^4.20.3 version: 4.20.3 typescript: specifier: ^5.0.4 - version: 5.0.4 + version: 5.8.3 vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.0.4) + version: 5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@18.19.121)(tsx@4.20.3) - - examples/code-scanning/test-gradle/test-nested-package-json: - dependencies: - dd-trace: - specifier: 2.45.1 - version: 2.45.1 - fast-csv: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - typescript: - specifier: ^5.0.4 - version: 5.0.4 - - examples/code-scanning/test-package-json: - dependencies: - dd-trace: - specifier: 2.45.1 - version: 2.45.1 - fast-csv: - specifier: ^4.3.6 - version: 4.3.6 - sequelize: - specifier: ^6.37.3 - version: 6.37.3 - sequelize-mock: - specifier: ^0.10.2 - version: 0.10.2 - devDependencies: - '@types/sequelize': - specifier: ^4.28.20 - version: 4.28.20 - typescript: - specifier: ^5.0.4 - version: 5.0.4 + version: 3.2.4(@types/node@18.19.115)(tsx@4.20.3) packages: - /@babel/code-frame@7.27.1: + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - dev: true - /@babel/generator@7.28.0: + '@babel/generator@7.28.0': resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - jsesc: 3.1.0 - dev: true - /@babel/helper-globals@7.28.0: + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-string-parser@7.27.1: + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-identifier@7.27.1: + '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - dev: true - /@babel/parser@7.28.0: + '@babel/parser@7.28.0': resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} engines: {node: '>=6.0.0'} hasBin: true - dependencies: - '@babel/types': 7.28.2 - dev: true - /@babel/template@7.27.2: + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - dev: true - /@babel/traverse@7.28.0: + '@babel/traverse@7.28.0': resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/types@7.28.2: - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - dev: true - - /@datadog/native-appsec@3.2.0: - resolution: {integrity: sha512-biAa7EFfuavjSWgSQaCit9CqGzr6Af5nhzfNNGJ38Y/Y387hDvLivAR374kK1z6XoxGZEOa+XPbVogmV/2Bcjw==} - engines: {node: '>=12'} - requiresBuild: true - dependencies: - node-gyp-build: 3.9.0 - dev: false - - /@datadog/native-iast-rewriter@2.0.1: - resolution: {integrity: sha512-Mm+FG3XxEbPrAfJQPOMHts7iZZXRvg9gnGeeFRGkyirmRcQcOpZO4wFe/8K61DUVa5pXpgAJQ2ZkBGYF1O9STg==} - engines: {node: '>= 10'} - dependencies: - node-gyp-build: 4.8.4 - dev: false - - /@datadog/native-iast-taint-tracking@1.5.0: - resolution: {integrity: sha512-SOWIk1M6PZH0osNB191Voz2rKBPoF5hISWVSK9GiJPrD40+xjib1Z/bFDV7EkDn3kjOyordSBdNPG5zOqZJdyg==} - dependencies: - node-gyp-build: 3.9.0 - dev: false - - /@datadog/native-metrics@1.6.0: - resolution: {integrity: sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==} - engines: {node: '>=12'} - requiresBuild: true - dependencies: - node-gyp-build: 3.9.0 - dev: false - - /@datadog/pprof@3.1.0: - resolution: {integrity: sha512-Bg8O8yrHeL2KKHXhLoAAT33ZfzLnZ6rWfOjy8PkcNhUJy3UwNVLbUoApf+99EyLjqpzpk/kZXrIAMBzMMB8ilg==} - engines: {node: '>=12'} - requiresBuild: true - dependencies: - delay: 5.0.0 - node-gyp-build: 3.9.0 - p-limit: 3.1.0 - pprof-format: 2.1.0 - source-map: 0.7.4 - dev: false - /@datadog/sketches-js@2.1.1: - resolution: {integrity: sha512-d5RjycE+MObE/hU+8OM5Zp4VjTwiPLRa8299fj7muOmR16fb942z8byoMbCErnGh0lBevvgkGrLclQDvINbIyg==} - dev: false + '@emnapi/core@1.4.4': + resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==} - /@emnapi/core@1.4.5: - resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} - requiresBuild: true - dependencies: - '@emnapi/wasi-threads': 1.0.4 - tslib: 2.8.1 - dev: true - optional: true - - /@emnapi/runtime@1.4.5: - resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} - requiresBuild: true - dependencies: - tslib: 2.8.1 - dev: true - optional: true + '@emnapi/runtime@1.4.4': + resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==} - /@emnapi/wasi-threads@1.0.4: - resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} - requiresBuild: true - dependencies: - tslib: 2.8.1 - dev: true - optional: true + '@emnapi/wasi-threads@1.0.3': + resolution: {integrity: sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==} - /@es-joy/jsdoccomment@0.37.1: + '@es-joy/jsdoccomment@0.37.1': resolution: {integrity: sha512-5vxWJ1gEkEF0yRd0O+uK6dHJf7adrxwQSX8PuRiPfFSAbNLnY0ZJfXaZucoz14Jj2N11xn2DnlEPwWRpYpvRjg==} engines: {node: ^14 || ^16 || ^17 || ^18 || ^19 || ^20} - dependencies: - comment-parser: 1.3.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.0.0 - dev: true - /@esbuild/aix-ppc64@0.25.8: - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + '@esbuild/aix-ppc64@0.25.6': + resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.25.8: - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + '@esbuild/android-arm64@0.25.6': + resolution: {integrity: sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.25.8: - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + '@esbuild/android-arm@0.25.6': + resolution: {integrity: sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==} engines: {node: '>=18'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.25.8: - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + '@esbuild/android-x64@0.25.6': + resolution: {integrity: sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==} engines: {node: '>=18'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.25.8: - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + '@esbuild/darwin-arm64@0.25.6': + resolution: {integrity: sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.25.8: - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + '@esbuild/darwin-x64@0.25.6': + resolution: {integrity: sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.25.8: - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + '@esbuild/freebsd-arm64@0.25.6': + resolution: {integrity: sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.25.8: - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + '@esbuild/freebsd-x64@0.25.6': + resolution: {integrity: sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.25.8: - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + '@esbuild/linux-arm64@0.25.6': + resolution: {integrity: sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.25.8: - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + '@esbuild/linux-arm@0.25.6': + resolution: {integrity: sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.25.8: - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + '@esbuild/linux-ia32@0.25.6': + resolution: {integrity: sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.25.8: - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + '@esbuild/linux-loong64@0.25.6': + resolution: {integrity: sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.25.8: - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + '@esbuild/linux-mips64el@0.25.6': + resolution: {integrity: sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.25.8: - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + '@esbuild/linux-ppc64@0.25.6': + resolution: {integrity: sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.25.8: - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + '@esbuild/linux-riscv64@0.25.6': + resolution: {integrity: sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.25.8: - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + '@esbuild/linux-s390x@0.25.6': + resolution: {integrity: sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.25.8: - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + '@esbuild/linux-x64@0.25.6': + resolution: {integrity: sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==} engines: {node: '>=18'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-arm64@0.25.8: - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + '@esbuild/netbsd-arm64@0.25.6': + resolution: {integrity: sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.25.8: - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + '@esbuild/netbsd-x64@0.25.6': + resolution: {integrity: sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-arm64@0.25.8: - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + '@esbuild/openbsd-arm64@0.25.6': + resolution: {integrity: sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.25.8: - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + '@esbuild/openbsd-x64@0.25.6': + resolution: {integrity: sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openharmony-arm64@0.25.8: - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + '@esbuild/openharmony-arm64@0.25.6': + resolution: {integrity: sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.25.8: - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + '@esbuild/sunos-x64@0.25.6': + resolution: {integrity: sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.25.8: - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + '@esbuild/win32-arm64@0.25.6': + resolution: {integrity: sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.25.8: - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + '@esbuild/win32-ia32@0.25.6': + resolution: {integrity: sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.25.8: - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + '@esbuild/win32-x64@0.25.6': + resolution: {integrity: sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@eslint-community/eslint-utils@4.7.0(eslint@8.57.1): + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.12.1: + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - /@eslint/eslintrc@2.1.4: + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - /@eslint/js@8.57.1: + '@eslint/js@8.57.1': resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@fast-csv/format@4.3.5: + '@fast-csv/format@4.3.5': resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.isboolean: 3.0.3 - lodash.isequal: 4.5.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - dev: false - /@fast-csv/parse@4.3.6: + '@fast-csv/parse@4.3.6': resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} - dependencies: - '@types/node': 14.18.63 - lodash.escaperegexp: 4.1.2 - lodash.groupby: 4.6.0 - lodash.isfunction: 3.0.9 - lodash.isnil: 4.0.0 - lodash.isundefined: 3.0.1 - lodash.uniq: 4.5.0 - dev: false - /@fastify/busboy@2.1.1: + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - dev: false - /@graphql-typed-document-node/core@3.2.0(graphql@16.11.0): + '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - graphql: 16.11.0 - dev: false - /@humanwhocodes/config-array@0.13.0: + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.1 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - dev: true - /@humanwhocodes/object-schema@2.0.3: + '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - dev: true - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true - /@jridgewell/gen-mapping@0.3.12: + '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@publint/pack@0.1.2': + resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} + engines: {node: '>=18'} + + '@rollup/rollup-android-arm-eabi@4.44.2': + resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.44.2': + resolution: {integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.44.2': + resolution: {integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.44.2': + resolution: {integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.44.2': + resolution: {integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.44.2': + resolution: {integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': + resolution: {integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.44.2': + resolution: {integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.44.2': + resolution: {integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.44.2': + resolution: {integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': + resolution: {integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': + resolution: {integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.44.2': + resolution: {integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.44.2': + resolution: {integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.2': + resolution: {integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.44.2': + resolution: {integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.44.2': + resolution: {integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.44.2': + resolution: {integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.44.2': + resolution: {integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.44.2': + resolution: {integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@stricli/auto-complete@1.2.0': + resolution: {integrity: sha512-r9/msiloVmTF95mdhe04Uzqei1B0ZofhYRLeiPqpJ1W1RMCC8p9iW7kqBZEbALl2aRL5ZK9OEW3Q1cIejH7KEQ==} + hasBin: true + + '@stricli/core@1.2.0': + resolution: {integrity: sha512-5b+npntDY0TAB7wAw0daGlh3/R2sf0TDLyrB1By2jCNH+C+lmcSqMtJXOMLVtEGSkIOvqAgIWpLMSs1PXqzt3w==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@textlint/ast-node-types@12.6.1': + resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} + + '@textlint/markdown-to-ast@12.6.1': + resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} + + '@transcend-io/airgap.js-types@12.12.2': + resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} + + '@transcend-io/handlebars-utils@1.1.0': + resolution: {integrity: sha512-mDbKm9JObd9mioNlEYGa2zfTBqli1KAAM+iRHSqi6s2l7MAYENxjlfvXN5uC97T9/90vfWlxNbHjLpUuQVURaA==} + + '@transcend-io/internationalization@1.7.4': + resolution: {integrity: sha512-kg6c2Bjr823hBWrx62074ZlyBcW1FvK1dj77+lN7E70EyYxj6o5PordsOzxbLmTV1iZ0GLKGu+QiNl4xVOWfLA==} + + '@transcend-io/persisted-state@1.0.4': + resolution: {integrity: sha512-jEDN64pr5Q1fniHMERa2+DKhuHbwdB1zzGdHoWgq4EIEx/ZJQxraiNfYoCpf5hpuiKxtXcIN41SC2/MI6MraoQ==} + + '@transcend-io/privacy-types@4.128.0': + resolution: {integrity: sha512-iOFVTpdM+ZFX+YxmqtK5mDJNYAlwVa7JhPg4Yg0pUSFcck7LvZlxFVNwpHVoGGdguy6Ejsh8hfPYKHhBOgBqMQ==} + + '@transcend-io/secret-value@1.2.1': + resolution: {integrity: sha512-pb2thX9znGJ9GOm6A2kgIzwEchB3E6Zn1KTLIBJx+T+K7XkqRwQJsQL0vKGiJVmZZ0iB3bLSAABHoWwNAZcFxA==} + + '@transcend-io/type-utils@1.8.4': + resolution: {integrity: sha512-J1/T+q2Bs5znAyxQpQjD0wzN+h7Wi1xRsqa99uAu3G8VcKb5nRLnaUyY7Ut8N0YU1FHAxNhyXz9MVakYUigWuw==} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/cli-progress@3.11.6': + resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} + + '@types/colors@1.2.4': + resolution: {integrity: sha512-oSQxEVIDcYisAzWLa+wr50GSIPu8ml4PsKNJzgrDX3SmEHVBBqbaUurqsUceFauNlCRxNtENKkQm3yOe3m3nfg==} + deprecated: This is a stub types definition. colors provides its own type definitions, so you do not need this installed. + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fuzzysearch@1.0.2': + resolution: {integrity: sha512-G2M0M7acg75BzTtUv1qmPFMe7nTwX1K2AEKNeBO8zW0o+H2oTmyir7IJ2v0UW8pZkY4nHgHALvgpWQpB9WXPpg==} + + '@types/global-agent@2.1.3': + resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/inquirer-autocomplete-prompt@3.0.3': + resolution: {integrity: sha512-OQCW09mEECgvhcppbQRgZSmWskWv58l+WwyUvWB1oxTu3CZj8keYSDZR9U8owUzJ5Zeux5kacN9iVPJLXcoLXg==} + + '@types/inquirer@7.3.3': + resolution: {integrity: sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/jsonstream@0.8.33': + resolution: {integrity: sha512-yhg1SNOgJ8y2nOkvAQ1zZ1Z2xibxgFs7984+EeBPuWgo/TbuYo79+rj2wUVch3KF4GhhcwAi/AlJcehmLCXb3g==} + + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + + '@types/node@18.19.115': + resolution: {integrity: sha512-kNrFiTgG4a9JAn1LMQeLOv3MvXIPokzXziohMrMsvpYgLpdEt/mMiVYc4sGKtDfyxM5gIDF4VgrPRyCw4fHOYg==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/semver@7.7.0': + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + + '@types/through@0.0.33': + resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@typescript-eslint/eslint-plugin@5.62.0': + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@5.62.0': + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/type-utils@5.62.0': + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.11.0': + resolution: {integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.0': + resolution: {integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.0': + resolution: {integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.0': + resolution: {integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.0': + resolution: {integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': + resolution: {integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': + resolution: {integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': + resolution: {integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': + resolution: {integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': + resolution: {integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': + resolution: {integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': + resolution: {integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': + resolution: {integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': + resolution: {integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.0': + resolution: {integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.0': + resolution: {integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': + resolution: {integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': + resolution: {integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': + resolution: {integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag==} + cpu: [x64] + os: [win32] + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + '@vue/compiler-core@3.5.17': + resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==} + + '@vue/compiler-dom@3.5.17': + resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==} + + '@vue/compiler-sfc@3.5.17': + resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==} + + '@vue/compiler-ssr@3.5.17': + resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==} + + '@vue/shared@3.5.17': + resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + anchor-markdown-header@0.6.0: + resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-differ@3.0.0: + resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} + engines: {node: '>=8'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + bail@1.0.5: + resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@1.2.3: + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + capital-case@1.0.4: + resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + + ccount@1.1.0: + resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + change-case@4.1.2: + resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} + + character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + + character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + + character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-progress@3.12.0: + resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} + engines: {node: '>=4'} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-parser@1.3.1: + resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} + engines: {node: '>= 12.0.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confusing-browser-globals@1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + constant-case@3.0.4: + resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csv-parse@5.6.0: + resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + decompress-tar@4.1.1: + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} + + decompress-tarbz2@4.1.1: + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} + + decompress-targz@4.1.1: + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} + + decompress-unzip@4.0.1: + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} + + decompress@4.2.1: + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depcheck@1.4.7: + resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} + engines: {node: '>=10'} + hasBin: true + + deps-regex@0.2.0: + resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} + + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctoc@2.2.1: + resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==} + hasBin: true + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + emoji-regex@10.1.0: + resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + envalid@8.1.0: + resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} + engines: {node: '>=18'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild@0.25.6: + resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-airbnb-base@15.0.0: + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-eslint-comments@3.2.0: + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + + eslint-plugin-import@2.27.5: + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsdoc@41.1.2: + resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} + engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + extract-files@9.0.0: + resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==} + engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0} + + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@3.9.0: + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} + + file-type@5.2.0: + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} + + file-type@6.2.0: + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} + engines: {node: '>= 10.13.0'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@3.0.3: + resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==} + engines: {node: '>= 6'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + fp-ts@2.16.10: + resolution: {integrity: sha512-vuROzbNVfCmUkZSUbnWSltR1sbheyQbTzug7LB/46fEa1c0EucLeBaCEUE0gF3ZGUGBt9lVUiziGOhhj6K1ORA==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + fuzzysearch@1.0.3: + resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@2.3.1: + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql-request@5.2.0: + resolution: {integrity: sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ==} + peerDependencies: + graphql: 14 - 16 + + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + header-case@2.0.4: + resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + htmlparser2@7.2.0: + resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + inquirer-autocomplete-prompt@1.3.0: + resolution: {integrity: sha512-zvAc+A6SZdcN+earG5SsBu1RnQdtBS4o8wZ/OqJiCfL34cfOx+twVRq7wumYix6Rkdjn1N2nVCcO3wHqKqgdGg==} + engines: {node: '>=10'} + peerDependencies: + inquirer: ^5.0.0 || ^6.0.0 || ^7.0.0 + + inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + io-ts-types@0.5.19: + resolution: {integrity: sha512-kQOYYDZG5vKre+INIDZbLeDJe+oM+4zLpUkjXyTMyUfoCpjJNyi29ZLkuEAwcPufaYo3yu/BsemZtbdD+NtRfQ==} + peerDependencies: + fp-ts: ^2.0.0 + io-ts: ^2.0.0 + monocle-ts: ^2.0.0 + newtype-ts: ^0.3.2 + + io-ts@2.2.22: + resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} + peerDependencies: + fp-ts: ^2.5.0 + + is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + + is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-natural-number@4.0.1: + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@2.0.4: + resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} + + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + make-dir@1.3.0: + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} + + markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-find-and-replace@1.1.1: + resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} + + mdast-util-footnote@0.1.7: + resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==} + + mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + + mdast-util-frontmatter@0.2.0: + resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==} + + mdast-util-gfm-autolink-literal@0.1.3: + resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==} + + mdast-util-gfm-strikethrough@0.2.3: + resolution: {integrity: sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==} + + mdast-util-gfm-table@0.1.6: + resolution: {integrity: sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==} + + mdast-util-gfm-task-list-item@0.1.6: + resolution: {integrity: sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==} + + mdast-util-gfm@0.1.2: + resolution: {integrity: sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==} + + mdast-util-to-markdown@0.6.5: + resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} + + mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-extension-footnote@0.3.2: + resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==} + + micromark-extension-frontmatter@0.2.2: + resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==} + + micromark-extension-gfm-autolink-literal@0.5.7: + resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==} + + micromark-extension-gfm-strikethrough@0.6.5: + resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==} + + micromark-extension-gfm-table@0.4.3: + resolution: {integrity: sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==} + + micromark-extension-gfm-tagfilter@0.3.0: + resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==} + + micromark-extension-gfm-task-list-item@0.3.3: + resolution: {integrity: sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==} + + micromark-extension-gfm@0.3.3: + resolution: {integrity: sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==} + + micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + monocle-ts@2.3.13: + resolution: {integrity: sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==} + peerDependencies: + fp-ts: ^2.5.0 + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multimatch@5.0.0: + resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} + engines: {node: '>=10'} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.0: + resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + newtype-ts@0.3.5: + resolution: {integrity: sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==} + peerDependencies: + fp-ts: ^2.0.0 + monocle-ts: ^2.0.0 + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + + path-case@3.0.4: + resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + please-upgrade-node@3.2.0: + resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + publint@0.3.12: + resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} + engines: {node: '>=18'} + hasBin: true + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + query-string@7.0.0: + resolution: {integrity: sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remark-footnotes@3.0.0: + resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} + + remark-frontmatter@3.0.0: + resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==} + + remark-gfm@1.0.0: + resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==} + + remark-parse@9.0.0: + resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-package-name@2.0.1: + resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + rollup@4.44.2: + resolution: {integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + seek-bzip@1.0.6: + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} + hasBin: true + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + sentence-case@3.0.4: + resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shellcheck@3.1.0: + resolution: {integrity: sha512-C6IM1sziNIhCLyVszKZ/mKHQN2/CZ8qZ3sFt8mFOmL0ApoaXLTqyeEVfo+t4MlTVw+hS+kIqSaaGDDrrS0nKBA==} + engines: {node: '>=18.12.0'} + hasBin: true + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-dirs@2.1.0: + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tar-stream@1.6.2: + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-buffer@1.2.1: + resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + traverse@0.6.11: + resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} + engines: {node: '>= 0.4'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trough@1.0.5: + resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.0: + resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray.prototype.slice@1.0.5: + resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} + engines: {node: '>= 0.4'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + + underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + + unified@9.2.2: + resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + + unist-util-is@4.1.0: + resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} + + unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + + unist-util-visit-parents@3.1.1: + resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + + unrs-resolver@1.11.0: + resolution: {integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg==} + + update-section@0.3.3: + resolution: {integrity: sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==} + + upper-case-first@2.0.2: + resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} + + upper-case@2.0.2: + resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@2.0.4: + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + + vfile@4.2.1: + resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@7.0.2: + resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@1.0.5: + resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.0 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@emnapi/core@1.4.4': + dependencies: + '@emnapi/wasi-threads': 1.0.3 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.4': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@es-joy/jsdoccomment@0.37.1': + dependencies: + comment-parser: 1.3.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.0.0 + + '@esbuild/aix-ppc64@0.25.6': + optional: true + + '@esbuild/android-arm64@0.25.6': + optional: true + + '@esbuild/android-arm@0.25.6': + optional: true + + '@esbuild/android-x64@0.25.6': + optional: true + + '@esbuild/darwin-arm64@0.25.6': + optional: true + + '@esbuild/darwin-x64@0.25.6': + optional: true + + '@esbuild/freebsd-arm64@0.25.6': + optional: true + + '@esbuild/freebsd-x64@0.25.6': + optional: true + + '@esbuild/linux-arm64@0.25.6': + optional: true + + '@esbuild/linux-arm@0.25.6': + optional: true + + '@esbuild/linux-ia32@0.25.6': + optional: true + + '@esbuild/linux-loong64@0.25.6': + optional: true + + '@esbuild/linux-mips64el@0.25.6': + optional: true + + '@esbuild/linux-ppc64@0.25.6': + optional: true + + '@esbuild/linux-riscv64@0.25.6': + optional: true + + '@esbuild/linux-s390x@0.25.6': + optional: true + + '@esbuild/linux-x64@0.25.6': + optional: true + + '@esbuild/netbsd-arm64@0.25.6': + optional: true + + '@esbuild/netbsd-x64@0.25.6': + optional: true + + '@esbuild/openbsd-arm64@0.25.6': + optional: true + + '@esbuild/openbsd-x64@0.25.6': + optional: true + + '@esbuild/openharmony-arm64@0.25.6': + optional: true + + '@esbuild/sunos-x64@0.25.6': + optional: true + + '@esbuild/win32-arm64@0.25.6': + optional: true + + '@esbuild/win32-ia32@0.25.6': + optional: true + + '@esbuild/win32-x64@0.25.6': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + + '@fastify/busboy@2.1.1': {} + + '@graphql-typed-document-node/core@3.2.0(graphql@16.11.0)': + dependencies: + graphql: 16.11.0 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.4 '@jridgewell/trace-mapping': 0.3.29 - dev: true - /@jridgewell/resolve-uri@3.1.2: - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/resolve-uri@3.1.2': {} - /@jridgewell/sourcemap-codec@1.5.4: - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - dev: true + '@jridgewell/sourcemap-codec@1.5.4': {} - /@jridgewell/trace-mapping@0.3.29: - resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@jridgewell/trace-mapping@0.3.29': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 - dev: true - /@napi-rs/wasm-runtime@0.2.12: - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - requiresBuild: true + '@napi-rs/wasm-runtime@0.2.11': dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 - dev: true + '@emnapi/core': 1.4.4 + '@emnapi/runtime': 1.4.4 + '@tybys/wasm-util': 0.9.0 optional: true - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - /@nolyfill/is-core-module@1.0.39: - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - dev: true - - /@opentelemetry/api@1.9.0: - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} - engines: {node: '>=8.0.0'} - dev: false - - /@opentelemetry/core@1.3.1(@opentelemetry/api@1.9.0): - resolution: {integrity: sha512-k7lOC86N7WIyUZsUuSKZfFIrUtINtlauMGQsC1r7jNmcr0vVJGqK1ROBvt7WWMxLbpMnt1q2pXJO8tKu0b9auA==} - engines: {node: '>=8.12.0'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.2.0' - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.3.1 - dev: false - - /@opentelemetry/semantic-conventions@1.3.1: - resolution: {integrity: sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA==} - engines: {node: '>=8.12.0'} - dev: false + '@nolyfill/is-core-module@1.0.39': {} - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - dev: true + '@pkgjs/parseargs@0.11.0': optional: true - /@protobufjs/aspromise@1.1.2: - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - dev: false - - /@protobufjs/base64@1.1.2: - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - dev: false - - /@protobufjs/codegen@2.0.4: - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - dev: false - - /@protobufjs/eventemitter@1.1.0: - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - dev: false - - /@protobufjs/fetch@1.1.0: - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - dev: false - - /@protobufjs/float@1.0.2: - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - dev: false - - /@protobufjs/inquire@1.1.0: - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - dev: false - - /@protobufjs/path@1.1.2: - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - dev: false - - /@protobufjs/pool@1.1.0: - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - dev: false - - /@protobufjs/utf8@1.1.0: - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - dev: false - - /@publint/pack@0.1.2: - resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} - engines: {node: '>=18'} - dev: true + '@publint/pack@0.1.2': {} - /@rollup/rollup-android-arm-eabi@4.46.2: - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@rollup/rollup-android-arm-eabi@4.44.2': optional: true - /@rollup/rollup-android-arm64@4.46.2: - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + '@rollup/rollup-android-arm64@4.44.2': optional: true - /@rollup/rollup-darwin-arm64@4.46.2: - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@rollup/rollup-darwin-arm64@4.44.2': optional: true - /@rollup/rollup-darwin-x64@4.46.2: - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@rollup/rollup-darwin-x64@4.44.2': optional: true - /@rollup/rollup-freebsd-arm64@4.46.2: - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true + '@rollup/rollup-freebsd-arm64@4.44.2': optional: true - /@rollup/rollup-freebsd-x64@4.46.2: - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true + '@rollup/rollup-freebsd-x64@4.44.2': optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.46.2: - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm-gnueabihf@4.44.2': optional: true - /@rollup/rollup-linux-arm-musleabihf@4.46.2: - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm-musleabihf@4.44.2': optional: true - /@rollup/rollup-linux-arm64-gnu@4.46.2: - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm64-gnu@4.44.2': optional: true - /@rollup/rollup-linux-arm64-musl@4.46.2: - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-arm64-musl@4.44.2': optional: true - /@rollup/rollup-linux-loongarch64-gnu@4.46.2: - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-loongarch64-gnu@4.44.2': optional: true - /@rollup/rollup-linux-ppc64-gnu@4.46.2: - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-powerpc64le-gnu@4.44.2': optional: true - /@rollup/rollup-linux-riscv64-gnu@4.46.2: - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-riscv64-gnu@4.44.2': optional: true - /@rollup/rollup-linux-riscv64-musl@4.46.2: - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-riscv64-musl@4.44.2': optional: true - /@rollup/rollup-linux-s390x-gnu@4.46.2: - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-s390x-gnu@4.44.2': optional: true - /@rollup/rollup-linux-x64-gnu@4.46.2: - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-x64-gnu@4.44.2': optional: true - /@rollup/rollup-linux-x64-musl@4.46.2: - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@rollup/rollup-linux-x64-musl@4.44.2': optional: true - /@rollup/rollup-win32-arm64-msvc@4.46.2: - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-arm64-msvc@4.44.2': optional: true - /@rollup/rollup-win32-ia32-msvc@4.46.2: - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-ia32-msvc@4.44.2': optional: true - /@rollup/rollup-win32-x64-msvc@4.46.2: - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@rollup/rollup-win32-x64-msvc@4.44.2': optional: true - /@sindresorhus/is@4.6.0: - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - dev: false + '@sindresorhus/is@4.6.0': {} - /@stricli/auto-complete@1.2.0: - resolution: {integrity: sha512-r9/msiloVmTF95mdhe04Uzqei1B0ZofhYRLeiPqpJ1W1RMCC8p9iW7kqBZEbALl2aRL5ZK9OEW3Q1cIejH7KEQ==} - hasBin: true + '@stricli/auto-complete@1.2.0': dependencies: '@stricli/core': 1.2.0 - dev: false - /@stricli/core@1.2.0: - resolution: {integrity: sha512-5b+npntDY0TAB7wAw0daGlh3/R2sf0TDLyrB1By2jCNH+C+lmcSqMtJXOMLVtEGSkIOvqAgIWpLMSs1PXqzt3w==} - dev: false + '@stricli/core@1.2.0': {} - /@szmarczak/http-timer@4.0.6: - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 - dev: false - /@textlint/ast-node-types@12.6.1: - resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} - dev: true + '@textlint/ast-node-types@12.6.1': {} - /@textlint/markdown-to-ast@12.6.1: - resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} + '@textlint/markdown-to-ast@12.6.1': dependencies: '@textlint/ast-node-types': 12.6.1 debug: 4.4.1 @@ -1075,343 +3713,196 @@ packages: unified: 9.2.2 transitivePeerDependencies: - supports-color - dev: true - /@transcend-io/airgap.js-types@12.12.2: - resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} + '@transcend-io/airgap.js-types@12.12.2': dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) - dev: false - /@transcend-io/handlebars-utils@1.1.0: - resolution: {integrity: sha512-mDbKm9JObd9mioNlEYGa2zfTBqli1KAAM+iRHSqi6s2l7MAYENxjlfvXN5uC97T9/90vfWlxNbHjLpUuQVURaA==} + '@transcend-io/handlebars-utils@1.1.0': dependencies: '@transcend-io/type-utils': 1.8.4 change-case: 4.1.2 handlebars: 4.7.8 lodash: 4.17.21 pluralize: 8.0.0 - dev: false - /@transcend-io/internationalization@1.7.4: - resolution: {integrity: sha512-kg6c2Bjr823hBWrx62074ZlyBcW1FvK1dj77+lN7E70EyYxj6o5PordsOzxbLmTV1iZ0GLKGu+QiNl4xVOWfLA==} - dev: false + '@transcend-io/internationalization@1.7.4': {} - /@transcend-io/persisted-state@1.0.4: - resolution: {integrity: sha512-jEDN64pr5Q1fniHMERa2+DKhuHbwdB1zzGdHoWgq4EIEx/ZJQxraiNfYoCpf5hpuiKxtXcIN41SC2/MI6MraoQ==} + '@transcend-io/persisted-state@1.0.4': dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) mkdirp: 1.0.4 - dev: false - /@transcend-io/privacy-types@4.128.0: - resolution: {integrity: sha512-iOFVTpdM+ZFX+YxmqtK5mDJNYAlwVa7JhPg4Yg0pUSFcck7LvZlxFVNwpHVoGGdguy6Ejsh8hfPYKHhBOgBqMQ==} + '@transcend-io/privacy-types@4.128.0': dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) - dev: false - /@transcend-io/secret-value@1.2.1: - resolution: {integrity: sha512-pb2thX9znGJ9GOm6A2kgIzwEchB3E6Zn1KTLIBJx+T+K7XkqRwQJsQL0vKGiJVmZZ0iB3bLSAABHoWwNAZcFxA==} + '@transcend-io/secret-value@1.2.1': dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) - dev: false - /@transcend-io/type-utils@1.8.4: - resolution: {integrity: sha512-J1/T+q2Bs5znAyxQpQjD0wzN+h7Wi1xRsqa99uAu3G8VcKb5nRLnaUyY7Ut8N0YU1FHAxNhyXz9MVakYUigWuw==} + '@transcend-io/type-utils@1.8.4': dependencies: fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) - dev: false - /@tybys/wasm-util@0.10.0: - resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} - requiresBuild: true + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 - dev: true optional: true - /@types/bluebird@3.5.42: - resolution: {integrity: sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==} - dev: true - - /@types/cacheable-request@6.0.3: - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 18.19.121 + '@types/node': 18.19.115 '@types/responselike': 1.0.3 - dev: false - /@types/chai@5.2.2: - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 - dev: true - /@types/cli-progress@3.11.6: - resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} + '@types/cli-progress@3.11.6': dependencies: - '@types/node': 18.19.121 - dev: true + '@types/node': 18.19.115 - /@types/colors@1.2.4: - resolution: {integrity: sha512-oSQxEVIDcYisAzWLa+wr50GSIPu8ml4PsKNJzgrDX3SmEHVBBqbaUurqsUceFauNlCRxNtENKkQm3yOe3m3nfg==} - deprecated: This is a stub types definition. colors provides its own type definitions, so you do not need this installed. + '@types/colors@1.2.4': dependencies: colors: 1.4.0 - dev: true - - /@types/continuation-local-storage@3.2.7: - resolution: {integrity: sha512-Q7dPOymVpRG5Zpz90/o26+OAqOG2Sw+FED7uQmTrJNCF/JAPTylclZofMxZKd6W7g1BDPmT9/C/jX0ZcSNTQwQ==} - dependencies: - '@types/node': 18.19.121 - dev: true - - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - dependencies: - '@types/ms': 2.1.0 - dev: false - /@types/deep-eql@4.0.2: - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - dev: true + '@types/deep-eql@4.0.2': {} - /@types/estree@1.0.8: - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true + '@types/estree@1.0.8': {} - /@types/fuzzysearch@1.0.2: - resolution: {integrity: sha512-G2M0M7acg75BzTtUv1qmPFMe7nTwX1K2AEKNeBO8zW0o+H2oTmyir7IJ2v0UW8pZkY4nHgHALvgpWQpB9WXPpg==} - dev: true + '@types/fuzzysearch@1.0.2': {} - /@types/global-agent@2.1.3: - resolution: {integrity: sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==} - dev: true + '@types/global-agent@2.1.3': {} - /@types/http-cache-semantics@4.0.4: - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - dev: false + '@types/http-cache-semantics@4.0.4': {} - /@types/inquirer-autocomplete-prompt@3.0.3: - resolution: {integrity: sha512-OQCW09mEECgvhcppbQRgZSmWskWv58l+WwyUvWB1oxTu3CZj8keYSDZR9U8owUzJ5Zeux5kacN9iVPJLXcoLXg==} + '@types/inquirer-autocomplete-prompt@3.0.3': dependencies: '@types/inquirer': 7.3.3 - dev: true - /@types/inquirer@7.3.3: - resolution: {integrity: sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==} + '@types/inquirer@7.3.3': dependencies: '@types/through': 0.0.33 rxjs: 6.6.7 - dev: true - /@types/js-yaml@4.0.9: - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - dev: true + '@types/js-yaml@4.0.9': {} - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true + '@types/json-schema@7.0.15': {} - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true + '@types/json5@0.0.29': {} - /@types/jsonstream@0.8.33: - resolution: {integrity: sha512-yhg1SNOgJ8y2nOkvAQ1zZ1Z2xibxgFs7984+EeBPuWgo/TbuYo79+rj2wUVch3KF4GhhcwAi/AlJcehmLCXb3g==} + '@types/jsonstream@0.8.33': dependencies: - '@types/node': 18.19.121 - dev: true + '@types/node': 18.19.115 - /@types/jsonwebtoken@9.0.10: - resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 18.19.121 - dev: true + '@types/node': 18.19.115 - /@types/keyv@3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/keyv@3.1.4': dependencies: - '@types/node': 18.19.121 - dev: false + '@types/node': 18.19.115 - /@types/lodash-es@4.17.12: - resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.20 - dev: true - /@types/lodash@4.17.20: - resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} - dev: true + '@types/lodash@4.17.20': {} - /@types/mdast@3.0.15: - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.11 - dev: true - /@types/minimatch@3.0.5: - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - dev: true - - /@types/ms@2.1.0: - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/minimatch@3.0.5': {} - /@types/node@14.18.63: - resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - dev: false + '@types/ms@2.1.0': {} - /@types/node@18.11.19: - resolution: {integrity: sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==} - dev: false + '@types/node@14.18.63': {} - /@types/node@18.19.121: - resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==} + '@types/node@18.19.115': dependencies: undici-types: 5.26.5 - /@types/parse-json@4.0.2: - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - dev: true + '@types/parse-json@4.0.2': {} - /@types/responselike@1.0.3: - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/responselike@1.0.3': dependencies: - '@types/node': 18.19.121 - dev: false - - /@types/semver@7.7.0: - resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} - dev: true + '@types/node': 18.19.115 - /@types/sequelize@4.28.20: - resolution: {integrity: sha512-XaGOKRhdizC87hDgQ0u3btxzbejlF+t6Hhvkek1HyphqCI4y7zVBIVAGmuc4cWJqGpxusZ1RiBToHHnNK/Edlw==} - dependencies: - '@types/bluebird': 3.5.42 - '@types/continuation-local-storage': 3.2.7 - '@types/lodash': 4.17.20 - '@types/validator': 13.15.2 - dev: true + '@types/semver@7.7.0': {} - /@types/through@0.0.33: - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + '@types/through@0.0.33': dependencies: - '@types/node': 18.19.121 - dev: true - - /@types/unist@2.0.11: - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - dev: true + '@types/node': 18.19.115 - /@types/validator@13.15.2: - resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} + '@types/unist@2.0.11': {} - /@types/yargs-parser@21.0.3: - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - dev: true + '@types/yargs-parser@21.0.3': {} - /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@5.0.4): - resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) debug: 4.4.1 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.0.4): - resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) debug: 4.4.1 eslint: 8.57.1 - typescript: 5.0.4 + optionalDependencies: + typescript: 5.8.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@5.62.0: - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.0.4): - resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) debug: 4.4.1 eslint: 8.57.1 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@5.62.0: - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@typescript-eslint/types@5.62.0': {} - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.0.4): - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -1419,408 +3910,221 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.0.4) - typescript: 5.0.4 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.0.4): - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@5.62.0: - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true + '@ungap/structured-clone@1.3.0': {} - /@unrs/resolver-binding-android-arm-eabi@1.11.1: - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true + '@unrs/resolver-binding-android-arm-eabi@1.11.0': optional: true - /@unrs/resolver-binding-android-arm64@1.11.1: - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true + '@unrs/resolver-binding-android-arm64@1.11.0': optional: true - /@unrs/resolver-binding-darwin-arm64@1.11.1: - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true + '@unrs/resolver-binding-darwin-arm64@1.11.0': optional: true - /@unrs/resolver-binding-darwin-x64@1.11.1: - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true + '@unrs/resolver-binding-darwin-x64@1.11.0': optional: true - /@unrs/resolver-binding-freebsd-x64@1.11.1: - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true + '@unrs/resolver-binding-freebsd-x64@1.11.0': optional: true - /@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1: - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': optional: true - /@unrs/resolver-binding-linux-arm-musleabihf@1.11.1: - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': optional: true - /@unrs/resolver-binding-linux-arm64-gnu@1.11.1: - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': optional: true - /@unrs/resolver-binding-linux-arm64-musl@1.11.1: - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': optional: true - /@unrs/resolver-binding-linux-ppc64-gnu@1.11.1: - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': optional: true - /@unrs/resolver-binding-linux-riscv64-gnu@1.11.1: - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': optional: true - /@unrs/resolver-binding-linux-riscv64-musl@1.11.1: - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': optional: true - /@unrs/resolver-binding-linux-s390x-gnu@1.11.1: - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': optional: true - /@unrs/resolver-binding-linux-x64-gnu@1.11.1: - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': optional: true - /@unrs/resolver-binding-linux-x64-musl@1.11.1: - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true + '@unrs/resolver-binding-linux-x64-musl@1.11.0': optional: true - /@unrs/resolver-binding-wasm32-wasi@1.11.1: - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - requiresBuild: true + '@unrs/resolver-binding-wasm32-wasi@1.11.0': dependencies: - '@napi-rs/wasm-runtime': 0.2.12 - dev: true + '@napi-rs/wasm-runtime': 0.2.11 optional: true - /@unrs/resolver-binding-win32-arm64-msvc@1.11.1: - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': optional: true - /@unrs/resolver-binding-win32-ia32-msvc@1.11.1: - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': optional: true - /@unrs/resolver-binding-win32-x64-msvc@1.11.1: - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': optional: true - /@vitest/expect@3.2.4: - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.1 + chai: 5.2.0 tinyrainbow: 2.0.0 - dev: true - /@vitest/mocker@3.2.4(vite@7.0.6): - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true + '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 - vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) - dev: true + optionalDependencies: + vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) - /@vitest/pretty-format@3.2.4: - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - dev: true - /@vitest/runner@3.2.4: - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 - dev: true - /@vitest/snapshot@3.2.4: - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - dev: true - /@vitest/spy@3.2.4: - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 - dev: true - /@vitest/utils@3.2.4: - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - loupe: 3.2.0 + loupe: 3.1.4 tinyrainbow: 2.0.0 - dev: true - /@vue/compiler-core@3.5.18: - resolution: {integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==} + '@vue/compiler-core@3.5.17': dependencies: '@babel/parser': 7.28.0 - '@vue/shared': 3.5.18 + '@vue/shared': 3.5.17 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - dev: true - /@vue/compiler-dom@3.5.18: - resolution: {integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==} + '@vue/compiler-dom@3.5.17': dependencies: - '@vue/compiler-core': 3.5.18 - '@vue/shared': 3.5.18 - dev: true + '@vue/compiler-core': 3.5.17 + '@vue/shared': 3.5.17 - /@vue/compiler-sfc@3.5.18: - resolution: {integrity: sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==} + '@vue/compiler-sfc@3.5.17': dependencies: '@babel/parser': 7.28.0 - '@vue/compiler-core': 3.5.18 - '@vue/compiler-dom': 3.5.18 - '@vue/compiler-ssr': 3.5.18 - '@vue/shared': 3.5.18 + '@vue/compiler-core': 3.5.17 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-ssr': 3.5.17 + '@vue/shared': 3.5.17 estree-walker: 2.0.2 magic-string: 0.30.17 postcss: 8.5.6 source-map-js: 1.2.1 - dev: true - /@vue/compiler-ssr@3.5.18: - resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==} + '@vue/compiler-ssr@3.5.17': dependencies: - '@vue/compiler-dom': 3.5.18 - '@vue/shared': 3.5.18 - dev: true + '@vue/compiler-dom': 3.5.17 + '@vue/shared': 3.5.17 - /@vue/shared@3.5.18: - resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==} - dev: true + '@vue/shared@3.5.17': {} - /JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 through: 2.3.8 - dev: false - - /acorn-import-attributes@1.9.5(acorn@8.15.0): - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - dependencies: - acorn: 8.15.0 - dev: false - /acorn-jsx@5.3.2(acorn@8.15.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 - dev: true - /acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn@8.15.0: {} - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true - /anchor-markdown-header@0.6.0: - resolution: {integrity: sha512-v7HJMtE1X7wTpNFseRhxsY/pivP4uAJbidVhPT+yhz4i/vV1+qx371IXuV9V7bN6KjFtheLJxqaSm0Y/8neJTA==} + anchor-markdown-header@0.6.0: dependencies: emoji-regex: 10.1.0 - dev: true - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - dev: false - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@5.0.1: {} - /ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - dev: true + ansi-regex@6.1.0: {} - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - dev: true + ansi-styles@6.2.1: {} - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true + any-promise@1.3.0: {} - /are-docs-informative@0.0.2: - resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} - engines: {node: '>=14'} - dev: true + are-docs-informative@0.0.2: {} - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - dev: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argparse@2.0.1: {} - /array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 - dev: true - /array-differ@3.0.0: - resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} - engines: {node: '>=8'} - dev: true + array-differ@3.0.0: {} - /array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} + array-includes@3.1.9: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -1830,36 +4134,24 @@ packages: get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 - dev: true - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true + array-union@2.1.0: {} - /array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + array.prototype.flat@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 - dev: true - /array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.3: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.24.0 es-shim-unscopables: 1.1.0 - dev: true - /arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 @@ -1868,133 +4160,73 @@ packages: es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - dev: true - /arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} - dev: true + arrify@2.0.1: {} - /assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - dev: true + assertion-error@2.0.1: {} - /async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - dev: true + async-function@1.0.0: {} - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false + asynckit@0.4.0: {} - /available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 - dev: true - /bail@1.0.5: - resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} - dev: true + bail@1.0.5: {} - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true + balanced-match@1.0.2: {} - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true + base64-js@1.5.1: {} - /bl@1.2.3: - resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + bl@1.2.3: dependencies: readable-stream: 2.3.8 safe-buffer: 5.2.1 - dev: true - - /bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: false - /boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + boolean@3.2.0: {} - /brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true - /brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 - dev: true - /braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + braces@3.0.3: dependencies: fill-range: 7.1.1 - /buffer-alloc-unsafe@1.1.0: - resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} - dev: true + buffer-alloc-unsafe@1.1.0: {} - /buffer-alloc@1.2.0: - resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + buffer-alloc@1.2.0: dependencies: buffer-alloc-unsafe: 1.1.0 buffer-fill: 1.0.0 - dev: true - /buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: true + buffer-crc32@0.2.13: {} - /buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - dev: false + buffer-equal-constant-time@1.0.1: {} - /buffer-fill@1.0.0: - resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} - dev: true + buffer-fill@1.0.0: {} - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true - /bundle-require@5.1.0(esbuild@0.25.8): - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' + bundle-require@5.1.0(esbuild@0.25.6): dependencies: - esbuild: 0.25.8 + esbuild: 0.25.6 load-tsconfig: 0.2.5 - dev: true - /cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - dev: true + cac@6.7.14: {} - /cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - dev: false + cacheable-lookup@5.0.4: {} - /cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} + cacheable-request@7.0.4: dependencies: clone-response: 1.0.3 get-stream: 5.2.0 @@ -2003,86 +4235,57 @@ packages: lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 - dev: false - /call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - /call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 get-intrinsic: 1.3.0 set-function-length: 1.2.2 - dev: true - /call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - dev: true - /callsite@1.0.0: - resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} - dev: true + callsite@1.0.0: {} - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + callsites@3.1.0: {} - /camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + camel-case@4.1.2: dependencies: pascal-case: 3.1.2 tslib: 2.8.1 - dev: false - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + camelcase@6.3.0: {} - /capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + capital-case@1.0.4: dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case-first: 2.0.2 - dev: false - /ccount@1.1.0: - resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} - dev: true + ccount@1.1.0: {} - /chai@5.2.1: - resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} - engines: {node: '>=18'} + chai@5.2.0: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.2.0 + loupe: 3.1.4 pathval: 2.0.1 - dev: true - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - /change-case@4.1.2: - resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} + change-case@4.1.2: dependencies: camel-case: 4.1.2 capital-case: 1.0.4 @@ -2096,315 +4299,157 @@ packages: sentence-case: 3.0.4 snake-case: 3.0.4 tslib: 2.8.1 - dev: false - /character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - dev: true + character-entities-legacy@1.1.4: {} - /character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} - dev: true + character-entities@1.2.4: {} - /character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} - dev: true + character-reference-invalid@1.1.4: {} - /chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: false + chardet@0.7.0: {} - /check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - dev: true + check-error@2.1.1: {} - /chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 - dev: true - /cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - dev: false - - /cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 - dev: false - /cli-progress@3.12.0: - resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} - engines: {node: '>=4'} + cli-progress@3.12.0: dependencies: string-width: 4.2.3 - dev: false - /cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - dev: false + cli-width@3.0.0: {} - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - /clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + clone-response@1.0.3: dependencies: mimic-response: 1.0.1 - dev: false - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@1.1.4: {} - /colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} + colors@1.4.0: {} - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - dev: false - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true + commander@2.20.3: {} - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: true + commander@4.1.1: {} - /comment-parser@1.3.1: - resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} - engines: {node: '>= 12.0.0'} - dev: true + comment-parser@1.3.1: {} - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + concat-map@0.0.1: {} - /confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - dev: true + confbox@0.1.8: {} - /confusing-browser-globals@1.0.11: - resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - dev: true + confusing-browser-globals@1.0.11: {} - /consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - dev: true + consola@3.4.2: {} - /constant-case@3.0.4: - resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + constant-case@3.0.4: dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case: 2.0.2 - dev: false - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: true + core-util-is@1.0.3: {} - /cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 - dev: true - /cross-fetch@3.2.0: - resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + cross-fetch@3.2.0: dependencies: node-fetch: 2.7.0 transitivePeerDependencies: - encoding - dev: false - /cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true - - /crypto-randomuuid@1.0.0: - resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} - dev: false - /csv-parse@5.6.0: - resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} - dev: false + csv-parse@5.6.0: {} - /data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 - dev: true - /data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} + data-view-byte-length@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 - dev: true - /data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} + data-view-byte-offset@1.0.1: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 - dev: true - - /dd-trace@2.45.1: - resolution: {integrity: sha512-FZu1kvYRFwqR1LHR3iBlSU60R33rdWD+Wtd9yQG5VIlkbrNFTIB0IXSC4fjUusPmvkUUxo4nP0roQBwyxHdnZA==} - engines: {node: '>=12'} - deprecated: The v2.x release line of dd-trace reached End of Life on 2023-08-15! Please upgrade to the latest version of dd-trace to continue receiving updates. - requiresBuild: true - dependencies: - '@datadog/native-appsec': 3.2.0 - '@datadog/native-iast-rewriter': 2.0.1 - '@datadog/native-iast-taint-tracking': 1.5.0 - '@datadog/native-metrics': 1.6.0 - '@datadog/pprof': 3.1.0 - '@datadog/sketches-js': 2.1.1 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.3.1(@opentelemetry/api@1.9.0) - '@types/node': 18.11.19 - crypto-randomuuid: 1.0.0 - diagnostics_channel: 1.1.0 - ignore: 5.3.2 - import-in-the-middle: 1.14.2 - int64-buffer: 0.1.10 - ipaddr.js: 2.2.0 - istanbul-lib-coverage: 3.2.0 - koalas: 1.0.2 - limiter: 1.1.5 - lodash.kebabcase: 4.1.1 - lodash.pick: 4.4.0 - lodash.sortby: 4.7.0 - lodash.uniq: 4.5.0 - lru-cache: 7.18.3 - methods: 1.1.2 - module-details-from-path: 1.0.4 - msgpack-lite: 0.1.26 - node-abort-controller: 3.1.1 - opentracing: 0.14.7 - path-to-regexp: 0.1.12 - protobufjs: 7.5.3 - retry: 0.13.1 - semver: 7.7.2 - dev: false - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@3.2.7: dependencies: ms: 2.1.3 - dev: true - /debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.4.1: dependencies: ms: 2.1.3 - /decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - dev: false + decode-uri-component@0.2.2: {} - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 - dev: false - /decompress-tar@4.1.1: - resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} - engines: {node: '>=4'} + decompress-tar@4.1.1: dependencies: file-type: 5.2.0 is-stream: 1.1.0 tar-stream: 1.6.2 - dev: true - /decompress-tarbz2@4.1.1: - resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} - engines: {node: '>=4'} + decompress-tarbz2@4.1.1: dependencies: decompress-tar: 4.1.1 file-type: 6.2.0 is-stream: 1.1.0 seek-bzip: 1.0.6 unbzip2-stream: 1.4.3 - dev: true - /decompress-targz@4.1.1: - resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} - engines: {node: '>=4'} + decompress-targz@4.1.1: dependencies: decompress-tar: 4.1.1 file-type: 5.2.0 is-stream: 1.1.0 - dev: true - /decompress-unzip@4.0.1: - resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} - engines: {node: '>=4'} + decompress-unzip@4.0.1: dependencies: file-type: 3.9.0 get-stream: 2.3.1 pify: 2.3.0 yauzl: 2.10.0 - dev: true - /decompress@4.2.1: - resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} - engines: {node: '>=4'} + decompress@4.2.1: dependencies: decompress-tar: 4.1.1 decompress-tarbz2: 4.1.1 @@ -2414,56 +4459,32 @@ packages: make-dir: 1.3.0 pify: 2.3.0 strip-dirs: 2.1.0 - dev: true - /deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - dev: true + deep-eql@5.0.2: {} - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + deep-is@0.1.4: {} - /defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - dev: false + defer-to-connect@2.0.1: {} - /define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - /delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - dev: false - - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: false + delayed-stream@1.0.0: {} - /depcheck@1.4.7: - resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} - engines: {node: '>=10'} - hasBin: true + depcheck@1.4.7: dependencies: '@babel/parser': 7.28.0 '@babel/traverse': 7.28.0 - '@vue/compiler-sfc': 3.5.18 + '@vue/compiler-sfc': 3.5.17 callsite: 1.0.0 camelcase: 6.3.0 cosmiconfig: 7.1.0 @@ -2486,35 +4507,18 @@ packages: yargs: 16.2.0 transitivePeerDependencies: - supports-color - dev: true - - /deps-regex@0.2.0: - resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} - dev: true - /detect-file@1.0.0: - resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} - engines: {node: '>=0.10.0'} - dev: true + deps-regex@0.2.0: {} - /detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + detect-file@1.0.0: {} - /diagnostics_channel@1.1.0: - resolution: {integrity: sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==} - engines: {node: '>=4'} - dev: false + detect-node@2.1.0: {} - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dev: true - /doctoc@2.2.1: - resolution: {integrity: sha512-qNJ1gsuo7hH40vlXTVVrADm6pdg30bns/Mo7Nv1SxuXSM1bwF9b4xQ40a6EFT/L1cI+Yylbyi8MPI4G4y7XJzQ==} - hasBin: true + doctoc@2.2.1: dependencies: '@textlint/markdown-to-ast': 12.6.1 anchor-markdown-header: 0.6.0 @@ -2524,124 +4528,75 @@ packages: update-section: 0.3.3 transitivePeerDependencies: - supports-color - dev: true - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + doctrine@2.1.0: dependencies: esutils: 2.0.3 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - dev: true - /dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dom-serializer@1.4.1: dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 entities: 2.2.0 - dev: true - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true + domelementtype@2.3.0: {} - /domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} + domhandler@4.3.1: dependencies: domelementtype: 2.3.0 - dev: true - /domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 domelementtype: 2.3.0 domhandler: 4.3.1 - dev: true - /dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dot-case@3.0.4: dependencies: no-case: 3.0.4 tslib: 2.8.1 - dev: false - /dottie@2.0.6: - resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} - dev: false - - /dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true + eastasianwidth@0.2.0: {} - /ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 - dev: false - /emoji-regex@10.1.0: - resolution: {integrity: sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==} - dev: true + emoji-regex@10.1.0: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@8.0.0: {} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true + emoji-regex@9.2.2: {} - /end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + end-of-stream@1.4.5: dependencies: once: 1.4.0 - /entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - dev: true + entities@2.2.0: {} - /entities@3.0.1: - resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} - engines: {node: '>=0.12'} - dev: true + entities@3.0.1: {} - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - dev: true + entities@4.5.0: {} - /envalid@8.1.0: - resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} - engines: {node: '>=18'} + envalid@8.1.0: dependencies: tslib: 2.8.1 - dev: true - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - dev: true - /es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} - engines: {node: '>= 0.4'} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 @@ -2697,204 +4652,122 @@ packages: typed-array-length: 1.0.7 unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - dev: true - /es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + es-define-property@1.0.1: {} - /es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + es-errors@1.3.0: {} - /es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - dev: true + es-module-lexer@1.7.0: {} - /es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 - /es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - /es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.2 - dev: true - /es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} + es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 - dev: true - /es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + es6-error@4.1.1: {} - /esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true + esbuild@0.25.6: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 - dev: true - - /escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - dev: true - - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - /eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.27.5)(eslint@8.57.1): - resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} - engines: {node: ^10.12.0 || >=12.0.0} - peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.2 + '@esbuild/aix-ppc64': 0.25.6 + '@esbuild/android-arm': 0.25.6 + '@esbuild/android-arm64': 0.25.6 + '@esbuild/android-x64': 0.25.6 + '@esbuild/darwin-arm64': 0.25.6 + '@esbuild/darwin-x64': 0.25.6 + '@esbuild/freebsd-arm64': 0.25.6 + '@esbuild/freebsd-x64': 0.25.6 + '@esbuild/linux-arm': 0.25.6 + '@esbuild/linux-arm64': 0.25.6 + '@esbuild/linux-ia32': 0.25.6 + '@esbuild/linux-loong64': 0.25.6 + '@esbuild/linux-mips64el': 0.25.6 + '@esbuild/linux-ppc64': 0.25.6 + '@esbuild/linux-riscv64': 0.25.6 + '@esbuild/linux-s390x': 0.25.6 + '@esbuild/linux-x64': 0.25.6 + '@esbuild/netbsd-arm64': 0.25.6 + '@esbuild/netbsd-x64': 0.25.6 + '@esbuild/openbsd-arm64': 0.25.6 + '@esbuild/openbsd-x64': 0.25.6 + '@esbuild/openharmony-arm64': 0.25.6 + '@esbuild/sunos-x64': 0.25.6 + '@esbuild/win32-arm64': 0.25.6 + '@esbuild/win32-ia32': 0.25.6 + '@esbuild/win32-x64': 0.25.6 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.27.5)(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) object.assign: 4.1.7 object.entries: 1.1.9 semver: 6.3.1 - dev: true - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1): - resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 eslint: 8.57.1 - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.14 - unrs-resolver: 1.11.1 + unrs-resolver: 1.11.0 + optionalDependencies: + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - dev: true - /eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true + eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.27.5)(eslint@8.57.1) transitivePeerDependencies: - supports-color - dev: true - /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): - resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} - engines: {node: '>=6.5.0'} - peerDependencies: - eslint: '>=4.19.1' + eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): dependencies: escape-string-regexp: 1.0.5 eslint: 8.57.1 ignore: 5.3.2 - dev: true - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true + eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.0.4) array-includes: 3.1.9 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 @@ -2902,7 +4775,7 @@ packages: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) has: 1.0.4 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -2911,17 +4784,14 @@ packages: resolve: 1.22.10 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true - /eslint-plugin-jsdoc@41.1.2(eslint@8.57.1): - resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==} - engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint-plugin-jsdoc@41.1.2(eslint@8.57.1): dependencies: '@es-joy/jsdoccomment': 0.37.1 are-docs-informative: 0.0.2 @@ -2934,34 +4804,20 @@ packages: spdx-expression-parse: 3.0.1 transitivePeerDependencies: - supports-color - dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - dev: true - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + eslint-visitor-keys@3.4.3: {} - /eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) '@eslint-community/regexpp': 4.12.1 @@ -3003,111 +4859,59 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@9.6.1: dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 - dev: true - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - dev: true + esprima@4.0.1: {} - /esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} + esquery@1.6.0: dependencies: estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - dev: true - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true + estraverse@4.3.0: {} - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true + estraverse@5.3.0: {} - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true + estree-walker@2.0.2: {} - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 - dev: true - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true + esutils@2.0.3: {} - /event-lite@0.1.3: - resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==} - dev: false - - /expand-tilde@2.0.2: - resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} - engines: {node: '>=0.10.0'} + expand-tilde@2.0.2: dependencies: homedir-polyfill: 1.0.3 - dev: true - /expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - dev: true + expect-type@1.2.2: {} - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: true + extend@3.0.2: {} - /external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 - dev: false - /extract-files@9.0.0: - resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==} - engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0} - dev: false + extract-files@9.0.0: {} - /fast-csv@4.3.6: - resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} - engines: {node: '>=10.0.0'} + fast-csv@4.3.6: dependencies: '@fast-csv/format': 4.3.5 '@fast-csv/parse': 4.3.6 - dev: false - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true + fast-deep-equal@3.1.3: {} - /fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -3115,178 +4919,102 @@ packages: merge2: 1.4.1 micromatch: 4.0.8 - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true + fast-json-stable-stringify@2.1.0: {} - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true + fast-levenshtein@2.0.6: {} - /fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fastq@1.19.1: dependencies: reusify: 1.1.0 - /fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + fault@1.0.4: dependencies: format: 0.2.2 - dev: true - /fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fd-slicer@1.1.0: dependencies: pend: 1.2.0 - dev: true - /fdir@6.4.6(picomatch@4.0.3): - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - dependencies: - picomatch: 4.0.3 - dev: true + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 - /figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 - dev: false - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - dev: true - /file-type@3.9.0: - resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} - engines: {node: '>=0.10.0'} - dev: true + file-type@3.9.0: {} - /file-type@5.2.0: - resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} - engines: {node: '>=4'} - dev: true + file-type@5.2.0: {} - /file-type@6.2.0: - resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} - engines: {node: '>=4'} - dev: true + file-type@6.2.0: {} - /fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - /filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} - engines: {node: '>=0.10.0'} - dev: false + filter-obj@1.1.0: {} - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true - /findup-sync@5.0.0: - resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} - engines: {node: '>= 10.13.0'} + findup-sync@5.0.0: dependencies: detect-file: 1.0.0 is-glob: 4.0.3 micromatch: 4.0.8 resolve-dir: 1.0.1 - dev: true - /fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.17 mlly: 1.7.4 - rollup: 4.46.2 - dev: true + rollup: 4.44.2 - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@3.2.0: dependencies: flatted: 3.3.3 keyv: 4.5.4 rimraf: 3.0.2 - dev: true - /flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - dev: true + flatted@3.3.3: {} - /for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} + for-each@0.3.5: dependencies: is-callable: 1.2.7 - dev: true - /foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - dev: true - /form-data@3.0.4: - resolution: {integrity: sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==} - engines: {node: '>= 6'} + form-data@3.0.3: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 mime-types: 2.1.35 - dev: false - /format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - dev: true + format@0.2.2: {} - /fp-ts@2.16.10: - resolution: {integrity: sha512-vuROzbNVfCmUkZSUbnWSltR1sbheyQbTzug7LB/46fEa1c0EucLeBaCEUE0gF3ZGUGBt9lVUiziGOhhj6K1ORA==} - dev: false + fp-ts@2.16.10: {} - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: true + fs-constants@1.0.0: {} - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true + fsevents@2.3.3: optional: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-bind@1.1.2: {} - /function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} + function.prototype.name@1.1.8: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -3294,24 +5022,14 @@ packages: functions-have-names: 1.2.3 hasown: 2.0.2 is-callable: 1.2.7 - dev: true - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true + functions-have-names@1.2.3: {} - /fuzzysearch@1.0.3: - resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} - dev: false + fuzzysearch@1.0.3: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true + get-caller-file@2.0.5: {} - /get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -3324,59 +5042,39 @@ packages: hasown: 2.0.2 math-intrinsics: 1.1.0 - /get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - /get-stream@2.3.1: - resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} - engines: {node: '>=0.10.0'} + get-stream@2.3.1: dependencies: object-assign: 4.1.1 pinkie-promise: 2.0.1 - dev: true - /get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} + get-stream@5.2.0: dependencies: pump: 3.0.3 - dev: false - /get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 - dev: true - /get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 - dev: true - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true + glob@10.4.5: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 @@ -3384,11 +5082,8 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -3396,11 +5091,8 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true - /global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} + global-agent@3.0.0: dependencies: boolean: 3.2.0 es6-error: 4.1.1 @@ -3409,43 +5101,30 @@ packages: semver: 7.7.2 serialize-error: 7.0.1 - /global-modules@1.0.0: - resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} - engines: {node: '>=0.10.0'} + global-modules@1.0.0: dependencies: global-prefix: 1.0.2 is-windows: 1.0.2 resolve-dir: 1.0.1 - dev: true - /global-prefix@1.0.2: - resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} - engines: {node: '>=0.10.0'} + global-prefix@1.0.2: dependencies: expand-tilde: 2.0.2 homedir-polyfill: 1.0.3 ini: 1.3.8 is-windows: 1.0.2 which: 1.3.1 - dev: true - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - dev: true - /globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -3453,19 +5132,12 @@ packages: ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 - dev: true - /globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - dev: true + globrex@0.1.2: {} - /gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + gopd@1.2.0: {} - /got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} + got@11.8.6: dependencies: '@sindresorhus/is': 4.6.0 '@szmarczak/http-timer': 4.0.6 @@ -3478,39 +5150,24 @@ packages: lowercase-keys: 2.0.0 p-cancelable: 2.1.1 responselike: 2.0.1 - dev: false - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true + graceful-fs@4.2.11: {} - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + graphemer@1.4.0: {} - /graphql-request@5.2.0(graphql@16.11.0): - resolution: {integrity: sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ==} - peerDependencies: - graphql: 14 - 16 + graphql-request@5.2.0(graphql@16.11.0): dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.11.0) cross-fetch: 3.2.0 extract-files: 9.0.0 - form-data: 3.0.4 + form-data: 3.0.3 graphql: 16.11.0 transitivePeerDependencies: - encoding - dev: false - /graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - dev: false + graphql@16.11.0: {} - /handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + handlebars@4.7.8: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -3518,147 +5175,79 @@ packages: wordwrap: 1.0.0 optionalDependencies: uglify-js: 3.19.3 - dev: false - /has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - dev: true + has-bigints@1.1.0: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + has-flag@4.0.0: {} - /has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 - /has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} + has-proto@1.2.0: dependencies: dunder-proto: 1.0.1 - dev: true - /has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + has-symbols@1.1.0: {} - /has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.1.0 - /has@1.0.4: - resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} - engines: {node: '>= 0.4.0'} - dev: true + has@1.0.4: {} - /hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + hasown@2.0.2: dependencies: function-bind: 1.1.2 - /header-case@2.0.4: - resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + header-case@2.0.4: dependencies: capital-case: 1.0.4 tslib: 2.8.1 - dev: false - /homedir-polyfill@1.0.3: - resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} - engines: {node: '>=0.10.0'} + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 - dev: true - /htmlparser2@7.2.0: - resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + htmlparser2@7.2.0: dependencies: domelementtype: 2.3.0 domhandler: 4.3.1 domutils: 2.8.0 entities: 3.0.1 - dev: true - /http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - dev: false + http-cache-semantics@4.2.0: {} - /http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - dev: false - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - dev: false - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ieee754@1.2.1: {} - /ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + ignore@5.3.2: {} - /import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /import-in-the-middle@1.14.2: - resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} - dependencies: - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) - cjs-module-lexer: 1.4.3 - module-details-from-path: 1.0.4 - dev: false - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflection@1.13.4: - resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} - engines: {'0': node >= 0.4.0} - dev: false + imurmurhash@0.1.4: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true + ini@1.3.8: {} - /inquirer-autocomplete-prompt@1.3.0(inquirer@7.3.3): - resolution: {integrity: sha512-zvAc+A6SZdcN+earG5SsBu1RnQdtBS4o8wZ/OqJiCfL34cfOx+twVRq7wumYix6Rkdjn1N2nVCcO3wHqKqgdGg==} - engines: {node: '>=10'} - peerDependencies: - inquirer: ^5.0.0 || ^6.0.0 || ^7.0.0 + inquirer-autocomplete-prompt@1.3.0(inquirer@7.3.3): dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -3666,11 +5255,8 @@ packages: inquirer: 7.3.3 run-async: 2.4.1 rxjs: 6.6.7 - dev: false - /inquirer@7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} + inquirer@7.3.3: dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -3685,390 +5271,212 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 - dev: false - /int64-buffer@0.1.10: - resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} - dev: false - - /internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 - dev: true - /io-ts-types@0.5.19(fp-ts@2.16.10)(io-ts@2.2.22)(monocle-ts@2.3.13)(newtype-ts@0.3.5): - resolution: {integrity: sha512-kQOYYDZG5vKre+INIDZbLeDJe+oM+4zLpUkjXyTMyUfoCpjJNyi29ZLkuEAwcPufaYo3yu/BsemZtbdD+NtRfQ==} - peerDependencies: - fp-ts: ^2.0.0 - io-ts: ^2.0.0 - monocle-ts: ^2.0.0 - newtype-ts: ^0.3.2 + io-ts-types@0.5.19(fp-ts@2.16.10)(io-ts@2.2.22(fp-ts@2.16.10))(monocle-ts@2.3.13(fp-ts@2.16.10))(newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10))): dependencies: fp-ts: 2.16.10 io-ts: 2.2.22(fp-ts@2.16.10) monocle-ts: 2.3.13(fp-ts@2.16.10) - newtype-ts: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13) - dev: false + newtype-ts: 0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)) - /io-ts@2.2.22(fp-ts@2.16.10): - resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} - peerDependencies: - fp-ts: ^2.5.0 + io-ts@2.2.22(fp-ts@2.16.10): dependencies: fp-ts: 2.16.10 - dev: false - /ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} - engines: {node: '>= 10'} - dev: false - - /is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} - dev: true + is-alphabetical@1.0.4: {} - /is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-alphanumerical@1.0.4: dependencies: is-alphabetical: 1.0.4 is-decimal: 1.0.4 - dev: true - /is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 - dev: true - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true + is-arrayish@0.2.1: {} - /is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - dev: true - - /is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} + + is-bigint@1.1.0: dependencies: has-bigints: 1.1.0 - dev: true - /is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} + is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 - dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - dev: true + is-buffer@2.0.5: {} - /is-bun-module@2.0.0: - resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + is-bun-module@2.0.0: dependencies: semver: 7.7.2 - dev: true - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true + is-callable@1.2.7: {} - /is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - dev: true - /is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} + is-data-view@1.0.2: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-typed-array: 1.1.15 - dev: true - /is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} + is-date-object@1.1.0: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 - dev: true - /is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} - dev: true + is-decimal@1.0.4: {} - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + is-extglob@2.1.1: {} - /is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} + is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 - dev: true - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} + is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - dev: true - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - /is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - dev: true + is-hexadecimal@1.0.4: {} - /is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - dev: true + is-map@2.0.3: {} - /is-natural-number@4.0.1: - resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} - dev: true + is-natural-number@4.0.1: {} - /is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - dev: true + is-negative-zero@2.0.3: {} - /is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + is-number@7.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - dev: true + is-plain-obj@2.1.0: {} - /is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 - dev: true - /is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - dev: true + is-set@2.0.3: {} - /is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} + is-shared-array-buffer@1.0.4: dependencies: call-bound: 1.0.4 - dev: true - /is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - dev: true + is-stream@1.1.0: {} - /is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} + is-string@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 - dev: true - /is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} + is-symbol@1.1.1: dependencies: call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 - dev: true - /is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.19 - dev: true - /is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - dev: true + is-weakmap@2.0.2: {} - /is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} + is-weakref@1.1.1: dependencies: call-bound: 1.0.4 - dev: true - /is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} + is-weakset@2.0.4: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 - dev: true - - /is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - dev: true - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + is-windows@1.0.2: {} - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + isarray@1.0.0: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + isarray@2.0.5: {} - /istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} - engines: {node: '>=8'} - dev: false + isexe@2.0.0: {} - /jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true - /joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - dev: true + joycon@3.1.1: {} - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true + js-tokens@4.0.0: {} - /js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - dev: true + js-tokens@9.0.1: {} - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - /jsdoc-type-pratt-parser@4.0.0: - resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} - engines: {node: '>=12.0.0'} - dev: true + jsdoc-type-pratt-parser@4.0.0: {} - /jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - dev: true + jsesc@3.1.0: {} - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-buffer@3.0.1: {} - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true + json-parse-even-better-errors@2.3.1: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-schema-traverse@0.4.1: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json-stringify-safe@5.0.1: {} - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true + json5@1.0.2: dependencies: minimist: 1.2.8 - dev: true - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - dev: true + json5@2.2.3: {} - /jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - dev: false + jsonparse@1.3.1: {} - /jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -4080,226 +5488,117 @@ packages: lodash.once: 4.1.1 ms: 2.1.3 semver: 7.7.2 - dev: false - /jwa@1.4.2: - resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + jwa@1.4.2: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - dev: false - /jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@3.2.2: dependencies: jwa: 1.4.2 safe-buffer: 5.2.1 - dev: false - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - /koalas@1.0.2: - resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} - engines: {node: '>=0.10.0'} - dev: false - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - - /lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - dev: true - /limiter@1.1.5: - resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} - dev: false + lilconfig@3.1.3: {} - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true + lines-and-columns@1.2.4: {} - /load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true + load-tsconfig@0.2.5: {} - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false - - /lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - dev: false - - /lodash.groupby@4.6.0: - resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} - dev: false - /lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - dev: false + lodash-es@4.17.21: {} - /lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - dev: false + lodash.escaperegexp@4.1.2: {} - /lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - dev: false + lodash.groupby@4.6.0: {} - /lodash.isfunction@3.0.9: - resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} - dev: false + lodash.includes@4.3.0: {} - /lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - dev: false + lodash.isboolean@3.0.3: {} - /lodash.isnil@4.0.0: - resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} - dev: false + lodash.isequal@4.5.0: {} - /lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - dev: false + lodash.isfunction@3.0.9: {} - /lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: false + lodash.isinteger@4.0.4: {} - /lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - dev: false + lodash.isnil@4.0.0: {} - /lodash.isundefined@3.0.1: - resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} - dev: false + lodash.isnumber@3.0.3: {} - /lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - dev: false + lodash.isplainobject@4.0.6: {} - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true + lodash.isstring@4.0.1: {} - /lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - dev: false + lodash.isundefined@3.0.1: {} - /lodash.pick@4.4.0: - resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} - deprecated: This package is deprecated. Use destructuring assignment syntax instead. - dev: false + lodash.merge@4.6.2: {} - /lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.once@4.1.1: {} - /lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - dev: false + lodash.sortby@4.7.0: {} - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash.uniq@4.5.0: {} - /long@5.3.2: - resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - dev: false + lodash@4.17.21: {} - /longest-streak@2.0.4: - resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} - dev: true + longest-streak@2.0.4: {} - /loupe@3.2.0: - resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} - dev: true + loupe@3.1.4: {} - /lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lower-case@2.0.2: dependencies: tslib: 2.8.1 - dev: false - - /lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: false - /lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: true + lowercase-keys@2.0.0: {} - /lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - dev: false + lru-cache@10.4.3: {} - /magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 - dev: true - /make-dir@1.3.0: - resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} - engines: {node: '>=4'} + make-dir@1.3.0: dependencies: pify: 3.0.0 - dev: true - /markdown-table@2.0.0: - resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + markdown-table@2.0.0: dependencies: repeat-string: 1.6.1 - dev: true - /matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 - /math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + math-intrinsics@1.1.0: {} - /mdast-util-find-and-replace@1.1.1: - resolution: {integrity: sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==} + mdast-util-find-and-replace@1.1.1: dependencies: escape-string-regexp: 4.0.0 unist-util-is: 4.1.0 unist-util-visit-parents: 3.1.1 - dev: true - /mdast-util-footnote@0.1.7: - resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==} + mdast-util-footnote@0.1.7: dependencies: mdast-util-to-markdown: 0.6.5 micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-from-markdown@0.8.5: - resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + mdast-util-from-markdown@0.8.5: dependencies: '@types/mdast': 3.0.15 mdast-util-to-string: 2.0.0 @@ -4308,45 +5607,33 @@ packages: unist-util-stringify-position: 2.0.3 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-frontmatter@0.2.0: - resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==} + mdast-util-frontmatter@0.2.0: dependencies: micromark-extension-frontmatter: 0.2.2 - dev: true - /mdast-util-gfm-autolink-literal@0.1.3: - resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==} + mdast-util-gfm-autolink-literal@0.1.3: dependencies: ccount: 1.1.0 mdast-util-find-and-replace: 1.1.1 micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-gfm-strikethrough@0.2.3: - resolution: {integrity: sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==} + mdast-util-gfm-strikethrough@0.2.3: dependencies: mdast-util-to-markdown: 0.6.5 - dev: true - /mdast-util-gfm-table@0.1.6: - resolution: {integrity: sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==} + mdast-util-gfm-table@0.1.6: dependencies: markdown-table: 2.0.0 mdast-util-to-markdown: 0.6.5 - dev: true - /mdast-util-gfm-task-list-item@0.1.6: - resolution: {integrity: sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==} + mdast-util-gfm-task-list-item@0.1.6: dependencies: mdast-util-to-markdown: 0.6.5 - dev: true - /mdast-util-gfm@0.1.2: - resolution: {integrity: sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==} + mdast-util-gfm@0.1.2: dependencies: mdast-util-gfm-autolink-literal: 0.1.3 mdast-util-gfm-strikethrough: 0.2.3 @@ -4355,10 +5642,8 @@ packages: mdast-util-to-markdown: 0.6.5 transitivePeerDependencies: - supports-color - dev: true - /mdast-util-to-markdown@0.6.5: - resolution: {integrity: sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==} + mdast-util-to-markdown@0.6.5: dependencies: '@types/unist': 2.0.11 longest-streak: 2.0.4 @@ -4366,73 +5651,48 @@ packages: parse-entities: 2.0.0 repeat-string: 1.6.1 zwitch: 1.0.5 - dev: true - /mdast-util-to-string@2.0.0: - resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} - dev: true - - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + mdast-util-to-string@2.0.0: {} - /methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - dev: false + merge2@1.4.1: {} - /micromark-extension-footnote@0.3.2: - resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==} + micromark-extension-footnote@0.3.2: dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /micromark-extension-frontmatter@0.2.2: - resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==} + micromark-extension-frontmatter@0.2.2: dependencies: fault: 1.0.4 - dev: true - /micromark-extension-gfm-autolink-literal@0.5.7: - resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==} + micromark-extension-gfm-autolink-literal@0.5.7: dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /micromark-extension-gfm-strikethrough@0.6.5: - resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==} + micromark-extension-gfm-strikethrough@0.6.5: dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /micromark-extension-gfm-table@0.4.3: - resolution: {integrity: sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==} + micromark-extension-gfm-table@0.4.3: dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /micromark-extension-gfm-tagfilter@0.3.0: - resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==} - dev: true + micromark-extension-gfm-tagfilter@0.3.0: {} - /micromark-extension-gfm-task-list-item@0.3.3: - resolution: {integrity: sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==} + micromark-extension-gfm-task-list-item@0.3.3: dependencies: micromark: 2.11.4 transitivePeerDependencies: - supports-color - dev: true - /micromark-extension-gfm@0.3.3: - resolution: {integrity: sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==} + micromark-extension-gfm@0.3.3: dependencies: micromark: 2.11.4 micromark-extension-gfm-autolink-literal: 0.5.7 @@ -4442,246 +5702,113 @@ packages: micromark-extension-gfm-task-list-item: 0.3.3 transitivePeerDependencies: - supports-color - dev: true - /micromark@2.11.4: - resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + micromark@2.11.4: dependencies: debug: 4.4.1 parse-entities: 2.0.0 transitivePeerDependencies: - supports-color - dev: true - /micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false + mime-db@1.52.0: {} - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 - dev: false - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: false + mimic-fn@2.1.0: {} - /mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: false + mimic-response@1.0.1: {} - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: false + mimic-response@3.1.0: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 - dev: true - /minimatch@7.4.6: - resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} - engines: {node: '>=10'} + minimatch@7.4.6: dependencies: brace-expansion: 2.0.2 - dev: true - /minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 - dev: true - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minimist@1.2.8: {} - /minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - dev: true + minipass@7.1.2: {} - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: false + mkdirp@1.0.4: {} - /mlly@1.7.4: - resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + mlly@1.7.4: dependencies: acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.1 - dev: true - - /module-details-from-path@1.0.4: - resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - dev: false - - /moment-timezone@0.5.48: - resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} - dependencies: - moment: 2.30.1 - dev: false - - /moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - dev: false - /monocle-ts@2.3.13(fp-ts@2.16.10): - resolution: {integrity: sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==} - peerDependencies: - fp-ts: ^2.5.0 + monocle-ts@2.3.13(fp-ts@2.16.10): dependencies: fp-ts: 2.16.10 - dev: false - - /mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - dev: true - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mri@1.2.0: {} - /msgpack-lite@0.1.26: - resolution: {integrity: sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==} - hasBin: true - dependencies: - event-lite: 0.1.3 - ieee754: 1.2.1 - int64-buffer: 0.1.10 - isarray: 1.0.0 - dev: false + ms@2.1.3: {} - /multimatch@5.0.0: - resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} - engines: {node: '>=10'} + multimatch@5.0.0: dependencies: '@types/minimatch': 3.0.5 array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 minimatch: 3.1.2 - dev: true - /mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - dev: false + mute-stream@0.0.8: {} - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + mz@2.7.0: dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: true - /nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true + nanoid@3.3.11: {} - /napi-postinstall@0.3.2: - resolution: {integrity: sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - dev: true + napi-postinstall@0.3.0: {} - /natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true + natural-compare-lite@1.4.0: {} - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true + natural-compare@1.4.0: {} - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: false + neo-async@2.6.2: {} - /newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13): - resolution: {integrity: sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==} - peerDependencies: - fp-ts: ^2.0.0 - monocle-ts: ^2.0.0 + newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)): dependencies: fp-ts: 2.16.10 monocle-ts: 2.3.13(fp-ts@2.16.10) - dev: false - /no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + no-case@3.0.4: dependencies: lower-case: 2.0.2 tslib: 2.8.1 - dev: false - /node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - dev: false - - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - dev: false - - /node-gyp-build@3.9.0: - resolution: {integrity: sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==} - hasBin: true - dev: false - /node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - dev: false - - /normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - dev: false + normalize-url@6.1.0: {} - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: true + object-assign@4.1.1: {} - /object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - dev: true + object-inspect@1.13.4: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + object-keys@1.1.1: {} - /object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} + object.assign@4.1.7: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -4689,48 +5816,30 @@ packages: es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 - dev: true - /object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} + object.entries@1.1.9: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 - dev: true - /object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} + object.values@1.2.1: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 - dev: true - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - dev: false - - /opentracing@0.14.7: - resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} - engines: {node: '>=0.10'} - dev: false - /optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + optionator@0.9.4: dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -4738,64 +5847,39 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 - dev: true - /os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - dev: false + os-tmpdir@1.0.2: {} - /own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - dev: true - /p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - dev: false + p-cancelable@2.1.1: {} - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - dev: true + package-json-from-dist@1.0.1: {} - /package-manager-detector@1.3.0: - resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} - dev: true + package-manager-detector@1.3.0: {} - /param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + param-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - dev: false - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - /parse-entities@2.0.0: - resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + parse-entities@2.0.0: dependencies: character-entities: 1.2.4 character-entities-legacy: 1.1.4 @@ -4803,266 +5887,124 @@ packages: is-alphanumerical: 1.0.4 is-decimal: 1.0.4 is-hexadecimal: 1.0.4 - dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true - /parse-passwd@1.0.0: - resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} - engines: {node: '>=0.10.0'} - dev: true + parse-passwd@1.0.0: {} - /pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + pascal-case@3.1.2: dependencies: no-case: 3.0.4 tslib: 2.8.1 - dev: false - /path-case@3.0.4: - resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} + path-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - dev: false - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + path-key@3.1.1: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true + path-parse@1.0.7: {} - /path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - dev: true - - /path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - dev: false - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true - /pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - dev: true + path-type@4.0.0: {} - /pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - dev: true + pathe@2.0.3: {} - /pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - dev: true + pathval@2.0.1: {} - /pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} - dev: false + pend@1.2.0: {} - /picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - dev: true + picocolors@1.1.1: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@2.3.1: {} - /picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - dev: true + picomatch@4.0.2: {} - /pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - dev: true + pify@2.3.0: {} - /pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - dev: true + pify@3.0.0: {} - /pinkie-promise@2.0.1: - resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} - engines: {node: '>=0.10.0'} + pinkie-promise@2.0.1: dependencies: pinkie: 2.0.4 - dev: true - /pinkie@2.0.4: - resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} - engines: {node: '>=0.10.0'} - dev: true + pinkie@2.0.4: {} - /pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - dev: true + pirates@4.0.7: {} - /pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 mlly: 1.7.4 pathe: 2.0.3 - dev: true - /please-upgrade-node@3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} + please-upgrade-node@3.2.0: dependencies: semver-compare: 1.0.0 - dev: true - /pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - dev: false + pluralize@8.0.0: {} - /possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - dev: true + possible-typed-array-names@1.1.0: {} - /postcss-load-config@6.0.1(tsx@4.20.3): - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true + postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.20.3): dependencies: lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.6 tsx: 4.20.3 - dev: true - /postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - dev: true - /pprof-format@2.1.0: - resolution: {integrity: sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw==} - dev: false + prelude-ls@1.2.1: {} - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true + prettier@2.8.8: {} - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: true + process-nextick-args@2.0.1: {} - /protobufjs@7.5.3: - resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} - engines: {node: '>=12.0.0'} - requiresBuild: true - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 18.19.121 - long: 5.3.2 - dev: false - - /publint@0.3.12: - resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} - engines: {node: '>=18'} - hasBin: true + publint@0.3.12: dependencies: '@publint/pack': 0.1.2 package-manager-detector: 1.3.0 picocolors: 1.1.1 sade: 1.8.1 - dev: true - /pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 once: 1.4.0 - dev: false - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /query-string@7.0.0: - resolution: {integrity: sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==} - engines: {node: '>=6'} + query-string@7.0.0: dependencies: decode-uri-component: 0.2.2 filter-obj: 1.1.0 split-on-first: 1.1.0 strict-uri-encode: 2.0.0 - dev: false - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-microtask@1.2.3: {} - /quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: false + quick-lru@5.1.1: {} - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -5071,23 +6013,14 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - dev: true - /readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - dev: true + readdirp@4.1.2: {} - /reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -5097,11 +6030,8 @@ packages: get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 - dev: true - /regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -5109,129 +6039,73 @@ packages: get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 - dev: true - /remark-footnotes@3.0.0: - resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==} + remark-footnotes@3.0.0: dependencies: mdast-util-footnote: 0.1.7 micromark-extension-footnote: 0.3.2 transitivePeerDependencies: - supports-color - dev: true - /remark-frontmatter@3.0.0: - resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==} + remark-frontmatter@3.0.0: dependencies: mdast-util-frontmatter: 0.2.0 micromark-extension-frontmatter: 0.2.2 - dev: true - /remark-gfm@1.0.0: - resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==} + remark-gfm@1.0.0: dependencies: mdast-util-gfm: 0.1.2 micromark-extension-gfm: 0.3.3 transitivePeerDependencies: - supports-color - dev: true - /remark-parse@9.0.0: - resolution: {integrity: sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==} + remark-parse@9.0.0: dependencies: mdast-util-from-markdown: 0.8.5 transitivePeerDependencies: - supports-color - dev: true - /repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - dev: true + repeat-string@1.6.1: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /require-package-name@2.0.1: - resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} - dev: true + require-package-name@2.0.1: {} - /resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - dev: false + resolve-alpn@1.2.1: {} - /resolve-dir@1.0.1: - resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} - engines: {node: '>=0.10.0'} + resolve-dir@1.0.1: dependencies: expand-tilde: 2.0.2 global-modules: 1.0.0 - dev: true - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + resolve-from@4.0.0: {} - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + resolve-from@5.0.0: {} - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true + resolve-pkg-maps@1.0.0: {} - /resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true + resolve@1.22.10: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true - /responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + responselike@2.0.1: dependencies: lowercase-keys: 2.0.0 - dev: false - /restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: false - - /retry-as-promised@7.1.1: - resolution: {integrity: sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==} - dev: false - /retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - dev: false - - /reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reusify@1.1.0: {} - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: true - /roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} + roarr@2.15.4: dependencies: boolean: 3.2.0 detect-node: 2.1.0 @@ -5240,201 +6114,92 @@ packages: semver-compare: 1.0.0 sprintf-js: 1.1.3 - /rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + rollup@4.44.2: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 + '@rollup/rollup-android-arm-eabi': 4.44.2 + '@rollup/rollup-android-arm64': 4.44.2 + '@rollup/rollup-darwin-arm64': 4.44.2 + '@rollup/rollup-darwin-x64': 4.44.2 + '@rollup/rollup-freebsd-arm64': 4.44.2 + '@rollup/rollup-freebsd-x64': 4.44.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.2 + '@rollup/rollup-linux-arm-musleabihf': 4.44.2 + '@rollup/rollup-linux-arm64-gnu': 4.44.2 + '@rollup/rollup-linux-arm64-musl': 4.44.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-gnu': 4.44.2 + '@rollup/rollup-linux-riscv64-musl': 4.44.2 + '@rollup/rollup-linux-s390x-gnu': 4.44.2 + '@rollup/rollup-linux-x64-gnu': 4.44.2 + '@rollup/rollup-linux-x64-musl': 4.44.2 + '@rollup/rollup-win32-arm64-msvc': 4.44.2 + '@rollup/rollup-win32-ia32-msvc': 4.44.2 + '@rollup/rollup-win32-x64-msvc': 4.44.2 fsevents: 2.3.3 - dev: true - /run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - dev: false + run-async@2.4.1: {} - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - /rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} + rxjs@6.6.7: dependencies: tslib: 1.14.1 - /sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} + sade@1.8.1: dependencies: mri: 1.2.0 - dev: true - /safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 - dev: true - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: true + safe-buffer@5.1.2: {} - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-buffer@5.2.1: {} - /safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 isarray: 2.0.5 - dev: true - /safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} + safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 - dev: true - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: false + safer-buffer@2.1.2: {} - /seek-bzip@1.0.6: - resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} - hasBin: true + seek-bzip@1.0.6: dependencies: commander: 2.20.3 - dev: true - /semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver-compare@1.0.0: {} - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - dev: true + semver@6.3.1: {} - /semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true + semver@7.7.2: {} - /sentence-case@3.0.4: - resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} + sentence-case@3.0.4: dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case-first: 2.0.2 - dev: false - - /sequelize-mock@0.10.2: - resolution: {integrity: sha512-Vu95by/Bmhcx9PHKlZe+w7/7zw1AycV/SeevxQ5lDokAb50H7Kaf2SkjK5mqKxHWX6y/ICZ8JEfyMOg0nd1M2w==} - dependencies: - bluebird: 3.7.2 - inflection: 1.13.4 - lodash: 4.17.21 - dev: false - - /sequelize-pool@7.1.0: - resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} - engines: {node: '>= 10.0.0'} - dev: false - - /sequelize@6.37.3: - resolution: {integrity: sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==} - engines: {node: '>=10.0.0'} - peerDependencies: - ibm_db: '*' - mariadb: '*' - mysql2: '*' - oracledb: '*' - pg: '*' - pg-hstore: '*' - snowflake-sdk: '*' - sqlite3: '*' - tedious: '*' - peerDependenciesMeta: - ibm_db: - optional: true - mariadb: - optional: true - mysql2: - optional: true - oracledb: - optional: true - pg: - optional: true - pg-hstore: - optional: true - snowflake-sdk: - optional: true - sqlite3: - optional: true - tedious: - optional: true - dependencies: - '@types/debug': 4.1.12 - '@types/validator': 13.15.2 - debug: 4.4.1 - dottie: 2.0.6 - inflection: 1.13.4 - lodash: 4.17.21 - moment: 2.30.1 - moment-timezone: 0.5.48 - pg-connection-string: 2.9.1 - retry-as-promised: 7.1.1 - semver: 7.7.2 - sequelize-pool: 7.1.0 - toposort-class: 1.0.1 - uuid: 8.3.2 - validator: 13.15.15 - wkx: 0.5.0 - transitivePeerDependencies: - - supports-color - dev: false - /serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 - /set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -5442,208 +6207,122 @@ packages: get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 - dev: true - /set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - dev: true - /set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} + set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.1 - dev: true - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + shebang-regex@3.0.0: {} - /shellcheck@3.1.0: - resolution: {integrity: sha512-C6IM1sziNIhCLyVszKZ/mKHQN2/CZ8qZ3sFt8mFOmL0ApoaXLTqyeEVfo+t4MlTVw+hS+kIqSaaGDDrrS0nKBA==} - engines: {node: '>=18.12.0'} - hasBin: true + shellcheck@3.1.0: dependencies: decompress: 4.2.1 envalid: 8.1.0 global-agent: 3.0.0 - dev: true - /side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - dev: true - /side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + side-channel-map@1.0.1: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 - dev: true - /side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} + side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 - dev: true - /side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + side-channel@1.1.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - dev: true - /siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - dev: true + siginfo@2.0.0: {} - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: false + signal-exit@3.0.7: {} - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - dev: true + signal-exit@4.1.0: {} - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + slash@3.0.0: {} - /snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - dev: false - - /source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - dev: true - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: false + source-map-js@1.2.1: {} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: false + source-map@0.6.1: {} - /source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 - dev: true - /spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - dev: true + spdx-exceptions@2.5.0: {} - /spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 - dev: true + spdx-license-ids: 3.0.21 - /spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - dev: true + spdx-license-ids@3.0.21: {} - /split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} - engines: {node: '>=6'} - dev: false + split-on-first@1.1.0: {} - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true + sprintf-js@1.0.3: {} - /sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sprintf-js@1.1.3: {} - /stable-hash@0.0.5: - resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - dev: true + stable-hash@0.0.5: {} - /stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - dev: true + stackback@0.0.2: {} - /std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - dev: true + std-env@3.9.0: {} - /stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - dev: true - /strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - dev: false + strict-uri-encode@2.0.0: {} - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true - /string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 @@ -5652,72 +6331,45 @@ packages: es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 - dev: true - /string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} + string.prototype.trimend@1.0.9: dependencies: call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 - dev: true - /string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.1.1 - dev: true - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 - dev: true - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: ansi-regex: 6.1.0 - dev: true - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true + strip-bom@3.0.0: {} - /strip-dirs@2.1.0: - resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} + strip-dirs@2.1.0: dependencies: is-natural-number: 4.0.1 - dev: true - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 - dev: true - /sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.12 commander: 4.1.1 @@ -5726,22 +6378,14 @@ packages: mz: 2.7.0 pirates: 4.0.7 ts-interface-checker: 0.1.13 - dev: true - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true + supports-preserve-symlinks-flag@1.0.0: {} - /tar-stream@1.6.2: - resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} - engines: {node: '>= 0.8.0'} + tar-stream@1.6.2: dependencies: bl: 1.2.3 buffer-alloc: 1.2.0 @@ -5750,254 +6394,146 @@ packages: readable-stream: 2.3.8 to-buffer: 1.2.1 xtend: 4.0.2 - dev: true - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} + thenify-all@1.6.0: dependencies: thenify: 3.3.1 - dev: true - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thenify@3.3.1: dependencies: any-promise: 1.3.0 - dev: true - /through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + through@2.3.8: {} - /tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - dev: true + tinybench@2.9.0: {} - /tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - dev: true + tinyexec@0.3.2: {} - /tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} + tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - dev: true + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 - /tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - dev: true + tinypool@1.1.1: {} - /tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - dev: true + tinyrainbow@2.0.0: {} - /tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - dev: true + tinyspy@4.0.3: {} - /tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 - dev: false - /to-buffer@1.2.1: - resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} - engines: {node: '>= 0.4'} + to-buffer@1.2.1: dependencies: isarray: 2.0.5 safe-buffer: 5.2.1 typed-array-buffer: 1.0.3 - dev: true - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - /toposort-class@1.0.1: - resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} - dev: false - - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false + tr46@0.0.3: {} - /tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tr46@1.0.1: dependencies: punycode: 2.3.1 - dev: true - /traverse@0.6.11: - resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} - engines: {node: '>= 0.4'} + traverse@0.6.11: dependencies: gopd: 1.2.0 typedarray.prototype.slice: 1.0.5 which-typed-array: 1.1.19 - dev: true - /tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - dev: true + tree-kill@1.2.2: {} - /trough@1.0.5: - resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} - dev: true + trough@1.0.5: {} - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true + ts-interface-checker@0.1.13: {} - /tsconfck@3.1.6(typescript@5.0.4): - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - typescript: 5.0.4 - dev: true + tsconfck@3.1.6(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 - /tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 - dev: true - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@1.14.1: {} - /tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tslib@2.8.1: {} - /tsup@8.5.0(tsx@4.20.3)(typescript@5.0.4): - resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true + tsup@8.5.0(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3): dependencies: - bundle-require: 5.1.0(esbuild@0.25.8) + bundle-require: 5.1.0(esbuild@0.25.6) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 debug: 4.4.1 - esbuild: 0.25.8 + esbuild: 0.25.6 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(tsx@4.20.3) + postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.20.3) resolve-from: 5.0.0 - rollup: 4.46.2 + rollup: 4.44.2 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tree-kill: 1.2.2 - typescript: 5.0.4 + optionalDependencies: + postcss: 8.5.6 + typescript: 5.8.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - dev: true - /tsutils@3.21.0(typescript@5.0.4): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + tsutils@3.21.0(typescript@5.8.3): dependencies: tslib: 1.14.1 - typescript: 5.0.4 - dev: true + typescript: 5.8.3 - /tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} - engines: {node: '>=18.0.0'} - hasBin: true + tsx@4.20.3: dependencies: - esbuild: 0.25.8 + esbuild: 0.25.6 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 - dev: true - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - /type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} + type-fest@0.13.1: {} - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true + type-fest@0.20.2: {} - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - dev: false + type-fest@0.21.3: {} - /typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 - dev: true - /typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 - dev: true - /typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -6006,11 +6542,8 @@ packages: has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - dev: true - /typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} + typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 for-each: 0.3.5 @@ -6018,11 +6551,8 @@ packages: is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - dev: true - /typedarray.prototype.slice@1.0.5: - resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} - engines: {node: '>= 0.4'} + typedarray.prototype.slice@1.0.5: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 @@ -6032,59 +6562,35 @@ packages: math-intrinsics: 1.1.0 typed-array-buffer: 1.0.3 typed-array-byte-offset: 1.0.4 - dev: true - /typescript@5.0.4: - resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} - engines: {node: '>=12.20'} - hasBin: true - dev: true + typescript@5.8.3: {} - /ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - dev: true + ufo@1.6.1: {} - /uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - requiresBuild: true - dev: false + uglify-js@3.19.3: optional: true - /unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - dev: true - /unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + unbzip2-stream@1.4.3: dependencies: buffer: 5.7.1 through: 2.3.8 - dev: true - /underscore@1.13.7: - resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - dev: true + underscore@1.13.7: {} - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@5.26.5: {} - /undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} + undici@5.29.0: dependencies: '@fastify/busboy': 2.1.1 - dev: false - /unified@9.2.2: - resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + unified@9.2.2: dependencies: '@types/unist': 2.0.11 bail: 1.0.5 @@ -6093,114 +6599,77 @@ packages: is-plain-obj: 2.1.0 trough: 1.0.5 vfile: 4.2.1 - dev: true - /unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - dev: true + unist-util-is@4.1.0: {} - /unist-util-stringify-position@2.0.3: - resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + unist-util-stringify-position@2.0.3: dependencies: '@types/unist': 2.0.11 - dev: true - /unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} + unist-util-visit-parents@3.1.1: dependencies: '@types/unist': 2.0.11 unist-util-is: 4.1.0 - dev: true - /unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - requiresBuild: true + unrs-resolver@1.11.0: dependencies: - napi-postinstall: 0.3.2 + napi-postinstall: 0.3.0 optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - dev: true - - /update-section@0.3.3: - resolution: {integrity: sha512-BpRZMZpgXLuTiKeiu7kK0nIPwGdyrqrs6EDSaXtjD/aQ2T+qVo9a5hRC3HN3iJjCMxNT/VxoLGQ7E/OzE5ucnw==} - dev: true - - /upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} + '@unrs/resolver-binding-android-arm-eabi': 1.11.0 + '@unrs/resolver-binding-android-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-x64': 1.11.0 + '@unrs/resolver-binding-freebsd-x64': 1.11.0 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.0 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.0 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-musl': 1.11.0 + '@unrs/resolver-binding-wasm32-wasi': 1.11.0 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.0 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.0 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.0 + + update-section@0.3.3: {} + + upper-case-first@2.0.2: dependencies: tslib: 2.8.1 - dev: false - /upper-case@2.0.2: - resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} + upper-case@2.0.2: dependencies: tslib: 2.8.1 - dev: false - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true - - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: false - - /validator@13.15.15: - resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} - engines: {node: '>= 0.10'} - dev: false + util-deprecate@1.0.2: {} - /vfile-message@2.0.4: - resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} + vfile-message@2.0.4: dependencies: '@types/unist': 2.0.11 unist-util-stringify-position: 2.0.3 - dev: true - /vfile@4.2.1: - resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} + vfile@4.2.1: dependencies: '@types/unist': 2.0.11 is-buffer: 2.0.5 unist-util-stringify-position: 2.0.3 vfile-message: 2.0.4 - dev: true - /vite-node@3.2.4(@types/node@18.19.121)(tsx@4.20.3): - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true + vite-node@3.2.4(@types/node@18.19.115)(tsx@4.20.3): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) + vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) transitivePeerDependencies: - '@types/node' - jiti @@ -6214,128 +6683,58 @@ packages: - terser - tsx - yaml - dev: true - /vite-tsconfig-paths@5.1.4(typescript@5.0.4): - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)): dependencies: debug: 4.4.1 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.0.4) + tsconfck: 3.1.6(typescript@5.8.3) + optionalDependencies: + vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) transitivePeerDependencies: - supports-color - typescript - dev: true - /vite@7.0.6(@types/node@18.19.121)(tsx@4.20.3): - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true + vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3): dependencies: - '@types/node': 18.19.121 - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 + esbuild: 0.25.6 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 postcss: 8.5.6 - rollup: 4.46.2 + rollup: 4.44.2 tinyglobby: 0.2.14 - tsx: 4.20.3 optionalDependencies: + '@types/node': 18.19.115 fsevents: 2.3.3 - dev: true + tsx: 4.20.3 - /vitest@3.2.4(@types/node@18.19.121)(tsx@4.20.3): - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true + vitest@3.2.4(@types/node@18.19.115)(tsx@4.20.3): dependencies: '@types/chai': 5.2.2 - '@types/node': 18.19.121 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.6) + '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@18.19.115)(tsx@4.20.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.1 + chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.2 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.6(@types/node@18.19.121)(tsx@4.20.3) - vite-node: 3.2.4(@types/node@18.19.121)(tsx@4.20.3) + vite: 7.0.2(@types/node@18.19.115)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@18.19.115)(tsx@4.20.3) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 18.19.115 transitivePeerDependencies: - jiti - less @@ -6349,45 +6748,31 @@ packages: - terser - tsx - yaml - dev: true - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false + webidl-conversions@3.0.1: {} - /webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: true + webidl-conversions@4.0.2: {} - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: false - /whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 tr46: 1.0.1 webidl-conversions: 4.0.2 - dev: true - /which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 is-boolean-object: 1.2.2 is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 - dev: true - /which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} + which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 function.prototype.name: 1.1.8 @@ -6402,21 +6787,15 @@ packages: which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.19 - dev: true - /which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} + which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.4 - dev: true - /which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} + which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -6425,96 +6804,49 @@ packages: get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 - dev: true - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true + which@1.3.1: dependencies: isexe: 2.0.0 - dev: true - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - dev: true - /why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - dev: true - - /wkx@0.5.0: - resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} - dependencies: - '@types/node': 18.19.121 - dev: false - /word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - dev: true + word-wrap@1.2.5: {} - /wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - dev: false + wordwrap@1.0.0: {} - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrappy@1.0.2: {} - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true + xtend@4.0.2: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true + y18n@5.0.8: {} - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: true + yaml@1.10.2: {} - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true + yargs-parser@20.2.9: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: false + yargs-parser@21.1.1: {} - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} + yargs@16.2.0: dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -6523,19 +6855,12 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 - dev: true - /yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - dev: true - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + yocto-queue@0.1.0: {} - /zwitch@1.0.5: - resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} - dev: true + zwitch@1.0.5: {} From 720d92cc3b2fde4d25a61643260a52496bf0dd41 Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 7 Aug 2025 05:58:46 +0000 Subject: [PATCH 04/72] clean up --- .../consent/upload-preferences/command.ts | 2 +- .../parsePreferenceIdentifiersFromCsv.ts | 24 ++++++++++++- .../parsePreferenceManagementCsv.ts | 35 ++++++++----------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index bdad578f..016c9b9e 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -97,7 +97,7 @@ export const uploadPreferencesCommand = buildCommand({ kind: 'parsed', parse: numberParser, brief: 'The concurrency to use when uploading in parallel', - default: '1', + default: '10', }, allowedIdentifierNames: { kind: 'parsed', diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index ceec16de..a0e2217e 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -170,7 +170,7 @@ export async function parsePreferenceIdentifiersFromCsv( * * @param options - Options * @param options.row - The current row from CSV file - * @param options.currentState - The current file metadata state + * @param options.columnToIdentifier - The column to identifier mapping metadata * @returns The updated preferences with identifiers payload */ export function getPreferenceIdentifiersFromRow({ @@ -189,3 +189,25 @@ export function getPreferenceIdentifiersFromRow({ value: row[col], })); } + +/** + * Helper function to get unique identifier name present in a row + * + * @param options - Options + * @param options.row - The current row from CSV file + * @param options.columnToIdentifier - The column to identifier mapping metadata + * @returns The unique identifier names present in the row + */ +export function getUniquePreferenceIdentifierNamesFromRow({ + row, + columnToIdentifier, +}: { + /** The current row from CSV file */ + row: Record; + /** The current file metadata state */ + columnToIdentifier: FileMetadataState['columnToIdentifier']; +}): string[] { + return Object.keys(columnToIdentifier).filter( + (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, + ); +} diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 728d590a..f9d6e8f5 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -10,7 +10,10 @@ import { getPreferencesForIdentifiers } from './getPreferencesForIdentifiers'; import { PreferenceTopic, type Identifier } from '../graphql'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { parsePreferenceTimestampsFromCsv } from './parsePreferenceTimestampsFromCsv'; -import { parsePreferenceIdentifiersFromCsv } from './parsePreferenceIdentifiersFromCsv'; +import { + getUniquePreferenceIdentifierNamesFromRow, + parsePreferenceIdentifiersFromCsv, +} from './parsePreferenceIdentifiersFromCsv'; import { parsePreferenceAndPurposeValuesFromCsv } from './parsePreferenceAndPurposeValuesFromCsv'; import { checkIfPendingPreferenceUpdatesAreNoOp } from './checkIfPendingPreferenceUpdatesAreNoOp'; import { checkIfPendingPreferenceUpdatesCauseConflict } from './checkIfPendingPreferenceUpdatesCauseConflict'; @@ -140,17 +143,13 @@ export async function parsePreferenceManagementCsvWithCache( // Grab existing preference store records const identifiers = preferences.flatMap((pref) => - Object.keys(currentState.columnToIdentifier) - .filter( - (col) => - pref[col] && - currentState.columnToIdentifier[col] && - currentState.columnToIdentifier[col].isUniqueOnPreferenceStore, - ) - .map((col) => ({ - name: currentState.columnToIdentifier[col].name, - value: pref[col], - })), + getUniquePreferenceIdentifierNamesFromRow({ + row: pref, + columnToIdentifier: currentState.columnToIdentifier, + }).map((col) => ({ + name: currentState.columnToIdentifier[col].name, + value: pref[col], + })), ); const existingConsentRecords = skipExistingRecordCheck @@ -169,14 +168,10 @@ export async function parsePreferenceManagementCsvWithCache( // Process each row preferences.forEach((pref) => { // Get the userIds that could be the primary key of the consent record - const possiblePrimaryKeys = Object.keys(currentState.columnToIdentifier) - .filter( - (col) => - pref[col] && - currentState.columnToIdentifier[col] && - currentState.columnToIdentifier[col].isUniqueOnPreferenceStore, - ) - .map((col) => pref[col]); + const possiblePrimaryKeys = getUniquePreferenceIdentifierNamesFromRow({ + row: pref, + columnToIdentifier: currentState.columnToIdentifier, + }).map((col) => pref[col]); // determine updates for user const pendingUpdates = getPreferenceUpdatesFromRow({ From 7543ac452666995fa7a26afc0070c2478437b247 Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 7 Aug 2025 22:32:44 +0000 Subject: [PATCH 05/72] add costco specific logic --- .../cli-upload-preferences-example.csv | 8 +-- .../cli-upload-preferences-example2.csv | 8 +-- .../parsePreferenceIdentifiersFromCsv.ts | 52 ++++++++++++++++--- .../parsePreferenceManagementCsv.ts | 4 ++ 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/examples/pm-test/cli-upload-preferences-example.csv b/examples/pm-test/cli-upload-preferences-example.csv index 6c267757..59eec1d0 100644 --- a/examples/pm-test/cli-upload-preferences-example.csv +++ b/examples/pm-test/cli-upload-preferences-example.csv @@ -1,5 +1,5 @@ email_id,person_id,member_id,timestamp,Sales -test-acme1@gmail.com,p1,m1,2025-08-07T05:09:40.317Z,true -test-acme2@gmail.com,p2,m2,2025-08-07T17:49:21.192Z,true -test-acme3@gmail.com,p3,m3,2025-08-07T18:49:21.192Z,false -test-acme4@gmail.com,p4,m4,2025-08-07T17:49:21.192Z,false +test-acme10@gmail.com,p1,m1,2025-08-07T05:09:40.317Z,true +test-acme11@gmail.com,p2,m2,2025-08-07T17:49:21.192Z,true +test-acme12@gmail.com,p3,m3,2025-08-07T18:49:21.192Z,false +test-acme13@gmail.com,p4,m4,2025-08-07T17:49:21.192Z,false diff --git a/examples/pm-test/cli-upload-preferences-example2.csv b/examples/pm-test/cli-upload-preferences-example2.csv index 2a61f2a9..5530dad5 100644 --- a/examples/pm-test/cli-upload-preferences-example2.csv +++ b/examples/pm-test/cli-upload-preferences-example2.csv @@ -1,5 +1,5 @@ email_id,person_id,member_id,timestamp,Sales -test-acme5@gmail.com,p5,m5,2025-08-07T05:09:40.317Z,true -test-acme6@gmail.com,p6,m6,2025-08-07T17:49:21.192Z,true -test-acme7@gmail.com,p7,m7,2025-08-07T18:49:21.192Z,false -test-acme8@gmail.com,p8,m8,2025-08-07T17:49:21.192Z,false +,p5,m5,2025-08-07T05:09:40.317Z,true +test-acme14@gmail.com,p6,m6,2025-08-07T17:49:21.192Z,true +test-acme15@gmail.com,p7,m7,2025-08-07T18:49:21.192Z,false +test-acme16@gmail.com,p8,m8,2025-08-07T17:49:21.192Z,false diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index a0e2217e..081b95a1 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -182,12 +182,25 @@ export function getPreferenceIdentifiersFromRow({ /** The current file metadata state */ columnToIdentifier: FileMetadataState['columnToIdentifier']; }): PreferenceStoreIdentifier[] { - return Object.entries(columnToIdentifier) - .filter(([col]) => !!row[col]) - .map(([col, identifierMapping]) => ({ - name: identifierMapping.name, - value: row[col], - })); + // TODO: Remove this COSTCO specific logic + const emailColumn = Object.keys(columnToIdentifier).find((x) => + x.includes('email'), + ); + if (!emailColumn) { + throw new Error('Email column not found in csv file.'); + } + return ( + Object.entries(columnToIdentifier) + .filter(([col]) => !!row[col]) + // TODO: Remove this COSTCO specific logic + .filter( + ([col]) => !(col === 'transcendID' && row[col] && row[emailColumn]), + ) + .map(([col, identifierMapping]) => ({ + name: identifierMapping.name, + value: row[col], + })) + ); } /** @@ -211,3 +224,30 @@ export function getUniquePreferenceIdentifierNamesFromRow({ (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, ); } + +/** + * Add Transcend ID to preferences if email_id is present + * + * @param preferences - List of preferences + * @returns The updated preferences with Transcend ID added + * // TODO: Remove this COSTCO specific logic + */ +export async function addTranscendIdToPreferences( + preferences: Record[], +): Promise[]> { + const haveTranscendId = await inquirerConfirmBoolean({ + message: 'Would you like transcendID for costco upload?', + }); + if (!haveTranscendId) { + logger.info(colors.yellow('Skipping adding Transcend ID to preferences.')); + return preferences; + } + + // Add a transcendent ID to each preference if it doesn't already exist + return preferences.map((pref) => { + if (!pref.person_id) { + throw new Error('person_id is required for this upload.'); + } + return { ...pref, transcendID: pref.person_id }; + }); +} diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index f9d6e8f5..8e0fd167 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -11,6 +11,7 @@ import { PreferenceTopic, type Identifier } from '../graphql'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { parsePreferenceTimestampsFromCsv } from './parsePreferenceTimestampsFromCsv'; import { + addTranscendIdToPreferences, getUniquePreferenceIdentifierNamesFromRow, parsePreferenceIdentifiersFromCsv, } from './parsePreferenceIdentifiersFromCsv'; @@ -91,6 +92,9 @@ export async function parsePreferenceManagementCsvWithCache( logger.info(colors.magenta(`Reading in file: "${file}"`)); let preferences = readCsv(file, t.record(t.string, t.string)); + // TODO: Remove this COSTCO specific logic + const updatedPreferences = await addTranscendIdToPreferences(preferences); + preferences = updatedPreferences; // start building the cache, can use previous cache as well let currentState: FileMetadataState = { columnToPurposeName: oldFileMetadata?.columnToPurposeName || {}, From 2549be4a2fdeac19b60eaa12889344a27f5e00bf Mon Sep 17 00:00:00 2001 From: Girish J Date: Tue, 12 Aug 2025 22:07:51 +0000 Subject: [PATCH 06/72] add columnsToIgnore --- examples/pm-test/cli-upload-preferences-example.csv | 10 +++++----- examples/pm-test/cli-upload-preferences-example2.csv | 10 +++++----- src/commands/consent/upload-preferences/command.ts | 7 +++++++ src/commands/consent/upload-preferences/impl.ts | 3 +++ .../parsePreferenceAndPurposeValuesFromCsv.ts | 4 ++++ .../parsePreferenceManagementCsv.ts | 4 ++++ ...uploadPreferenceManagementPreferencesInteractive.ts | 4 ++++ 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/examples/pm-test/cli-upload-preferences-example.csv b/examples/pm-test/cli-upload-preferences-example.csv index 59eec1d0..97bb1b96 100644 --- a/examples/pm-test/cli-upload-preferences-example.csv +++ b/examples/pm-test/cli-upload-preferences-example.csv @@ -1,5 +1,5 @@ -email_id,person_id,member_id,timestamp,Sales -test-acme10@gmail.com,p1,m1,2025-08-07T05:09:40.317Z,true -test-acme11@gmail.com,p2,m2,2025-08-07T17:49:21.192Z,true -test-acme12@gmail.com,p3,m3,2025-08-07T18:49:21.192Z,false -test-acme13@gmail.com,p4,m4,2025-08-07T17:49:21.192Z,false +email_id,person_id,member_id,timestamp,Sales,source_system +test-acme10@gmail.com,p1,m1,2025-08-07T05:09:40.317Z,true, +test-acme11@gmail.com,p2,m2,2025-08-07T17:49:21.192Z,true, +test-acme12@gmail.com,p3,m3,2025-08-07T18:49:21.192Z,false, +test-acme13@gmail.com,p4,m4,2025-08-07T17:49:21.192Z,false, diff --git a/examples/pm-test/cli-upload-preferences-example2.csv b/examples/pm-test/cli-upload-preferences-example2.csv index 5530dad5..f0241406 100644 --- a/examples/pm-test/cli-upload-preferences-example2.csv +++ b/examples/pm-test/cli-upload-preferences-example2.csv @@ -1,5 +1,5 @@ -email_id,person_id,member_id,timestamp,Sales -,p5,m5,2025-08-07T05:09:40.317Z,true -test-acme14@gmail.com,p6,m6,2025-08-07T17:49:21.192Z,true -test-acme15@gmail.com,p7,m7,2025-08-07T18:49:21.192Z,false -test-acme16@gmail.com,p8,m8,2025-08-07T17:49:21.192Z,false +email_id,person_id,member_id,timestamp,Sales,source_system +,p5,m5,2025-08-07T05:09:40.317Z,true, +test-acme14@gmail.com,p6,m6,2025-08-07T17:49:21.192Z,true, +test-acme15@gmail.com,p7,m7,2025-08-07T18:49:21.192Z,false, +test-acme16@gmail.com,p8,m8,2025-08-07T17:49:21.192Z,false, diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 016c9b9e..322838cc 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -111,6 +111,13 @@ export const uploadPreferencesCommand = buildCommand({ brief: 'Columns in the CSV that should be used as identifiers. Comma-separated list of column names.', }, + columnsToIgnore: { + kind: 'parsed', + parse: (value: string) => value.split(',').map((s) => s.trim()), + brief: + 'Columns in the CSV that should be ignored. Comma-separated list of column names.', + optional: true, + }, }, }, docs: { diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 4cb4e61a..4a498858 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -28,6 +28,7 @@ export interface UploadPreferencesCommandFlags { concurrency: number; allowedIdentifierNames: string[]; identifierColumns: string[]; + columnsToIgnore?: string[]; } export async function uploadPreferences( @@ -50,6 +51,7 @@ export async function uploadPreferences( concurrency, allowedIdentifierNames, identifierColumns, + columnsToIgnore = [], }: UploadPreferencesCommandFlags, ): Promise { if (!!directory && !!file) { @@ -151,6 +153,7 @@ export async function uploadPreferences( allowedIdentifierNames, identifierColumns, oldReceiptFilepath, + columnsToIgnore, }); }, { concurrency }, diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index f1ed9a53..8ed82f3e 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -25,6 +25,7 @@ export async function parsePreferenceAndPurposeValuesFromCsv( purposeSlugs, preferenceTopics, forceTriggerWorkflows, + columnsToIgnore, }: { /** The purpose slugs that are allowed to be updated */ purposeSlugs: string[]; @@ -32,6 +33,8 @@ export async function parsePreferenceAndPurposeValuesFromCsv( preferenceTopics: PreferenceTopic[]; /** Force workflow triggers */ forceTriggerWorkflows: boolean; + /** Columns to ignore in the CSV file */ + columnsToIgnore: string[]; }, ): Promise { // Determine columns to map @@ -41,6 +44,7 @@ export async function parsePreferenceAndPurposeValuesFromCsv( const otherColumns = difference(columnNames, [ ...Object.keys(currentState.columnToIdentifier), ...(currentState.timestampColumn ? [currentState.timestampColumn] : []), + ...columnsToIgnore, ]); if (otherColumns.length === 0) { if (forceTriggerWorkflows) { diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 8e0fd167..699877cf 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -40,6 +40,7 @@ export async function parsePreferenceManagementCsvWithCache( allowedIdentifierNames, oldReceiptFilepath, identifierColumns, + columnsToIgnore, }: { /** File to parse */ file: string; @@ -63,6 +64,8 @@ export async function parsePreferenceManagementCsvWithCache( oldReceiptFilepath?: string; /** Identifier columns on the CSV file */ identifierColumns: string[]; + /** Columns to ignore in the CSV file */ + columnsToIgnore: string[]; }, cache: PersistedState, ): Promise { @@ -140,6 +143,7 @@ export async function parsePreferenceManagementCsvWithCache( preferenceTopics, purposeSlugs, forceTriggerWorkflows, + columnsToIgnore, }, ); fileMetadata[file] = currentState; diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 5a71d575..5511c8d1 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -44,6 +44,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ forceTriggerWorkflows = false, allowedIdentifierNames, identifierColumns, + columnsToIgnore = [], }: { /** The Transcend API key */ auth: string; @@ -80,6 +81,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ allowedIdentifierNames: string[]; /** identifier columns on the CSV file */ identifierColumns: string[]; + /** Columns to ignore in the CSV file */ + columnsToIgnore: string[]; }): Promise { // Parse out the extra attributes to apply to all requests uploaded const parsedAttributes = parseAttributesFromString(attributes); @@ -142,6 +145,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ allowedIdentifierNames, oldReceiptFilepath, identifierColumns, + columnsToIgnore, }, preferenceState, ); From 69bfa8e23261a2540ca1cfcbb7d443071d7dc9af Mon Sep 17 00:00:00 2001 From: Girish J Date: Wed, 13 Aug 2025 16:10:53 +0000 Subject: [PATCH 07/72] remove transcendID prompt --- .../parsePreferenceIdentifiersFromCsv.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 081b95a1..cc8f524d 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -235,14 +235,13 @@ export function getUniquePreferenceIdentifierNamesFromRow({ export async function addTranscendIdToPreferences( preferences: Record[], ): Promise[]> { - const haveTranscendId = await inquirerConfirmBoolean({ - message: 'Would you like transcendID for costco upload?', - }); - if (!haveTranscendId) { - logger.info(colors.yellow('Skipping adding Transcend ID to preferences.')); - return preferences; - } - + // const haveTranscendId = await inquirerConfirmBoolean({ + // message: 'Would you like transcendID for costco upload?', + // }); + // if (!haveTranscendId) { + // logger.info(colors.yellow('Skipping adding Transcend ID to preferences.')); + // return preferences; + // } // Add a transcendent ID to each preference if it doesn't already exist return preferences.map((pref) => { if (!pref.person_id) { From 95443275df61f4c3b23fe98a6fb814b37d09493e Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 14 Aug 2025 00:30:38 +0000 Subject: [PATCH 08/72] try increasing concurrencey --- .../uploadPreferenceManagementPreferencesInteractive.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 5511c8d1..a4e3768c 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -251,7 +251,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ // Build a GraphQL client let total = 0; const updatesToRun = Object.entries(pendingUpdates); - const chunkedUpdates = chunk(updatesToRun, skipWorkflowTriggers ? 100 : 10); + const chunkedUpdates = chunk(updatesToRun, skipWorkflowTriggers ? 50 : 10); progressBar.start(updatesToRun.length, 0); await map( chunkedUpdates, @@ -300,7 +300,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ progressBar.update(total); }, { - concurrency: 40, + concurrency: 80, }, ); From 1a5c785f4ded2e2bbeb12774202775f3dcd5fbf9 Mon Sep 17 00:00:00 2001 From: Girish J Date: Thu, 14 Aug 2025 16:01:32 +0000 Subject: [PATCH 09/72] revert to bluebird --- package.json | 6 ++++-- src/commands/consent/upload-preferences/command.ts | 2 +- .../uploadPreferenceManagementPreferencesInteractive.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 82da6ac3..b19a687d 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,8 @@ "query-string": "=7.0.0", "semver": "^7.6.0", "undici": "^5.22.1", - "yargs-parser": "^21.1.1" + "yargs-parser": "^21.1.1", + "bluebird": "^3.7.2" }, "devDependencies": { "@types/JSONStream": "npm:@types/jsonstream@^0.8.33", @@ -165,7 +166,8 @@ "tsx": "^4.20.3", "typescript": "^5.0.4", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4" + "vitest": "^3.2.4", + "@types/bluebird": "^3.5.38" }, "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184" } diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 322838cc..8f0263f6 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -97,7 +97,7 @@ export const uploadPreferencesCommand = buildCommand({ kind: 'parsed', parse: numberParser, brief: 'The concurrency to use when uploading in parallel', - default: '10', + default: '1', }, allowedIdentifierNames: { kind: 'parsed', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index a4e3768c..f8843313 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -8,7 +8,7 @@ import { fetchAllIdentifiers, } from '../graphql'; import colors from 'colors'; -import { map } from '../bluebird-replace'; +import { map } from 'bluebird'; import { chunk } from 'lodash-es'; import { logger } from '../../logger'; import cliProgress from 'cli-progress'; From 2de8843ab70711e3717581c2fbed59010c19ecaf Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 15 Aug 2025 17:24:33 -0700 Subject: [PATCH 10/72] Rewrites how receipts are stored to file --- CHANGELOG.md | 5 + package.json | 2 +- pnpm-lock.yaml | 16 +++ .../consent/upload-preferences/command.ts | 13 +- .../consent/upload-preferences/impl.ts | 54 ++++++-- src/lib/preference-management/codecs.ts | 53 ++++---- src/lib/preference-management/index.ts | 2 +- .../parsePreferenceAndPurposeValuesFromCsv.ts | 28 ++--- ...ts => parsePreferenceFileFormatFromCsv.ts} | 43 ++++--- .../parsePreferenceIdentifiersFromCsv.ts | 45 ++++--- .../parsePreferenceManagementCsv.ts | 118 ++++++------------ ...ferenceManagementPreferencesInteractive.ts | 78 +++++++----- 12 files changed, 248 insertions(+), 209 deletions(-) rename src/lib/preference-management/{parsePreferenceTimestampsFromCsv.ts => parsePreferenceFileFormatFromCsv.ts} (61%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3879dd8a..ac453ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## Table of Contents - [Changelog](#changelog) + - [[8.0.0] - 2025-08-15](#800---2025-08-15) - [[7.1.0] - 2025-08-05](#710---2025-08-05) - [Added](#added) - [[7.0.3] - 2025-07-29](#703---2025-07-29) @@ -27,6 +28,10 @@ All notable changes to the Transcend CLI tools will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [8.0.0] - 2025-08-15 + +FIXME + ## [7.1.0] - 2025-08-05 ### Added diff --git a/package.json b/package.json index b19a687d..e65d5a26 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Transcend Inc.", "name": "@transcend-io/cli", "description": "A command line interface for programmatic operations across Transcend.", - "version": "7.1.0", + "version": "8.0.0", "homepage": "https://github.com/transcend-io/cli", "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5ac3fd7..c1f7a79c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: JSONStream: specifier: ^1.3.5 version: 1.3.5 + bluebird: + specifier: ^3.7.2 + version: 3.7.2 cli-progress: specifier: ^3.11.2 version: 3.12.0 @@ -114,6 +117,9 @@ importers: '@types/JSONStream': specifier: npm:@types/jsonstream@^0.8.33 version: '@types/jsonstream@0.8.33' + '@types/bluebird': + specifier: ^3.5.38 + version: 3.5.42 '@types/cli-progress': specifier: ^3.11.0 version: 3.11.6 @@ -654,6 +660,9 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/bluebird@3.5.42': + resolution: {integrity: sha512-Jhy+MWRlro6UjVi578V/4ZGNfeCOcNCp0YaFNIUGFKlImowqwb1O/22wDVk3FDGMLqxdpOV3qQHD5fPEH4hK6A==} + '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} @@ -1057,6 +1066,9 @@ packages: bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -3759,6 +3771,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/bluebird@3.5.42': {} + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 @@ -4184,6 +4198,8 @@ snapshots: readable-stream: 2.3.8 safe-buffer: 5.2.1 + bluebird@3.7.2: {} + boolean@3.2.0: {} brace-expansion@1.1.12: diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 8f0263f6..ea2a53b1 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -54,8 +54,17 @@ export const uploadPreferencesCommand = buildCommand({ receiptFileDir: { kind: 'parsed', parse: String, - brief: 'Directory path where the response receipts should be saved', - default: './receipts', + brief: + 'Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided.', + optional: true, + }, + schemaFilePath: { + kind: 'parsed', + parse: String, + brief: + 'The path to where the schema for the file should be saved. If file is provided, it will default to ./-preference-upload-schema.json ' + + 'If directory is provided, it will default to /../preference-upload-schema.json', + optional: true, }, skipWorkflowTriggers: { kind: 'boolean', diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 4a498858..befd9b2a 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -18,7 +18,8 @@ export interface UploadPreferencesCommandFlags { directory?: string; dryRun: boolean; skipExistingRecordCheck: boolean; - receiptFileDir: string; + receiptFileDir?: string; + schemaFilePath?: string; skipWorkflowTriggers: boolean; forceTriggerWorkflows: boolean; skipConflictUpdates: boolean; @@ -31,6 +32,16 @@ export interface UploadPreferencesCommandFlags { columnsToIgnore?: string[]; } +/** + * Get the file prefix from the file name + * + * @param file - The file name + * @returns The file prefix + */ +function getFilePrefix(file: string): string { + return basename(file).replace('.csv', ''); +} + export async function uploadPreferences( this: LocalContext, { @@ -43,6 +54,7 @@ export async function uploadPreferences( dryRun, skipExistingRecordCheck, receiptFileDir, + schemaFilePath, skipWorkflowTriggers, forceTriggerWorkflows, skipConflictUpdates, @@ -125,19 +137,38 @@ export async function uploadPreferences( ); } + // Determine receipts folder + const receiptsFolder = + receiptFileDir || + (directory ? join(directory, '../receipts') : './receipts'); + + // Determine the schema file + const schemaFile = + schemaFilePath || + (directory + ? join(directory, '../preference-upload-schema.json') + : `${getFilePrefix(files[0])}-preference-upload-schema.json`); + + // yarn ts-node ./src/cli-chunk-csv.ts --inputFile=$file + // Create folder of 1200 chunks in ./working/costco/udp/all-chunks + // Copy over 100 files from all-chunks -> pending-chunks + // Run pnpm start consent upload-preferences --auth=$API_KEY --partition=448b3320-9d7c-499a-bc56-f0dae33c8f5c --directory=./working/costco/udp/pending-chunks --dryRun=false --skipWorkflowTriggers=true --skipExistingRecordCheck=true --isSilent=true --attributes="Tags:transcend-cli,Source:transcend-cli" --transcendUrl=https://api.us.transcend.io/ --allowedIdentifierNames="email,personID,memberID,transcendID,birthDate" --identifierColumns="email_address,person_id,member_id,transcendID,birth_dt" --columnsToIgnore="source_system,mktg_consent_ts" --sombraAuth=$SOMBRA_AUTH --concurrency=1 + // Writes each of 1200 files to ./receipts/-receipts.json -> currently there are 300 + + // FIXME auto splitting + // FIXME: use single tenant sombra + // FIXME add overview of status + // FIXME handle re-processing of same file, and error handling + await map( files, - async (filePath, index) => { - const fileName = basename(filePath).replace('.csv', ''); - const oldReceiptFilepath = - index > 0 - ? join( - receiptFileDir, - `${basename(files[0]).replace('.csv', '')}-receipts.json`, - ) - : undefined; + async (filePath) => { await uploadPreferenceManagementPreferencesInteractive({ - receiptFilepath: join(receiptFileDir, `${fileName}-receipts.json`), + receiptFilepath: join( + receiptsFolder, + `${getFilePrefix(filePath)}-receipts.json`, + ), + schemaFilePath: schemaFile, auth, sombraAuth, file: filePath, @@ -152,7 +183,6 @@ export async function uploadPreferences( forceTriggerWorkflows, allowedIdentifierNames, identifierColumns, - oldReceiptFilepath, columnsToIgnore, }); }, diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index 514fc80a..dc9b0dcc 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -42,7 +42,7 @@ export const PurposeRowMapping = t.type({ /** Override type */ export type PurposeRowMapping = t.TypeOf; -export const FileMetadataState = t.intersection([ +export const FileFormatState = t.intersection([ t.type({ /** * Definition of how to map each column in the CSV to @@ -51,27 +51,6 @@ export const FileMetadataState = t.intersection([ columnToPurposeName: t.record(t.string, PurposeRowMapping), /** Last time the file was last parsed at */ lastFetchedAt: t.string, - /** - * Mapping of userId to the rows in the file that need to be uploaded - * These uploads are overwriting non-existent preferences and are safe - */ - pendingSafeUpdates: t.record(t.string, t.record(t.string, t.string)), - /** - * Mapping of userId to the rows in the file that need to be uploaded - * these records have conflicts with existing consent preferences - */ - pendingConflictUpdates: t.record( - t.string, - t.type({ - record: PreferenceQueryResponseItem, - row: t.record(t.string, t.string), - }), - ), - /** - * Mapping of userId to the rows in the file that can be skipped because - * their preferences are already in the store - */ - skippedUpdates: t.record(t.string, t.record(t.string, t.string)), /** The column name that maps to the identifier */ columnToIdentifier: t.record( t.string, @@ -90,14 +69,32 @@ export const FileMetadataState = t.intersection([ ]); /** Override type */ -export type FileMetadataState = t.TypeOf; +export type FileFormatState = t.TypeOf; -/** Persist this data between runs of the script */ -export const PreferenceState = t.type({ +export const RequestUploadReceipts = t.type({ + /** Last time the file was last parsed at */ + lastFetchedAt: t.string, + /** + * Mapping of userId to the rows in the file that need to be uploaded + * These uploads are overwriting non-existent preferences and are safe + */ + pendingSafeUpdates: t.record(t.string, t.record(t.string, t.string)), + /** + * Mapping of userId to the rows in the file that need to be uploaded + * these records have conflicts with existing consent preferences + */ + pendingConflictUpdates: t.record( + t.string, + t.type({ + record: PreferenceQueryResponseItem, + row: t.record(t.string, t.string), + }), + ), /** - * Store a cache of previous files read in + * Mapping of userId to the rows in the file that can be skipped because + * their preferences are already in the store */ - fileMetadata: t.record(t.string, FileMetadataState), + skippedUpdates: t.record(t.string, t.record(t.string, t.string)), /** * The set of successful uploads to Transcend * Mapping from userId to the upload metadata @@ -121,4 +118,4 @@ export const PreferenceState = t.type({ }); /** Override type */ -export type PreferenceState = t.TypeOf; +export type RequestUploadReceipts = t.TypeOf; diff --git a/src/lib/preference-management/index.ts b/src/lib/preference-management/index.ts index 1f914460..36c72522 100644 --- a/src/lib/preference-management/index.ts +++ b/src/lib/preference-management/index.ts @@ -5,7 +5,7 @@ export * from './parsePreferenceManagementCsv'; export * from './getPreferenceUpdatesFromRow'; export * from './parsePreferenceManagementCsv'; export * from './parsePreferenceIdentifiersFromCsv'; -export * from './parsePreferenceTimestampsFromCsv'; +export * from './parsePreferenceFileFormatFromCsv'; export * from './parsePreferenceAndPurposeValuesFromCsv'; export * from './checkIfPendingPreferenceUpdatesAreNoOp'; export * from './checkIfPendingPreferenceUpdatesCauseConflict'; diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index 8ed82f3e..5451db5c 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -1,26 +1,25 @@ import { uniq, difference } from 'lodash-es'; import colors from 'colors'; import inquirer from 'inquirer'; -import { FileMetadataState } from './codecs'; +import { FileFormatState } from './codecs'; import { logger } from '../../logger'; import { mapSeries } from '../bluebird-replace'; import { PreferenceTopic } from '../graphql'; import { PreferenceTopicType } from '@transcend-io/privacy-types'; import { splitCsvToList } from '../requests'; - -/* eslint-disable no-param-reassign */ +import type { PersistedState } from '@transcend-io/persisted-state'; /** * Parse out the purpose.enabled and preference values from a CSV file * * @param preferences - List of preferences - * @param currentState - The current file metadata state for parsing this list + * @param schemaState - The schema state to use for parsing the file * @param options - Options * @returns The updated file metadata state */ export async function parsePreferenceAndPurposeValuesFromCsv( preferences: Record[], - currentState: FileMetadataState, + schemaState: PersistedState, { purposeSlugs, preferenceTopics, @@ -36,19 +35,20 @@ export async function parsePreferenceAndPurposeValuesFromCsv( /** Columns to ignore in the CSV file */ columnsToIgnore: string[]; }, -): Promise { +): Promise> { // Determine columns to map const columnNames = uniq(preferences.map((x) => Object.keys(x)).flat()); // Determine the columns that could potentially be used for identifier + const timestampCol = schemaState.getValue('timestampColumn'); const otherColumns = difference(columnNames, [ - ...Object.keys(currentState.columnToIdentifier), - ...(currentState.timestampColumn ? [currentState.timestampColumn] : []), + ...Object.keys(schemaState.getValue('columnToIdentifier')), + ...(timestampCol ? [timestampCol] : []), ...columnsToIgnore, ]); if (otherColumns.length === 0) { if (forceTriggerWorkflows) { - return currentState; + return schemaState; } throw new Error('No other columns to process'); } @@ -65,7 +65,8 @@ export async function parsePreferenceAndPurposeValuesFromCsv( const uniqueValues = uniq(preferences.map((x) => x[col])); // Map the column to a purpose - let purposeMapping = currentState.columnToPurposeName[col]; + const currentPurposeMapping = schemaState.getValue('columnToPurposeName'); + let purposeMapping = currentPurposeMapping[col]; if (purposeMapping) { logger.info( colors.magenta( @@ -203,10 +204,9 @@ export async function parsePreferenceAndPurposeValuesFromCsv( ); } }); - - currentState.columnToPurposeName[col] = purposeMapping; + currentPurposeMapping[col] = purposeMapping; + schemaState.setValue(currentPurposeMapping, 'columnToPurposeName'); }); - return currentState; + return schemaState; } -/* eslint-enable no-param-reassign */ diff --git a/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts b/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts similarity index 61% rename from src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts rename to src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts index 4e166f1c..fc835e81 100644 --- a/src/lib/preference-management/parsePreferenceTimestampsFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts @@ -1,15 +1,14 @@ import { uniq, difference } from 'lodash-es'; import colors from 'colors'; import inquirer from 'inquirer'; -import { FileMetadataState } from './codecs'; +import { FileFormatState } from './codecs'; import { logger } from '../../logger'; +import type { PersistedState } from '@transcend-io/persisted-state'; export const NONE_PREFERENCE_MAP = '[NONE]'; -/* eslint-disable no-param-reassign */ - /** - * Parse timestamps from a CSV list of preferences + * Parse timestamps and other file format mapping from a CSV list of preferences * * When timestamp is requested, this script * ensures that all rows have a valid timestamp. @@ -20,21 +19,21 @@ export const NONE_PREFERENCE_MAP = '[NONE]'; * @param currentState - The current file metadata state for parsing this list * @returns The updated file metadata state */ -export async function parsePreferenceTimestampsFromCsv( +export async function parsePreferenceFileFormatFromCsv( preferences: Record[], - currentState: FileMetadataState, -): Promise { + currentState: PersistedState, +): Promise> { // Determine columns to map const columnNames = uniq(preferences.map((x) => Object.keys(x)).flat()); // Determine the columns that could potentially be used for timestamp const remainingColumnsForTimestamp = difference(columnNames, [ - ...Object.keys(currentState.columnToIdentifier), - ...Object.keys(currentState.columnToPurposeName), + ...Object.keys(currentState.getValue('columnToIdentifier')), + ...Object.keys(currentState.getValue('columnToPurposeName')), ]); // Determine the timestamp column to work off of - if (!currentState.timestampColumn) { + if (!currentState.getValue('timestampColumn')) { const { timestampName } = await inquirer.prompt<{ /** timestamp name */ timestampName: string; @@ -55,33 +54,39 @@ export async function parsePreferenceTimestampsFromCsv( choices: [...remainingColumnsForTimestamp, NONE_PREFERENCE_MAP], }, ]); - currentState.timestampColumn = timestampName; + + currentState.setValue(timestampName, 'timestampColumn'); } logger.info( - colors.magenta(`Using timestamp column "${currentState.timestampColumn}"`), + colors.magenta( + `Using timestamp column "${currentState.getValue('timestampColumn')}"`, + ), ); // Validate that all rows have valid timestamp - if (currentState.timestampColumn !== NONE_PREFERENCE_MAP) { + if (currentState.getValue('timestampColumn') !== NONE_PREFERENCE_MAP) { const timestampColumnsMissing = preferences - .map((pref, ind) => (pref[currentState.timestampColumn!] ? null : [ind])) + .map((pref, ind) => + pref[currentState.getValue('timestampColumn')!] ? null : [ind], + ) .filter((x): x is number[] => !!x) .flat(); if (timestampColumnsMissing.length > 0) { throw new Error( - `The timestamp column "${ - currentState.timestampColumn - }" is missing a value for the following rows: ${timestampColumnsMissing.join( + `The timestamp column "${currentState.getValue( + 'timestampColumn', + )}" is missing a value for the following rows: ${timestampColumnsMissing.join( '\n', )}`, ); } logger.info( colors.magenta( - `The timestamp column "${currentState.timestampColumn}" is present for all row`, + `The timestamp column "${currentState.getValue( + 'timestampColumn', + )}" is present for all row`, ), ); } return currentState; } -/* eslint-enable no-param-reassign */ diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index cc8f524d..9aa5b66c 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -2,12 +2,13 @@ import { uniq, keyBy } from 'lodash-es'; import colors from 'colors'; import inquirer from 'inquirer'; -import { FileMetadataState } from './codecs'; +import type { FileFormatState } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; import { mapSeries } from '../bluebird-replace'; import type { Identifier } from '../graphql'; import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; +import type { PersistedState } from '@transcend-io/persisted-state'; /* eslint-disable no-param-reassign */ @@ -18,21 +19,29 @@ import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; * and that all identifiers are unique. * * @param preferences - List of preferences - * @param currentState - The current file metadata state for parsing this list - * @param orgIdentifiers - The list of identifiers configured for the org - * @param allowedIdentifierNames - The list of identifier names that are allowed for this upload - * @param identifierColumns - The columns in the CSV that should be used as identifiers + * @param options - Options * @returns The updated file metadata state */ export async function parsePreferenceIdentifiersFromCsv( preferences: Record[], - currentState: FileMetadataState, - orgIdentifiers: Identifier[], - allowedIdentifierNames: string[], - identifierColumns: string[], + { + schemaState, + orgIdentifiers, + allowedIdentifierNames, + identifierColumns, + }: { + /** The current state of the schema metadata */ + schemaState: PersistedState; + /** The list of identifiers configured for the org */ + orgIdentifiers: Identifier[]; + /** The list of identifier names that are allowed for this upload */ + allowedIdentifierNames: string[]; + /** The columns in the CSV that should be used as identifiers */ + identifierColumns: string[]; + }, ): Promise<{ /** The updated state */ - currentState: FileMetadataState; + schemaState: PersistedState; /** The updated preferences */ preferences: Record[]; }> { @@ -77,9 +86,10 @@ export async function parsePreferenceIdentifiersFromCsv( } // Determine the columns that could potentially be used for identifiers + const currentColumnToIdentifier = schemaState.getValue('columnToIdentifier'); await mapSeries(identifierColumns, async (col) => { // Map the column to an identifier - const identifierMapping = currentState.columnToIdentifier[col]; + const identifierMapping = currentColumnToIdentifier[col]; if (identifierMapping) { logger.info( colors.magenta( @@ -102,16 +112,15 @@ export async function parsePreferenceIdentifiersFromCsv( choices: allowedIdentifierNames, }, ]); - currentState.columnToIdentifier[col] = { + currentColumnToIdentifier[col] = { name: identifierName, isUniqueOnPreferenceStore: orgIdentifiersByName[identifierName].isUniqueOnPreferenceStore, }; }); + schemaState.setValue(currentColumnToIdentifier, 'columnToIdentifier'); - const uniqueIdentifierColumns = Object.entries( - currentState.columnToIdentifier, - ) + const uniqueIdentifierColumns = Object.entries(currentColumnToIdentifier) .filter( ([, identifierMapping]) => identifierMapping.isUniqueOnPreferenceStore, ) @@ -161,7 +170,7 @@ export async function parsePreferenceIdentifiersFromCsv( ), ); - return { currentState, preferences }; + return { schemaState, preferences }; } /* eslint-enable no-param-reassign */ @@ -180,7 +189,7 @@ export function getPreferenceIdentifiersFromRow({ /** The current row from CSV file */ row: Record; /** The current file metadata state */ - columnToIdentifier: FileMetadataState['columnToIdentifier']; + columnToIdentifier: FileFormatState['columnToIdentifier']; }): PreferenceStoreIdentifier[] { // TODO: Remove this COSTCO specific logic const emailColumn = Object.keys(columnToIdentifier).find((x) => @@ -218,7 +227,7 @@ export function getUniquePreferenceIdentifierNamesFromRow({ /** The current row from CSV file */ row: Record; /** The current file metadata state */ - columnToIdentifier: FileMetadataState['columnToIdentifier']; + columnToIdentifier: FileFormatState['columnToIdentifier']; }): string[] { return Object.keys(columnToIdentifier).filter( (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 699877cf..090afa1f 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -3,13 +3,13 @@ import type { Got } from 'got'; import { keyBy } from 'lodash-es'; import * as t from 'io-ts'; import colors from 'colors'; -import { FileMetadataState, PreferenceState } from './codecs'; +import { type FileFormatState, type RequestUploadReceipts } from './codecs'; import { logger } from '../../logger'; import { readCsv } from '../requests'; import { getPreferencesForIdentifiers } from './getPreferencesForIdentifiers'; import { PreferenceTopic, type Identifier } from '../graphql'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; -import { parsePreferenceTimestampsFromCsv } from './parsePreferenceTimestampsFromCsv'; +import { parsePreferenceFileFormatFromCsv } from './parsePreferenceFileFormatFromCsv'; import { addTranscendIdToPreferences, getUniquePreferenceIdentifierNamesFromRow, @@ -24,7 +24,8 @@ import { checkIfPendingPreferenceUpdatesCauseConflict } from './checkIfPendingPr * * * @param options - Options - * @param cache - The cache to store the parsed file in + * @param schemaState - The schema state to use for parsing the file + * @param uploadState - The upload state to use for parsing the file * @returns The cache with the parsed file */ export async function parsePreferenceManagementCsvWithCache( @@ -38,7 +39,6 @@ export async function parsePreferenceManagementCsvWithCache( forceTriggerWorkflows, orgIdentifiers, allowedIdentifierNames, - oldReceiptFilepath, identifierColumns, columnsToIgnore, }: { @@ -60,37 +60,17 @@ export async function parsePreferenceManagementCsvWithCache( orgIdentifiers: Identifier[]; /** allowed identifiers names */ allowedIdentifierNames: string[]; - /** Old receipt file path to restore from */ - oldReceiptFilepath?: string; /** Identifier columns on the CSV file */ identifierColumns: string[]; /** Columns to ignore in the CSV file */ columnsToIgnore: string[]; }, - cache: PersistedState, + schemaState: PersistedState, + uploadState: PersistedState, ): Promise { - // Restore the old file metadata if it exists - let oldFileMetadata: FileMetadataState | undefined; - if (oldReceiptFilepath) { - const oldPreferenceState = new PersistedState( - oldReceiptFilepath, - PreferenceState, - { - fileMetadata: {}, - failingUpdates: {}, - pendingUpdates: {}, - }, - ); - const oldGlobalMetadata = await oldPreferenceState.getValue('fileMetadata'); - const startFileKey = Object.keys(oldGlobalMetadata)[0]; - oldFileMetadata = oldGlobalMetadata[startFileKey]; - } // Start the timer const t0 = new Date().getTime(); - // Get the current metadata - const fileMetadata = cache.getValue('fileMetadata'); - // Read in the file logger.info(colors.magenta(`Reading in file: "${file}"`)); let preferences = readCsv(file, t.record(t.string, t.string)); @@ -98,64 +78,39 @@ export async function parsePreferenceManagementCsvWithCache( // TODO: Remove this COSTCO specific logic const updatedPreferences = await addTranscendIdToPreferences(preferences); preferences = updatedPreferences; - // start building the cache, can use previous cache as well - let currentState: FileMetadataState = { - columnToPurposeName: oldFileMetadata?.columnToPurposeName || {}, - pendingSafeUpdates: {}, - pendingConflictUpdates: {}, - skippedUpdates: {}, - columnToIdentifier: oldFileMetadata?.columnToIdentifier || {}, - ...(oldFileMetadata?.timestampColumn && { - timestampColumn: oldFileMetadata.timestampColumn, - }), - // Load in the last fetched time - ...((fileMetadata[file] || {}) as Partial), - lastFetchedAt: new Date().toISOString(), - }; // Validate that all timestamps are present in the file - currentState = await parsePreferenceTimestampsFromCsv( - preferences, - currentState, - ); - fileMetadata[file] = currentState; - await cache.setValue(fileMetadata, 'fileMetadata'); + await parsePreferenceFileFormatFromCsv(preferences, schemaState); // Validate that all identifiers are present and unique - const result = await parsePreferenceIdentifiersFromCsv( - preferences, - currentState, + const result = await parsePreferenceIdentifiersFromCsv(preferences, { + schemaState, orgIdentifiers, allowedIdentifierNames, identifierColumns, - ); - currentState = result.currentState; + }); preferences = result.preferences; - fileMetadata[file] = currentState; - await cache.setValue(fileMetadata, 'fileMetadata'); - - // Ensure all other columns are mapped to purpose and preference - // slug values - currentState = await parsePreferenceAndPurposeValuesFromCsv( - preferences, - currentState, - { - preferenceTopics, - purposeSlugs, - forceTriggerWorkflows, - columnsToIgnore, - }, - ); - fileMetadata[file] = currentState; - await cache.setValue(fileMetadata, 'fileMetadata'); + + // Ensure all other columns are mapped to purpose and preference slug values + await parsePreferenceAndPurposeValuesFromCsv(preferences, schemaState, { + preferenceTopics, + purposeSlugs, + forceTriggerWorkflows, + columnsToIgnore, + }); // Grab existing preference store records + const currentColumnToIdentifierMap = + schemaState.getValue('columnToIdentifier'); + const currentColumnToPurposeName = schemaState.getValue( + 'columnToPurposeName', + ); const identifiers = preferences.flatMap((pref) => getUniquePreferenceIdentifierNamesFromRow({ row: pref, - columnToIdentifier: currentState.columnToIdentifier, + columnToIdentifier: currentColumnToIdentifierMap, }).map((col) => ({ - name: currentState.columnToIdentifier[col].name, + name: currentColumnToIdentifierMap.name, value: pref[col], })), ); @@ -169,22 +124,23 @@ export async function parsePreferenceManagementCsvWithCache( const consentRecordByIdentifier = keyBy(existingConsentRecords, 'userId'); // Clear out previous updates - currentState.pendingConflictUpdates = {}; - currentState.pendingSafeUpdates = {}; - currentState.skippedUpdates = {}; + const pendingConflictUpdates: RequestUploadReceipts['pendingConflictUpdates'] = + {}; + const pendingSafeUpdates: RequestUploadReceipts['pendingSafeUpdates'] = {}; + const skippedUpdates: RequestUploadReceipts['skippedUpdates'] = {}; // Process each row preferences.forEach((pref) => { // Get the userIds that could be the primary key of the consent record const possiblePrimaryKeys = getUniquePreferenceIdentifierNamesFromRow({ row: pref, - columnToIdentifier: currentState.columnToIdentifier, + columnToIdentifier: currentColumnToIdentifierMap, }).map((col) => pref[col]); // determine updates for user const pendingUpdates = getPreferenceUpdatesFromRow({ row: pref, - columnToPurposeName: currentState.columnToPurposeName, + columnToPurposeName: currentColumnToPurposeName, preferenceTopics, purposeSlugs, }); @@ -216,7 +172,7 @@ export async function parsePreferenceManagementCsvWithCache( }) && !forceTriggerWorkflows ) { - currentState.skippedUpdates[primaryKey] = pref; + skippedUpdates[primaryKey] = pref; return; } @@ -229,7 +185,7 @@ export async function parsePreferenceManagementCsvWithCache( preferenceTopics, }) ) { - currentState.pendingConflictUpdates[primaryKey] = { + pendingConflictUpdates[primaryKey] = { row: pref, record: currentConsentRecord, }; @@ -237,12 +193,14 @@ export async function parsePreferenceManagementCsvWithCache( } // Add to pending updates - currentState.pendingSafeUpdates[primaryKey] = pref; + pendingSafeUpdates[primaryKey] = pref; }); // Read in the file - fileMetadata[file] = currentState; - await cache.setValue(fileMetadata, 'fileMetadata'); + uploadState.setValue(pendingSafeUpdates, 'pendingSafeUpdates'); + uploadState.setValue(pendingConflictUpdates, 'pendingConflictUpdates'); + uploadState.setValue(skippedUpdates, 'skippedUpdates'); + const t1 = new Date().getTime(); logger.info( colors.green( diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index f8843313..9ea57d0a 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -15,10 +15,10 @@ import cliProgress from 'cli-progress'; import { parseAttributesFromString } from '../requests'; import { PersistedState } from '@transcend-io/persisted-state'; import { parsePreferenceManagementCsvWithCache } from './parsePreferenceManagementCsv'; -import { PreferenceState } from './codecs'; +import { FileFormatState, RequestUploadReceipts } from './codecs'; import { PreferenceUpdateItem } from '@transcend-io/privacy-types'; import { apply } from '@transcend-io/type-utils'; -import { NONE_PREFERENCE_MAP } from './parsePreferenceTimestampsFromCsv'; +import { NONE_PREFERENCE_MAP } from './parsePreferenceFileFormatFromCsv'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; @@ -31,7 +31,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ auth, sombraAuth, receiptFilepath, - oldReceiptFilepath, + schemaFilePath, file, partition, isSilent = true, @@ -54,8 +54,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ partition: string; /** File where to store receipt and continue from where left off */ receiptFilepath: string; - /** Old receipt file path to restore from */ - oldReceiptFilepath?: string; + /** Path to schema file */ + schemaFilePath: string; /** The file to process */ file: string; /** API URL for Transcend backend */ @@ -88,14 +88,25 @@ export async function uploadPreferenceManagementPreferencesInteractive({ const parsedAttributes = parseAttributesFromString(attributes); // Create a new state file to store the requests from this run - const preferenceState = new PersistedState(receiptFilepath, PreferenceState, { - fileMetadata: {}, - failingUpdates: {}, - pendingUpdates: {}, + const uploadState = new PersistedState( + receiptFilepath, + RequestUploadReceipts, + { + pendingSafeUpdates: {}, + pendingConflictUpdates: {}, + skippedUpdates: {}, + failingUpdates: {}, + pendingUpdates: {}, + lastFetchedAt: new Date().toISOString(), + }, + ); + const schemaState = new PersistedState(schemaFilePath, FileFormatState, { + columnToPurposeName: {}, + lastFetchedAt: new Date().toISOString(), + columnToIdentifier: {}, }); - const failingRequests = preferenceState.getValue('failingUpdates'); - const pendingRequests = preferenceState.getValue('pendingUpdates'); - let fileMetadata = preferenceState.getValue('fileMetadata'); + const failingRequests = uploadState.getValue('failingUpdates'); + const pendingRequests = uploadState.getValue('pendingUpdates'); logger.info( colors.magenta( @@ -106,11 +117,6 @@ export async function uploadPreferenceManagementPreferencesInteractive({ `${ Object.values(pendingRequests).length } pending requests to be processed\n` + - `The following files are stored in cache and will be used:\n${Object.keys( - fileMetadata, - ) - .map((x) => x) - .join('\n')}\n` + `The following file will be processed: ${file}\n`, ), ); @@ -143,63 +149,67 @@ export async function uploadPreferenceManagementPreferencesInteractive({ forceTriggerWorkflows, orgIdentifiers: identifiers, allowedIdentifierNames, - oldReceiptFilepath, identifierColumns, columnsToIgnore, }, - preferenceState, + schemaState, + uploadState, ); // Construct the pending updates const pendingUpdates: Record = {}; - fileMetadata = preferenceState.getValue('fileMetadata'); - const metadata = fileMetadata[file]; + const safeUpdatesInCache = uploadState.getValue('pendingSafeUpdates'); + const conflictUpdatesInCache = uploadState.getValue('pendingConflictUpdates'); + const skippedUpdatesInCache = uploadState.getValue('skippedUpdates'); + const timestampColumn = schemaState.getValue('timestampColumn'); + const columnToPurposeName = schemaState.getValue('columnToPurposeName'); + const columnToIdentifier = schemaState.getValue('columnToIdentifier'); logger.info( colors.magenta( `Found ${ - Object.entries(metadata.pendingSafeUpdates).length + Object.entries(safeUpdatesInCache).length } safe updates in ${file}`, ), ); logger.info( colors.magenta( `Found ${ - Object.entries(metadata.pendingConflictUpdates).length + Object.entries(conflictUpdatesInCache).length } conflict updates in ${file}`, ), ); logger.info( colors.magenta( `Found ${ - Object.entries(metadata.skippedUpdates).length + Object.entries(skippedUpdatesInCache).length } skipped updates in ${file}`, ), ); // Update either safe updates only or safe + conflict Object.entries({ - ...metadata.pendingSafeUpdates, + ...safeUpdatesInCache, ...(skipConflictUpdates ? {} - : apply(metadata.pendingConflictUpdates, ({ row }) => row)), + : apply(conflictUpdatesInCache, ({ row }) => row)), }).forEach(([userId, update]) => { // Determine timestamp const timestamp = - metadata.timestampColumn === NONE_PREFERENCE_MAP + timestampColumn === NONE_PREFERENCE_MAP ? new Date() - : new Date(update[metadata.timestampColumn!]); + : new Date(update[timestampColumn!]); // Determine updates const updates = getPreferenceUpdatesFromRow({ row: update, - columnToPurposeName: metadata.columnToPurposeName, + columnToPurposeName, preferenceTopics, purposeSlugs: purposes.map((x) => x.trackingType), }); const identifiers = getPreferenceIdentifiersFromRow({ row: update, - columnToIdentifier: metadata.columnToIdentifier, + columnToIdentifier, }); pendingUpdates[userId] = { identifiers, @@ -216,8 +226,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ })), }; }); - await preferenceState.setValue(pendingUpdates, 'pendingUpdates'); - await preferenceState.setValue({}, 'failingUpdates'); + await uploadState.setValue(pendingUpdates, 'pendingUpdates'); + await uploadState.setValue({}, 'failingUpdates'); // Exist early if dry run if (dryRun) { @@ -285,7 +295,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }`, ), ); - const failingUpdates = preferenceState.getValue('failingUpdates'); + const failingUpdates = uploadState.getValue('failingUpdates'); currentChunk.forEach(([userId, update]) => { failingUpdates[userId] = { uploadedAt: new Date().toISOString(), @@ -293,7 +303,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ error: err?.response?.body || err?.message || 'Unknown error', }; }); - await preferenceState.setValue(failingUpdates, 'failingUpdates'); + await uploadState.setValue(failingUpdates, 'failingUpdates'); } total += currentChunk.length; From a57fbd13534fc1bc87a88ce2c1b3eab930bbd5c8 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 15 Aug 2025 20:44:06 -0700 Subject: [PATCH 11/72] Parrallel: --- package.json | 2 +- .../collectCsvFilesOrExit.ts | 75 ++++ .../consent/upload-preferences/command.ts | 15 +- .../upload-preferences/computeFiles.ts | 55 +++ .../consent/upload-preferences/impl.ts | 355 ++++++++++++------ .../consent/upload-preferences/runChild.ts | 95 +++++ src/lib/pooling/assignWorkToWorker.ts | 46 +++ src/lib/pooling/attachWorkerHandlers.ts | 153 ++++++++ src/lib/pooling/computePoolSize.ts | 28 ++ src/lib/pooling/ensureLogFile.ts | 13 + src/lib/pooling/index.ts | 7 + src/lib/pooling/installInteractiveSwitcher.ts | 253 +++++++++++++ src/lib/pooling/logRotation.ts | 39 ++ src/lib/pooling/openTerminal.ts | 80 ++++ src/lib/pooling/renderDashboard.ts | 70 ++++ src/lib/pooling/spawnWorkerProcess.ts | 106 ++++++ .../getPreferencesForIdentifiers.ts | 30 +- .../parsePreferenceManagementCsv.ts | 2 +- ...ferenceManagementPreferencesInteractive.ts | 22 +- 19 files changed, 1308 insertions(+), 138 deletions(-) create mode 100644 src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts create mode 100644 src/commands/consent/upload-preferences/computeFiles.ts create mode 100644 src/commands/consent/upload-preferences/runChild.ts create mode 100644 src/lib/pooling/assignWorkToWorker.ts create mode 100644 src/lib/pooling/attachWorkerHandlers.ts create mode 100644 src/lib/pooling/computePoolSize.ts create mode 100644 src/lib/pooling/ensureLogFile.ts create mode 100644 src/lib/pooling/index.ts create mode 100644 src/lib/pooling/installInteractiveSwitcher.ts create mode 100644 src/lib/pooling/logRotation.ts create mode 100644 src/lib/pooling/openTerminal.ts create mode 100644 src/lib/pooling/renderDashboard.ts create mode 100644 src/lib/pooling/spawnWorkerProcess.ts diff --git a/package.json b/package.json index e65d5a26..0e188f24 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "__tr-upload-preferences": "dist/bin/deprecated-command.cjs" }, "engines": { - "node": ">=18" + "node": ">=22" }, "files": [ "dist" diff --git a/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts b/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts new file mode 100644 index 00000000..8cb3060e --- /dev/null +++ b/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts @@ -0,0 +1,75 @@ +import { logger } from '../../../logger'; +import { join } from 'path'; +import { readdirSync } from 'fs'; +import colors from 'colors'; +import type { LocalContext } from '../../../context'; + +/** + * Validate flags and collect CSV file paths from a directory or single file. + * On validation error, the provided `exit` function is called. + * + * @param directory - the directory containing CSV files, or undefined if a single file is provided + * @param file - the path to a single CSV file, or undefined if a directory is provided + * @param localContext - the context of the command, used for logging and exit + * @returns an array of valid CSV file paths + */ +export function collectCsvFilesOrExit( + directory: string | undefined, + file: string | undefined, + localContext: LocalContext, +): string[] { + const files: string[] = []; + + // Mutually exclusive inputs. + if (!!directory && !!file) { + logger.error( + colors.red( + 'Cannot provide both a directory and a file. Please provide only one.', + ), + ); + localContext.process.exit(1); + } + + // At least one must be present. + if (!file && !directory) { + logger.error( + colors.red( + 'A file or directory must be provided. Use --file=./preferences.csv or --directory=./preferences', + ), + ); + localContext.process.exit(1); + } + + if (directory) { + // Collect all CSVs under directory. + try { + const csvFiles = readdirSync(directory).filter((f) => f.endsWith('.csv')); + if (csvFiles.length === 0) { + logger.error( + colors.red(`No CSV files found in directory: ${directory}`), + ); + localContext.process.exit(1); + } + files.push(...csvFiles.map((f) => join(directory, f))); + } catch (err) { + logger.error(colors.red(`Failed to read directory: ${directory}`)); + logger.error(colors.red((err as Error).message)); + localContext.process.exit(1); + } + } else if (file) { + // Single-file mode; ensure it's CSV. + try { + if (!file.endsWith('.csv')) { + logger.error(colors.red('File must be a CSV file')); + localContext.process.exit(1); + } + files.push(file); + } catch (err) { + logger.error(colors.red(`Failed to access file: ${file}`)); + logger.error(colors.red((err as Error).message)); + localContext.process.exit(1); + } + } + + return files; +} diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index ea2a53b1..532c68f6 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -105,8 +105,9 @@ export const uploadPreferencesCommand = buildCommand({ concurrency: { kind: 'parsed', parse: numberParser, - brief: 'The concurrency to use when uploading in parallel', - default: '1', + brief: + 'The concurrency to use when uploading in parallel - otherwise uses the number of CPU cores available', + optional: true, }, allowedIdentifierNames: { kind: 'parsed', @@ -135,6 +136,14 @@ export const uploadPreferencesCommand = buildCommand({ This 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. -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs.`, +The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. + +Parallel preference uploader (Node 22+ ESM/TS) +----------------------------------------------------------------------------- +- Spawns a pool of child *processes* (not threads) to run uploads in parallel. +- Shows a live dashboard in the parent terminal with progress per worker. +- Creates per-worker log files and (optionally) opens OS terminals to tail them. +- Uses the same module as both parent and child; the child mode is toggled + by the presence of a CLI flag ('--child-upload-preferences').`, }, }); diff --git a/src/commands/consent/upload-preferences/computeFiles.ts b/src/commands/consent/upload-preferences/computeFiles.ts new file mode 100644 index 00000000..5ad427dc --- /dev/null +++ b/src/commands/consent/upload-preferences/computeFiles.ts @@ -0,0 +1,55 @@ +import { mkdirSync } from 'fs'; +import { join, basename } from 'path'; + +/** + * Derive a "prefix" for a CSV file (basename without ".csv"), + * used to compute the receipt filename. + * + * @param file - The file name + * @returns The file prefix + */ +export function getFilePrefix(file: string): string { + return basename(file).replace('.csv', ''); +} + +/** + * Ensure and return the receipts folder path. + * If a directory flag is provided, default to sibling "../receipts". + * + * @param receiptFileDir - Optional directory for receipt files + * @param directory - Optional directory containing CSV files + * @returns The receipts folder path + */ +export function computeReceiptsFolder( + receiptFileDir: string | undefined, + directory: string | undefined, +): string { + const receiptsFolder = + receiptFileDir || + (directory ? join(directory, '../receipts') : './receipts'); + mkdirSync(receiptsFolder, { recursive: true }); + return receiptsFolder; +} + +/** + * Resolve the schema file path. + * - If user provided `schemaFilePath`, use it. + * - Otherwise, default near the directory or next to the single file. + * + * @param schemaFilePath - Optional path to the schema file + * @param directory - Optional directory containing CSV files + * @param firstFile - The first CSV file to derive the prefix from + * @returns The resolved schema file path + */ +export function computeSchemaFile( + schemaFilePath: string | undefined, + directory: string | undefined, + firstFile: string, +): string { + return ( + schemaFilePath || + (directory + ? join(directory, '../preference-upload-schema.json') + : `${getFilePrefix(firstFile)}-preference-upload-schema.json`) + ); +} diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index befd9b2a..2cc94d74 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -1,14 +1,28 @@ +// main.ts import type { LocalContext } from '../../../context'; import colors from 'colors'; - import { logger } from '../../../logger'; -import { uploadPreferenceManagementPreferencesInteractive } from '../../../lib/preference-management'; -import { splitCsvToList } from '../../../lib/requests'; -import { readdirSync } from 'fs'; -import { map } from '../../../lib/bluebird-replace'; -import { basename, join } from 'path'; +import { join } from 'node:path'; +import { mkdirSync } from 'node:fs'; + import { doneInputValidation } from '../../../lib/cli/done-input-validation'; +import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; +import { collectCsvFilesOrExit } from './collectCsvFilesOrExit'; + +import { availableParallelism } from 'node:os'; +import type { ChildProcess } from 'node:child_process'; + +import { runChild } from './runChild'; +import { + getWorkerLogPaths, + renderDashboard, + spawnWorkerProcess, + type WorkerState, +} from '../../../lib/pooling'; +import { installInteractiveSwitcher } from '../../../lib/pooling/installInteractiveSwitcher'; +import { resetWorkerLogs } from '../../../lib/pooling/logRotation'; +/** CLI flags */ export interface UploadPreferencesCommandFlags { auth: string; partition: string; @@ -26,20 +40,58 @@ export interface UploadPreferencesCommandFlags { isSilent: boolean; attributes: string; receiptFilepath: string; - concurrency: number; + concurrency?: number; allowedIdentifierNames: string[]; identifierColumns: string[]; columnsToIgnore?: string[]; } +/** Common options shared by upload tasks */ +export type TaskCommonOpts = Pick< + UploadPreferencesCommandFlags, + | 'auth' + | 'partition' + | 'sombraAuth' + | 'transcendUrl' + | 'skipConflictUpdates' + | 'skipWorkflowTriggers' + | 'skipExistingRecordCheck' + | 'isSilent' + | 'dryRun' + | 'attributes' + | 'forceTriggerWorkflows' + | 'allowedIdentifierNames' + | 'identifierColumns' + | 'columnsToIgnore' +> & { + /** Path to the schema file */ + schemaFile: string; + /** Directory to store receipt files */ + receiptsFolder: string; +}; + +/** + * In CJS, __filename is available; use it as the path to fork. + */ +function getCurrentModulePath(): string { + // @ts-ignore - __filename is present in CJS/ts-node + if (typeof __filename !== 'undefined') return __filename as unknown as string; + return process.argv[1]; +} + /** - * Get the file prefix from the file name + * Compute pool size from flags or CPU count * - * @param file - The file name - * @returns The file prefix + * @param concurrency + * @param filesCount */ -function getFilePrefix(file: string): string { - return basename(file).replace('.csv', ''); +function computePoolSize(concurrency: number | undefined, filesCount: number) { + const cpuCount = Math.max(1, availableParallelism?.() ?? 1); + const desired = + typeof concurrency === 'number' && concurrency > 0 + ? Math.min(concurrency, filesCount) + : Math.min(cpuCount, filesCount); + return { poolSize: desired, cpuCount }; } export async function uploadPreferences( @@ -66,68 +118,16 @@ export async function uploadPreferences( columnsToIgnore = [], }: UploadPreferencesCommandFlags, ): Promise { - if (!!directory && !!file) { - logger.error( - colors.red( - 'Cannot provide both a directory and a file. Please provide only one.', - ), - ); - this.process.exit(1); - } - - if (!file && !directory) { - logger.error( - colors.red( - 'A file or directory must be provided. Please provide one using --file=./preferences.csv or --directory=./preferences', - ), - ); - this.process.exit(1); - } - + // Build the set of CSV files we need to process + const files = collectCsvFilesOrExit(directory, file, this); doneInputValidation(this.process.exit); - const files: string[] = []; - - if (directory) { - try { - const filesInDirectory = readdirSync(directory); - const csvFiles = filesInDirectory.filter((file) => file.endsWith('.csv')); - - if (csvFiles.length === 0) { - logger.error( - colors.red(`No CSV files found in directory: ${directory}`), - ); - this.process.exit(1); - } - - // Add full paths for each CSV file - files.push(...csvFiles.map((file) => join(directory, file))); - } catch (err) { - logger.error(colors.red(`Failed to read directory: ${directory}`)); - logger.error(colors.red((err as Error).message)); - this.process.exit(1); - } - } else { - try { - // Verify file exists and is a CSV - if (!file.endsWith('.csv')) { - logger.error(colors.red('File must be a CSV file')); - this.process.exit(1); - } - files.push(file); - } catch (err) { - logger.error(colors.red(`Failed to access file: ${file}`)); - logger.error(colors.red((err as Error).message)); - this.process.exit(1); - } - } - logger.info( colors.green( `Processing ${files.length} consent preferences files for partition: ${partition}`, ), ); - logger.debug(`Files to process: ${files.join(', ')}`); + logger.debug(`Files to process:\n${files.join('\n')}`); if (skipExistingRecordCheck) { logger.info( @@ -137,55 +137,178 @@ export async function uploadPreferences( ); } - // Determine receipts folder - const receiptsFolder = - receiptFileDir || - (directory ? join(directory, '../receipts') : './receipts'); - - // Determine the schema file - const schemaFile = - schemaFilePath || - (directory - ? join(directory, '../preference-upload-schema.json') - : `${getFilePrefix(files[0])}-preference-upload-schema.json`); - - // yarn ts-node ./src/cli-chunk-csv.ts --inputFile=$file - // Create folder of 1200 chunks in ./working/costco/udp/all-chunks - // Copy over 100 files from all-chunks -> pending-chunks - // Run pnpm start consent upload-preferences --auth=$API_KEY --partition=448b3320-9d7c-499a-bc56-f0dae33c8f5c --directory=./working/costco/udp/pending-chunks --dryRun=false --skipWorkflowTriggers=true --skipExistingRecordCheck=true --isSilent=true --attributes="Tags:transcend-cli,Source:transcend-cli" --transcendUrl=https://api.us.transcend.io/ --allowedIdentifierNames="email,personID,memberID,transcendID,birthDate" --identifierColumns="email_address,person_id,member_id,transcendID,birth_dt" --columnsToIgnore="source_system,mktg_consent_ts" --sombraAuth=$SOMBRA_AUTH --concurrency=1 - // Writes each of 1200 files to ./receipts/-receipts.json -> currently there are 300 - - // FIXME auto splitting - // FIXME: use single tenant sombra - // FIXME add overview of status - // FIXME handle re-processing of same file, and error handling - - await map( - files, - async (filePath) => { - await uploadPreferenceManagementPreferencesInteractive({ - receiptFilepath: join( - receiptsFolder, - `${getFilePrefix(filePath)}-receipts.json`, - ), - schemaFilePath: schemaFile, - auth, - sombraAuth, - file: filePath, - partition, - transcendUrl, - skipConflictUpdates, - skipWorkflowTriggers, - skipExistingRecordCheck, - isSilent, - dryRun, - attributes: splitCsvToList(attributes), - forceTriggerWorkflows, - allowedIdentifierNames, - identifierColumns, - columnsToIgnore, - }); + // Resolve I/O targets (receipts + schema) + const receiptsFolder = computeReceiptsFolder(receiptFileDir, directory); + const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); + + // Size the worker pool + const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); + const common: TaskCommonOpts = { + /* same as before */ schemaFile, + receiptsFolder, + auth, + sombraAuth, + partition, + transcendUrl, + skipConflictUpdates, + skipWorkflowTriggers, + skipExistingRecordCheck, + isSilent, + dryRun, + attributes, + forceTriggerWorkflows, + allowedIdentifierNames, + identifierColumns, + columnsToIgnore, + }; + + const LOG_DIR = join(receiptsFolder, 'logs'); + mkdirSync(LOG_DIR, { recursive: true }); + + const RESET_MODE = + (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'delete'; // FIXME + resetWorkerLogs(LOG_DIR, RESET_MODE); + + const modulePath = getCurrentModulePath(); + const workers = new Map(); + const workerState = new Map(); + const pending = [...files]; + const totals = { completed: 0, failed: 0 }; + + let dashboardPaused = false; + const repaint = () => { + if (!dashboardPaused) { + renderDashboard( + poolSize, + cpuCount, + files.length, + totals.completed, + totals.failed, + workerState, + ); + } + }; + + const assignWorkToWorker = (id: number) => { + const w = workers.get(id); + if (!w) return; + const filePath = pending.shift(); + if (!filePath) { + workerState.set(id, { busy: false, file: null, startedAt: null }); + return; + } + workerState.set(id, { busy: true, file: filePath, startedAt: Date.now() }); + w.send?.({ type: 'task', payload: { filePath, options: common } }); + }; + + // spawn pool + for (let i = 0; i < poolSize; i += 1) { + const child = spawnWorkerProcess(i, modulePath, LOG_DIR, true, isSilent); + workers.set(i, child); + workerState.set(i, { busy: false, file: null, startedAt: null }); + + child.on('message', (msg: any) => { + if (!msg || typeof msg !== 'object') return; + if (msg.type === 'ready') { + assignWorkToWorker(i); + repaint(); + } else if (msg.type === 'result') { + const { ok } = msg.payload || {}; + if (ok) totals.completed += 1; + else totals.failed += 1; + workerState.set(i, { busy: false, file: null, startedAt: null }); + assignWorkToWorker(i); + repaint(); + } + }); + child.on('exit', () => { + workers.delete(i); + workerState.set(i, { busy: false, file: null, startedAt: null }); + repaint(); + }); + } + + const renderInterval = setInterval(repaint, 350); + + // graceful Ctrl+C + const onSigint = () => { + clearInterval(renderInterval); + cleanupSwitcher(); + process.stdout.write('\nStopping workers...\n'); + for (const [, w] of workers) { + try { + w.send?.({ type: 'shutdown' }); + } catch {} + try { + w.kill('SIGTERM'); + } catch {} + } + this.process.exit(130); + }; + process.once('SIGINT', onSigint); + + // attach/switch UI with replay + const detachScreen = () => { + dashboardPaused = false; + repaint(); + }; + const attachScreen = (id: number) => { + dashboardPaused = true; + process.stdout.write('\x1b[2J\x1b[H'); // clear + process.stdout.write( + `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT • Ctrl+D EOF)\n`, + ); + }; + const cleanupSwitcher = installInteractiveSwitcher({ + workers, + onAttach: attachScreen, + onDetach: detachScreen, + onCtrlC: onSigint, + getLogPaths: (id) => { + const w = workers.get(id); + return w ? getWorkerLogPaths(w) : undefined; }, - { concurrency }, + replayBytes: 200 * 1024, // last ~200KB + replayWhich: ['out', 'err'], // also add 'structured' if you want + onEnterAttachScreen: attachScreen, + }); + + // hint + const maxDigit = Math.min(poolSize - 1, 9); + const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + process.stdout.write( + colors.dim( + `\nHotkeys: [${digitRange}] attach${extra} • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit\n\n`, + ), ); + + // wait for completion + await new Promise((resolve) => { + const check = setInterval(() => { + if (pending.length === 0 && workers.size === 0) { + clearInterval(check); + clearInterval(renderInterval); + detachScreen(); + cleanupSwitcher(); + process.stdout.write('\nAll done.\n'); + resolve(); + } else if (pending.length === 0) { + for (const [, w] of workers) w.send?.({ type: 'shutdown' }); + } + }, 300); + }); + + process.removeListener('SIGINT', onSigint); +} + +/* ------------------------------------------------------------------------------------------------- + * If invoked directly as a child process, enter worker loop + * ------------------------------------------------------------------------------------------------- */ +const CHILD_FLAG = '--child-upload-preferences'; +if (process.argv.includes(CHILD_FLAG)) { + runChild().catch((err) => { + logger.error(err); + process.exit(1); + }); } diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts new file mode 100644 index 00000000..4f197a64 --- /dev/null +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -0,0 +1,95 @@ +// runChild.ts +import { mkdirSync, createWriteStream } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { uploadPreferenceManagementPreferencesInteractive } from '../../../lib/preference-management'; +import { getFilePrefix } from './computeFiles'; +import { splitCsvToList } from '../../../lib/requests'; + +export async function runChild(): Promise { + const workerId = Number(process.env.WORKER_ID || '0'); + const logFile = + process.env.WORKER_LOG || + join(process.cwd(), `logs/worker-${workerId}.log`); + mkdirSync(dirname(logFile), { recursive: true }); + + const logStream = createWriteStream(logFile, { flags: 'a' }); + const log = (...args: unknown[]): void => { + const line = `[w${workerId}] ${new Date().toISOString()} ${args + .map((a) => String(a)) + .join(' ')}\n`; + logStream.write(line); + }; + + console.log(`[w${workerId}] ready pid=${process.pid}`); + process.send?.({ type: 'ready' }); + + process.on('message', async (msg: any) => { + if (!msg || typeof msg !== 'object') return; + if (msg.type === 'task') { + const { filePath, options } = msg.payload as { + filePath: string; + options: TaskCommonOpts; + }; + const receiptFilepath = join( + options.receiptsFolder, + `${getFilePrefix(filePath)}-receipts.json`, + ); + try { + mkdirSync(dirname(receiptFilepath), { recursive: true }); + console.log(`[w${workerId}] START ${filePath}`); + log(`START ${filePath}`); + + await uploadPreferenceManagementPreferencesInteractive({ + receiptFilepath, + schemaFilePath: options.schemaFile, + auth: options.auth, + sombraAuth: options.sombraAuth, + file: filePath, + partition: options.partition, + transcendUrl: options.transcendUrl, + skipConflictUpdates: options.skipConflictUpdates, + skipWorkflowTriggers: options.skipWorkflowTriggers, + skipExistingRecordCheck: options.skipExistingRecordCheck, + isSilent: options.isSilent, + dryRun: options.dryRun, + attributes: splitCsvToList(options.attributes), + forceTriggerWorkflows: options.forceTriggerWorkflows, + allowedIdentifierNames: options.allowedIdentifierNames, + identifierColumns: options.identifierColumns, + columnsToIgnore: options.columnsToIgnore || [], + }); + + console.log(`[w${workerId}] DONE ${filePath}`); + log(`SUCCESS ${filePath}`); + process.send?.({ type: 'result', payload: { ok: true, filePath } }); + } catch (err: any) { + const e = err?.stack || err?.message || String(err); + console.error( + `[w${workerId}] ERROR ${filePath}: ${err?.message || err}`, + ); + log(`FAIL ${filePath}\n${e}`); + process.send?.({ + type: 'result', + payload: { ok: false, filePath, error: e }, + }); + } + } else if (msg.type === 'shutdown') { + console.log(`[w${workerId}] shutdown`); + log('Shutting down.'); + logStream.end(() => process.exit(0)); + } + }); + + process.on('uncaughtException', (err) => { + console.error(`[w${workerId}] uncaughtException: ${err?.stack || err}`); + log(`uncaughtException\n${err?.stack || err}`); + logStream.end(() => process.exit(1)); + }); + process.on('unhandledRejection', (reason) => { + console.error(`[w${workerId}] unhandledRejection: ${String(reason)}`); + log(`unhandledRejection\n${String(reason)}`); + logStream.end(() => process.exit(1)); + }); + + await new Promise(() => {}); +} diff --git a/src/lib/pooling/assignWorkToWorker.ts b/src/lib/pooling/assignWorkToWorker.ts new file mode 100644 index 00000000..43b9ccfa --- /dev/null +++ b/src/lib/pooling/assignWorkToWorker.ts @@ -0,0 +1,46 @@ +import type { WorkerState } from './renderDashboard'; +import type { ChildProcess } from 'node:child_process'; + +/** + * Assign the next pending file to a worker. + * Updates `workerState` for the dashboard and sends the IPC message. + * + * @param id - the worker ID + * @param pending - the list of pending files + * @param workers - the map of worker processes + * @param workerState - the map of worker states + * @param common - the common task options + * @param repaint - the function to call to repaint the dashboard + */ +export function assignWorkToWorker( + id: number, + pending: string[], + workers: Map, + workerState: Map, + common: T, + repaint: () => void, +): void { + const w = workers.get(id); + if (!w) return; + + const filePath = pending.shift(); + if (!filePath) { + // No work left; mark idle + workerState.set(id, { busy: false, file: null, startedAt: null }); + repaint(); + return; + } + + // Update dashboard state + workerState.set(id, { busy: true, file: filePath, startedAt: Date.now() }); + repaint(); + + // Send work item over IPC + w.send?.({ + type: 'task', + payload: { + filePath, + options: common, + }, + }); +} diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts new file mode 100644 index 00000000..04db77df --- /dev/null +++ b/src/lib/pooling/attachWorkerHandlers.ts @@ -0,0 +1,153 @@ +import type { ChildProcess } from 'child_process'; +import type { WorkerState } from './renderDashboard'; +import { assignWorkToWorker } from './assignWorkToWorker'; +import { appendFileSync } from 'fs'; +import { join } from 'path'; +import { spawnWorkerProcess } from './spawnWorkerProcess'; + +export interface WorkerResultMessage { + /** Message type indicating a result */ + type: 'result'; + /** Result payload containing status and file info */ + payload: { + /** Whether the operation was successful */ + ok: boolean /** */; + /** Path to the processed file */ + filePath: string /** */; + /** Optional error message if operation failed */ + error?: string; + }; +} + +export interface WorkerReadyMessage { + /** Message type indicating worker is ready */ + type: 'ready'; +} + +/** + * Union type for worker messages + */ +export type WorkerMessage = WorkerResultMessage | WorkerReadyMessage; + +/** + * Wire up a worker's lifecycle: + * - on "ready": hand it work + * - on "result": update counters, queue next work + * - on "exit": optionally respawn if there is still work pending + * + * @param id - the worker ID + * @param child - the child process + * @param workers - the map of all worker processes + * @param workerState - the map of worker states + * @param state - the overall state + * @param filesPending - the list of files pending processing + * @param repaint - the function to call to repaint the dashboard + * @param common - the common task options + * @param onAllWorkersExited - the function to call when all workers have exited + * @param logDir - the directory for log files + * @param modulePath - the path to the current module (for spawning workers) + * @param spawnSilent - whether to spawn workers silently + */ +export function attachWorkerHandlers( + id: number, + child: ChildProcess, + workers: Map, + workerState: Map, + state: { + /** Number of completed files */ + completed: number; + /** Number of failed files */ + failed: number; + }, + filesPending: string[], + repaint: () => void, + common: T, + onAllWorkersExited: () => void, + logDir: string, + modulePath: string, + spawnSilent = false, +): void { + workers.set(id, child); + workerState.set(id, { busy: false, file: null, startedAt: null }); + + // Handle IPC messages from the child + child.on('message', (msg: WorkerMessage) => { + if (!msg || typeof msg !== 'object') return; + + if (msg.type === 'ready') { + // First work assignment + assignWorkToWorker( + id, + filesPending, + workers, + workerState, + common, + repaint, + ); + } else if (msg.type === 'result') { + // Update success/failure counters + const { ok, filePath, error } = msg.payload; + // eslint-disable-next-line no-param-reassign + if (ok) state.completed += 1; + // eslint-disable-next-line no-param-reassign + else state.failed += 1; + + // Mark idle on completion + workerState.set(id, { busy: false, file: null, startedAt: null }); + repaint(); + + // Append failure details (if any) to a shared log + if (!ok && error) { + appendFileSync( + join(logDir, 'failures.log'), + `[${new Date().toISOString()}] worker ${id} file=${filePath}\n${error}\n\n`, + ); + } + + // Keep the worker busy until the queue is empty + assignWorkToWorker( + id, + filesPending, + workers, + workerState, + common, + repaint, + ); + } + }); + + // Handle worker termination/crash + child.on('exit', (code, signal) => { + workers.delete(id); + + // If it crashed and there's still work to do, respawn a replacement + if ((code && code !== 0) || signal) { + if (filesPending.length > 0) { + const replacement = spawnWorkerProcess( + id, + modulePath, + logDir, + true, // attempt to open tail windows for respawns too + spawnSilent, + ); + attachWorkerHandlers( + id, + replacement, + workers, + workerState, + state, + filesPending, + repaint, + common, + onAllWorkersExited, + logDir, + modulePath, + ); + return; + } + } + + // If all workers have exited, let the parent clean up + if (workers.size === 0) onAllWorkersExited(); + }); +} diff --git a/src/lib/pooling/computePoolSize.ts b/src/lib/pooling/computePoolSize.ts new file mode 100644 index 00000000..cf147ba4 --- /dev/null +++ b/src/lib/pooling/computePoolSize.ts @@ -0,0 +1,28 @@ +import { availableParallelism } from 'node:os'; + +/** + * Decide how many worker processes to spawn: + * - If `concurrency` is set and > 0, use that (capped by file count). + * - Otherwise, use availableParallelism (capped by file count). + * Returns both pool size and CPU count for display. + * + * @param concurrency - Optional concurrency setting, defaults to undefined + * @param filesCount - The number of files to process + * @returns An object with `poolSize` and `cpuCount` + */ +export function computePoolSize( + concurrency: number | undefined, + filesCount: number, +): { + /** The number of worker processes to spawn */ + poolSize: number; + /** The number of CPU cores available for parallel processing */ + cpuCount: number; +} { + const cpuCount = Math.max(1, availableParallelism?.() ?? 1); + const desired = + typeof concurrency === 'number' && concurrency > 0 + ? Math.min(concurrency, filesCount) + : Math.min(cpuCount, filesCount); + return { poolSize: desired, cpuCount }; +} diff --git a/src/lib/pooling/ensureLogFile.ts b/src/lib/pooling/ensureLogFile.ts new file mode 100644 index 00000000..036b1e8d --- /dev/null +++ b/src/lib/pooling/ensureLogFile.ts @@ -0,0 +1,13 @@ +import { closeSync, existsSync, openSync } from 'fs'; + +/** + * Ensure a log file exists (touch). + * + * @param pathStr - the path to the log file + */ +export function ensureLogFile(pathStr: string): void { + if (!existsSync(pathStr)) { + const fd = openSync(pathStr, 'a'); + closeSync(fd); + } +} diff --git a/src/lib/pooling/index.ts b/src/lib/pooling/index.ts new file mode 100644 index 00000000..3abc65f5 --- /dev/null +++ b/src/lib/pooling/index.ts @@ -0,0 +1,7 @@ +export * from './computePoolSize'; +export * from './openTerminal'; +export * from './renderDashboard'; +export * from './assignWorkToWorker'; +export * from './ensureLogFile'; +export * from './spawnWorkerProcess'; +export * from './attachWorkerHandlers'; diff --git a/src/lib/pooling/installInteractiveSwitcher.ts b/src/lib/pooling/installInteractiveSwitcher.ts new file mode 100644 index 00000000..9cccad50 --- /dev/null +++ b/src/lib/pooling/installInteractiveSwitcher.ts @@ -0,0 +1,253 @@ +// interactiveSwitcher.ts +import * as readline from 'node:readline'; +import { createReadStream, statSync } from 'node:fs'; +import type { ChildProcess } from 'node:child_process'; +import type { WorkerLogPaths } from './spawnWorkerProcess'; + +/** + * + */ +export type WhichLogs = Array<'out' | 'err' | 'structured'>; + +/** + * + * @param opts + */ +export function installInteractiveSwitcher(opts: { + /** */ + workers: Map; + /** */ + onAttach?: (id: number) => void; + /** */ + onDetach?: () => void; + /** */ + onCtrlC?: () => void; // parent graceful shutdown in dashboard + /** Provide log paths so we can replay the tail on attach */ + getLogPaths?: (id: number) => WorkerLogPaths | undefined; + /** How many bytes to replay from the end of each file (default 200 KB) */ + replayBytes?: number; + /** Which logs to replay first (default ['out','err']) */ + replayWhich?: WhichLogs; + /** Print a small banner/clear screen before replaying (optional) */ + onEnterAttachScreen?: (id: number) => void; +}) { + const { + workers, + onAttach, + onDetach, + onCtrlC, + getLogPaths, + replayBytes = 200 * 1024, + replayWhich = ['out', 'err'], + onEnterAttachScreen, + } = opts; + + const { stdin } = process; + if (!stdin.isTTY) return () => {}; + + readline.emitKeypressEvents(stdin); + stdin.setRawMode?.(true); + + let mode: 'dashboard' | 'attached' = 'dashboard'; + let focus: number | null = null; + + // live mirroring handlers while attached + let outHandler: ((chunk: any) => void) | null = null; + let errHandler: ((chunk: any) => void) | null = null; + + const workerIds = () => [...workers.keys()].sort((a, b) => a - b); + const idxOf = (id: number) => workerIds().indexOf(id); + + /** + * + * @param path + * @param maxBytes + */ + function replayFileTailToStdout( + path: string, + maxBytes: number, + ): Promise { + return new Promise((resolve) => { + try { + const st = statSync(path); + const start = Math.max(0, st.size - maxBytes); + const stream = createReadStream(path, { start, encoding: 'utf8' }); + stream.on('data', (chunk) => process.stdout.write(chunk)); + stream.on('end', () => resolve()); + stream.on('error', () => resolve()); + } catch { + resolve(); + } + }); + } + + /** + * + * @param id + */ + async function replayLogs(id: number) { + if (!getLogPaths) return; + const paths = getLogPaths(id); + if (!paths) return; + + const toReplay: string[] = []; + for (const which of replayWhich) { + if (which === 'out') toReplay.push(paths.outPath); + if (which === 'err') toReplay.push(paths.errPath); + if (which === 'structured') toReplay.push(paths.structuredPath); + } + + if (toReplay.length) { + process.stdout.write(`\n${'-'.repeat(12)} replay ${'-'.repeat(12)}\n`); + for (const p of toReplay) { + // small header for context + process.stdout.write( + `\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\n`, + ); + + await replayFileTailToStdout(p, replayBytes); + } + process.stdout.write(`\n${'-'.repeat(32)}\n\n`); + } + } + + const attach = async (id: number) => { + const w = workers.get(id); + if (!w) return; + + // Detach any previous focus + if (mode === 'attached') detach(); + + mode = 'attached'; + focus = id; + + // UX: clear + banner + onEnterAttachScreen?.(id); + + // 1) replay last bytes from logs so you see history + await replayLogs(id); + + // 2) now mirror live child output to our terminal + onAttach?.(id); + outHandler = (chunk: any) => process.stdout.write(chunk); + errHandler = (chunk: any) => process.stderr.write(chunk); + w.stdout?.on('data', outHandler); + w.stderr?.on('data', errHandler); + + // auto-detach if child exits + const onExit = () => { + if (focus === id) detach(); + }; + w.once('exit', onExit); + }; + + const detach = () => { + if (focus == null) return; + const id = focus; + const w = workers.get(id); + if (w) { + if (outHandler) w.stdout?.off('data', outHandler); + if (errHandler) w.stderr?.off('data', errHandler); + } + outHandler = null; + errHandler = null; + focus = null; + mode = 'dashboard'; + onDetach?.(); + }; + + const cycle = (delta: number) => { + const ids = workerIds(); + if (ids.length === 0) return; + const current = focus == null ? ids[0] : focus; + let i = idxOf(current); + if (i === -1) i = 0; + i = (i + delta + ids.length) % ids.length; + void attach(ids[i]); + }; + + const onKey = (str: string, key: readline.Key) => { + if (!key) return; + + // Ctrl+C behavior + if (key.ctrl && key.name === 'c') { + if (mode === 'attached' && focus != null) { + const w = workers.get(focus); + try { + w?.kill('SIGINT'); + } catch {} + // optional: auto-detach so second Ctrl+C exits parent + detach(); + return; + } + onCtrlC?.(); + return; + } + + if (mode === 'dashboard') { + if (key.name && /^[0-9]$/.test(key.name)) { + const n = Number(key.name); + if (workers.has(n)) void attach(n); + return; + } + if (key.name === 'tab' && !key.shift) { + cycle(+1); + return; + } + if (key.name === 'tab' && key.shift) { + cycle(-1); + return; + } + if (key.name === 'q') { + onCtrlC?.(); + return; + } + return; + } + + // attached mode + if (key.name === 'escape' || (key.ctrl && key.name === ']')) { + detach(); + return; + } + if (key.ctrl && key.name === 'd') { + const w = focus != null ? workers.get(focus) : null; + try { + w?.stdin?.end(); + } catch {} + return; + } + + // forward keystrokes to child + const w = focus != null ? workers.get(focus) : null; + if (!w || !w.stdin) return; + const seq = (key as any).sequence ?? str ?? ''; + if (seq) { + try { + w.stdin.write(seq); + } catch {} + } + }; + + // Raw bytes fallback (usually not hit because keypress handles it) + const onData = (chunk: Buffer) => { + if (mode === 'attached' && focus != null) { + const w = workers.get(focus); + try { + w?.stdin?.write(chunk); + } catch {} + } + }; + + const cleanup = () => { + stdin.off('keypress', onKey); + stdin.off('data', onData); + stdin.setRawMode?.(false); + process.stdout.write('\x1b[?25h'); + }; + + stdin.on('keypress', onKey); + stdin.on('data', onData); + + return cleanup; +} diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts new file mode 100644 index 00000000..04f205f6 --- /dev/null +++ b/src/lib/pooling/logRotation.ts @@ -0,0 +1,39 @@ +// logRotation.ts +import { readdirSync, statSync, truncateSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; + +const WORKER_LOG_RE = /^worker-\d+\.(?:log|out\.log|err\.log)$/; + +/** + * Reset worker logs in the given directory. + * mode: + * - "truncate": empty files but keep them (best if tails are open) + * - "delete": remove files entirely (simplest if no tails yet) + * + * @param logDir + * @param mode + */ +export function resetWorkerLogs( + logDir: string, + mode: 'truncate' | 'delete' = 'truncate', +): void { + let entries: string[] = []; + try { + entries = readdirSync(logDir); + } catch { + return; + } + + for (const name of entries) { + if (!WORKER_LOG_RE.test(name)) continue; + const p = join(logDir, name); + try { + const st = statSync(p); + if (!st.isFile()) continue; + if (mode === 'truncate') truncateSync(p, 0); + else rmSync(p, { force: true }); + } catch { + // ignore individual failures + } + } +} diff --git a/src/lib/pooling/openTerminal.ts b/src/lib/pooling/openTerminal.ts new file mode 100644 index 00000000..28eca736 --- /dev/null +++ b/src/lib/pooling/openTerminal.ts @@ -0,0 +1,80 @@ +// openTerminal.ts +import { spawn } from 'node:child_process'; +import { platform } from 'node:os'; + +/** + * + * @param p + */ +export function shellEscape(p: string): string { + return `'${String(p).replace(/'/g, "'\\''")}'`; +} + +/** + * + * @param paths + * @param title + * @param isSilent + */ +export function openLogTailWindowMulti( + paths: string[], + title: string, + isSilent: boolean, +): void { + if (isSilent) return; + const p = platform(); + try { + if (p === 'darwin') { + const tails = paths.map(shellEscape).join(' -f '); + const script = ` + tell application "Terminal" + activate + do script "printf '\\e]0;${title}\\a'; tail -n +1 -f ${tails}" + end tell + `; + spawn('osascript', ['-e', script], { stdio: 'ignore', detached: true }); + return; + } + if (p === 'win32') { + const arrayLiteral = `@(${paths + .map((x) => `'${x.replace(/'/g, "''")}'`) + .join(',')})`; + const ps = [ + 'powershell', + '-NoExit', + '-Command', + `Write-Host '${title}'; $paths = ${arrayLiteral}; Get-Content -Path $paths -Tail 200 -Wait`, + ]; + spawn('cmd.exe', ['/c', 'start', ...ps], { + stdio: 'ignore', + detached: true, + }).unref(); + return; + } + const tails = paths.map(shellEscape).join(' -f '); + try { + spawn( + 'gnome-terminal', + [ + '--', + 'bash', + '-lc', + `printf '\\e]0;${title}\\a'; tail -n +1 -f ${tails}`, + ], + { + stdio: 'ignore', + detached: true, + }, + ).unref(); + } catch { + spawn( + 'xterm', + ['-title', title, '-e', `tail -n +1 -f ${paths.join(' ')}`], + { + stdio: 'ignore', + detached: true, + }, + ).unref(); + } + } catch {} +} diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts new file mode 100644 index 00000000..b897db59 --- /dev/null +++ b/src/lib/pooling/renderDashboard.ts @@ -0,0 +1,70 @@ +// renderer.ts +import { basename } from 'node:path'; +import * as readline from 'node:readline'; + +export interface WorkerState { + /** */ + busy: boolean; + /** */ + file?: string | null; + /** */ + startedAt?: number | null; +} + +let lastFrame = ''; + +/** + * + * @param poolSize + * @param cpuCount + * @param total + * @param completed + * @param failed + * @param workerState + */ +export function renderDashboard( + poolSize: number, + cpuCount: number, + total: number, + completed: number, + failed: number, + workerState: Map, +): void { + const inProgress = [...workerState.values()].filter((s) => s.busy).length; + const pct = + total === 0 + ? 100 + : Math.floor(((completed + failed) / Math.max(1, total)) * 100); + const barWidth = 40; + const filled = Math.floor((pct / 100) * barWidth); + const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); + + const lines = [...workerState.entries()].map(([id, s]) => { + const label = s.busy ? 'WORKING' : 'IDLE '; + const fname = s.file ? basename(s.file) : '-'; + const elapsed = s.startedAt + ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` + : '-'; + return ` [w${id}] ${label} | ${fname} | ${elapsed}`; + }); + + const maxDigit = Math.min(poolSize - 1, 9); + const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + const frame = [ + `Parallel uploader — ${poolSize} workers (CPU avail: ${cpuCount})`, + `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, + `[${bar}] ${pct}%`, + '', + ...lines, + '', + `Hotkeys: [${digitRange}] attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit`, + ].join('\n'); + + if (frame === lastFrame) return; + lastFrame = frame; + + process.stdout.write('\x1b[?25l'); + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + process.stdout.write(`${frame}\n`); +} diff --git a/src/lib/pooling/spawnWorkerProcess.ts b/src/lib/pooling/spawnWorkerProcess.ts new file mode 100644 index 00000000..af487ec2 --- /dev/null +++ b/src/lib/pooling/spawnWorkerProcess.ts @@ -0,0 +1,106 @@ +// spawnWorkerProcess.ts +import { fork, type ChildProcess } from 'node:child_process'; +import { join } from 'node:path'; +import { createWriteStream } from 'node:fs'; +import { openLogTailWindowMulti } from './openTerminal'; + +export const CHILD_FLAG = '--child-upload-preferences'; + +export interface WorkerLogPaths { + /** */ + structuredPath: string; + /** */ + outPath: string; + /** */ + errPath: string; +} + +// Symbol key so we can stash/retrieve paths on the child proc safely +const LOG_PATHS_SYM: unique symbol = Symbol('workerLogPaths'); + +/** + * + * @param pathStr + */ +function ensureLogFile(pathStr: string) { + try { + const w = createWriteStream(pathStr, { flags: 'a' }); + w.end(); + } catch {} +} + +/** + * Spawn a worker process with piped stdio and persisted logs. + * - stdin/stdout/stderr = 'pipe' so we can attach interactively + * - stdout/stderr also go to per-worker files (.out/.err) + * - worker writes structured logs to WORKER_LOG (structuredPath) + * + * @param id + * @param modulePath + * @param logDir + * @param openLogWindows + * @param isSilent + */ +export function spawnWorkerProcess( + id: number, + modulePath: string, + logDir: string, + openLogWindows: boolean, + isSilent: boolean, +): ChildProcess { + const structuredPath = join(logDir, `worker-${id}.log`); + const outPath = join(logDir, `worker-${id}.out.log`); + const errPath = join(logDir, `worker-${id}.err.log`); + [structuredPath, outPath, errPath].forEach(ensureLogFile); + + const child = fork(modulePath, ['--child-upload-preferences'], { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + env: { ...process.env, WORKER_ID: String(id), WORKER_LOG: structuredPath }, + execArgv: process.execArgv, + }); + + // Persist stdout/stderr to files + const outStream = createWriteStream(outPath, { flags: 'a' }); + const errStream = createWriteStream(errPath, { flags: 'a' }); + child.stdout?.pipe(outStream); + child.stderr?.pipe(errStream); + + // Headers so tail windows show something immediately + outStream.write( + `[parent] stdout capture active for w${id} (pid ${child.pid})\n`, + ); + errStream.write( + `[parent] stderr capture active for w${id} (pid ${child.pid})\n`, + ); + + // Stash log path metadata on the child + (child as any)[LOG_PATHS_SYM] = { + structuredPath, + outPath, + errPath, + } as WorkerLogPaths; + + if (openLogWindows) { + openLogTailWindowMulti( + [structuredPath, outPath, errPath], + `worker-${id}`, + isSilent, + ); + } + + outStream.on('error', () => {}); + errStream.on('error', () => {}); + + return child; +} + +/** + * Retrieve the paths we stashed on the child. + * + * @param child + */ +export function getWorkerLogPaths( + child: ChildProcess, +): WorkerLogPaths | undefined { + return (child as any)[LOG_PATHS_SYM] as WorkerLogPaths | undefined; +} diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 6a77d9b8..6b841c52 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -1,7 +1,7 @@ import { PreferenceQueryResponseItem } from '@transcend-io/privacy-types'; import type { Got } from 'got'; import colors from 'colors'; -import cliProgress from 'cli-progress'; +// import cliProgress from 'cli-progress'; import { chunk } from 'lodash-es'; import { decodeCodec } from '@transcend-io/type-utils'; import * as t from 'io-ts'; @@ -43,6 +43,8 @@ export async function getPreferencesForIdentifiers( identifiers: { /** The value of the identifier */ value: string; + /** The name of the identifier */ + name: string; }[]; /** The partition key to look up */ partitionKey: string; @@ -55,13 +57,13 @@ export async function getPreferencesForIdentifiers( // create a new progress bar instance and use shades_classic theme const t0 = new Date().getTime(); - const progressBar = new cliProgress.SingleBar( - {}, - cliProgress.Presets.shades_classic, - ); - if (!skipLogging) { - progressBar.start(identifiers.length, 0); - } + // const progressBar = new cliProgress.SingleBar( + // {}, + // cliProgress.Presets.shades_classic, + // ); + // if (!skipLogging) { + // progressBar.start(identifiers.length, 0); + // } let total = 0; await map( @@ -86,7 +88,15 @@ export async function getPreferencesForIdentifiers( const result = decodeCodec(PreferenceRecordsQueryResponse, rawResult); results.push(...result.nodes); total += group.length; - progressBar.update(total); + // progressBar.update(total); + // log every 1000 + if (total % 1000 === 0 && !skipLogging) { + logger.info( + colors.green( + `Fetched ${total}/${identifiers.length} user preferences from partition ${partitionKey}`, + ), + ); + } break; // Exit loop if successful } catch (err) { attempts += 1; @@ -114,7 +124,7 @@ export async function getPreferencesForIdentifiers( }, ); - progressBar.stop(); + // progressBar.stop(); const t1 = new Date().getTime(); const totalTime = t1 - t0; diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 090afa1f..7406fd12 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -110,7 +110,7 @@ export async function parsePreferenceManagementCsvWithCache( row: pref, columnToIdentifier: currentColumnToIdentifierMap, }).map((col) => ({ - name: currentColumnToIdentifierMap.name, + name: currentColumnToIdentifierMap[col].name, value: pref[col], })), ); diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 9ea57d0a..2a8a1544 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -253,16 +253,16 @@ export async function uploadPreferenceManagementPreferencesInteractive({ const t0 = new Date().getTime(); // create a new progress bar instance and use shades_classic theme - const progressBar = new cliProgress.SingleBar( - {}, - cliProgress.Presets.shades_classic, - ); + // const progressBar = new cliProgress.SingleBar( + // {}, + // cliProgress.Presets.shades_classic, + // ); // Build a GraphQL client let total = 0; const updatesToRun = Object.entries(pendingUpdates); const chunkedUpdates = chunk(updatesToRun, skipWorkflowTriggers ? 50 : 10); - progressBar.start(updatesToRun.length, 0); + // progressBar.start(updatesToRun.length, 0); await map( chunkedUpdates, async (currentChunk) => { @@ -307,14 +307,22 @@ export async function uploadPreferenceManagementPreferencesInteractive({ } total += currentChunk.length; - progressBar.update(total); + // progressBar.update(total); + // log every 1000 + if (total % 1000 === 0) { + logger.info( + colors.green( + `Uploaded ${total}/${updatesToRun.length} user preferences to partition ${partition}`, + ), + ); + } }, { concurrency: 80, }, ); - progressBar.stop(); + // progressBar.stop(); const t1 = new Date().getTime(); const totalTime = t1 - t0; logger.info( From fba88091ef4f76cb08f4606abd7724f6ae6f79c3 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 15 Aug 2025 23:04:22 -0700 Subject: [PATCH 12/72] basically working --- .../consent/upload-preferences/impl.ts | 277 +++++++++++++++--- .../consent/upload-preferences/runChild.ts | 10 +- src/lib/pooling/renderDashboard.ts | 66 ++++- ...IfPendingPreferenceUpdatesCauseConflict.ts | 63 +++- src/lib/preference-management/codecs.ts | 4 + .../parsePreferenceIdentifiersFromCsv.ts | 25 +- .../parsePreferenceManagementCsv.ts | 38 +++ ...ferenceManagementPreferencesInteractive.ts | 24 +- 8 files changed, 438 insertions(+), 69 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 2cc94d74..f22bba9c 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -1,12 +1,22 @@ -// main.ts +// impl.ts import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; import { join } from 'node:path'; -import { mkdirSync } from 'node:fs'; +import { + mkdirSync, + existsSync, + readFileSync, + readdirSync, + statSync, +} from 'node:fs'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; -import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; +import { + computeReceiptsFolder, + computeSchemaFile, + getFilePrefix, +} from './computeFiles'; import { collectCsvFilesOrExit } from './collectCsvFilesOrExit'; import { availableParallelism } from 'node:os'; @@ -70,9 +80,7 @@ export type TaskCommonOpts = Pick< receiptsFolder: string; }; -/** - * In CJS, __filename is available; use it as the path to fork. - */ +/** In CJS, __filename is available; use it as the path to fork. */ function getCurrentModulePath(): string { // @ts-ignore - __filename is present in CJS/ts-node if (typeof __filename !== 'undefined') return __filename as unknown as string; @@ -85,7 +93,10 @@ function getCurrentModulePath(): string { * @param concurrency * @param filesCount */ -function computePoolSize(concurrency: number | undefined, filesCount: number) { +function computePoolSize( + concurrency: number | undefined, + filesCount: number, +): { poolSize: number; cpuCount: number } { const cpuCount = Math.max(1, availableParallelism?.() ?? 1); const desired = typeof concurrency === 'number' && concurrency > 0 @@ -94,6 +105,104 @@ function computePoolSize(concurrency: number | undefined, filesCount: number) { return { poolSize: desired, cpuCount }; } +/** + * Find the receipt JSON for a given input file (supports suffixes like __1). + * + * @param receiptsFolder + * @param filePath + */ +function resolveReceiptPath( + receiptsFolder: string, + filePath: string, +): string | null { + const base = `${getFilePrefix(filePath)}-receipts.json`; + const exact = join(receiptsFolder, base); + if (existsSync(exact)) return exact; + + const prefix = `${getFilePrefix(filePath)}-receipts`; + try { + const entries = readdirSync(receiptsFolder) + .filter((n) => n.startsWith(prefix) && n.endsWith('.json')) + .map((name) => { + const full = join(receiptsFolder, name); + let mtime = 0; + try { + mtime = statSync(full).mtimeMs; + } catch {} + return { full, mtime }; + }) + .sort((a, b) => b.mtime - a.mtime); + return entries[0]?.full ?? null; + } catch { + return null; + } +} + +/** Totals union types for renderer */ +type UploadModeTotals = { + mode: 'upload'; + success: number; + skipped: number; + error: number; +}; +type CheckModeTotals = { + mode: 'check'; + totalPending: number; + pendingConflicts: number; + pendingSafe: number; + skipped: number; +}; +type AnyTotals = UploadModeTotals | CheckModeTotals; + +/** + * Summarize receipt based on skipExistingRecordCheck flag (skipped counted in both modes). + * + * @param receiptPath + * @param dryRun + */ +function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { + try { + const raw = readFileSync(receiptPath, 'utf8'); + const json = JSON.parse(raw) as any; + + const skippedCount = Object.values( + json?.skippedUpdated ?? json?.skippedUpdates ?? {}, + ).length; + + if (!dryRun) { + // Post-upload view: show final results + const success = Object.values(json?.successfulUpdates ?? {}).length; + const failed = Object.values(json?.failingUpdates ?? {}).length; + return { mode: 'upload', success, skipped: skippedCount, error: failed }; + } + + // Pre-upload check view: show pending breakdown + const totalPending = Object.values(json?.pendingUpdates ?? {}).length; + const pendingConflicts = Object.values( + json?.pendingConflictUpdates ?? {}, + ).length; + const pendingSafe = Object.values(json?.pendingSafeUpdates ?? {}).length; + + return { + mode: 'check', + totalPending, + pendingConflicts, + pendingSafe, + skipped: skippedCount, + }; + } catch { + return !dryRun + ? { mode: 'upload', success: 0, skipped: 0, error: 0 } + : { + mode: 'check', + totalPending: 0, + pendingConflicts: 0, + pendingSafe: 0, + skipped: 0, + }; + } +} + export async function uploadPreferences( this: LocalContext, { @@ -118,7 +227,6 @@ export async function uploadPreferences( columnsToIgnore = [], }: UploadPreferencesCommandFlags, ): Promise { - // Build the set of CSV files we need to process const files = collectCsvFilesOrExit(directory, file, this); doneInputValidation(this.process.exit); @@ -144,7 +252,7 @@ export async function uploadPreferences( // Size the worker pool const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); const common: TaskCommonOpts = { - /* same as before */ schemaFile, + schemaFile, receiptsFolder, auth, sombraAuth, @@ -162,46 +270,83 @@ export async function uploadPreferences( columnsToIgnore, }; + // ---- Worker pool lifecycle ---- const LOG_DIR = join(receiptsFolder, 'logs'); mkdirSync(LOG_DIR, { recursive: true }); + // Reset logs ONCE at start; then always append const RESET_MODE = - (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'delete'; // FIXME + (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate'; resetWorkerLogs(LOG_DIR, RESET_MODE); const modulePath = getCurrentModulePath(); + const workers = new Map(); const workerState = new Map(); const pending = [...files]; const totals = { completed: 0, failed: 0 }; + // Initialize totals in correct mode + const agg: AnyTotals = !dryRun + ? { mode: 'upload', success: 0, skipped: 0, error: 0 } + : { + mode: 'check', + totalPending: 0, + pendingConflicts: 0, + pendingSafe: 0, + skipped: 0, + }; + let dashboardPaused = false; - const repaint = () => { - if (!dashboardPaused) { - renderDashboard( - poolSize, - cpuCount, - files.length, - totals.completed, - totals.failed, - workerState, - ); - } + const repaint = (final = false) => { + if (dashboardPaused && !final) return; + renderDashboard( + poolSize, + cpuCount, + files.length, + totals.completed, + totals.failed, + workerState, + agg, + { final }, + ); }; + /** + * Assign one task to a specific worker id if available + * + * @param id + */ const assignWorkToWorker = (id: number) => { const w = workers.get(id); if (!w) return; const filePath = pending.shift(); if (!filePath) { - workerState.set(id, { busy: false, file: null, startedAt: null }); + const prev = workerState.get(id) || ({} as WorkerState); + workerState.set(id, { + ...prev, + busy: false, + file: null, + startedAt: null, + }); return; } workerState.set(id, { busy: true, file: filePath, startedAt: Date.now() }); w.send?.({ type: 'task', payload: { filePath, options: common } }); }; - // spawn pool + /** Fill all idle workers while there is pending work */ + const refillIdleWorkers = () => { + for (const [id] of workers) { + const st = workerState.get(id); + if (!st || !st.busy) { + if (pending.length === 0) break; + assignWorkToWorker(id); + } + } + }; + + // Spawn the initial pool for (let i = 0; i < poolSize; i += 1) { const child = spawnWorkerProcess(i, modulePath, LOG_DIR, true, isSilent); workers.set(i, child); @@ -209,18 +354,43 @@ export async function uploadPreferences( child.on('message', (msg: any) => { if (!msg || typeof msg !== 'object') return; + if (msg.type === 'ready') { - assignWorkToWorker(i); + refillIdleWorkers(); // fill as many as possible when workers come up repaint(); - } else if (msg.type === 'result') { - const { ok } = msg.payload || {}; + return; + } + + if (msg.type === 'result') { + const { ok, filePath, receiptFilepath } = msg.payload || {}; if (ok) totals.completed += 1; else totals.failed += 1; + + // Update global receipt totals + const resolved = + (typeof receiptFilepath === 'string' && receiptFilepath) || + resolveReceiptPath(common.receiptsFolder, filePath); + if (resolved) { + const summary = summarizeReceipt(resolved, common.dryRun); + if (summary.mode === 'upload' && agg.mode === 'upload') { + agg.success += summary.success; + agg.skipped += summary.skipped; + agg.error += summary.error; + } else if (summary.mode === 'check' && agg.mode === 'check') { + agg.totalPending += summary.totalPending; + agg.pendingConflicts += summary.pendingConflicts; + agg.pendingSafe += summary.pendingSafe; + agg.skipped += summary.skipped; + } + } + + // Mark idle and refill workerState.set(i, { busy: false, file: null, startedAt: null }); - assignWorkToWorker(i); + refillIdleWorkers(); repaint(); } }); + child.on('exit', () => { workers.delete(i); workerState.set(i, { busy: false, file: null, startedAt: null }); @@ -228,17 +398,21 @@ export async function uploadPreferences( }); } - const renderInterval = setInterval(repaint, 350); + const renderInterval = setInterval(() => repaint(false), 350); - // graceful Ctrl+C + // graceful Ctrl+C (declare cleanup first so we can reference it) + let cleanupSwitcher: () => void = () => {}; const onSigint = () => { clearInterval(renderInterval); cleanupSwitcher(); process.stdout.write('\nStopping workers...\n'); - for (const [, w] of workers) { - try { - w.send?.({ type: 'shutdown' }); - } catch {} + for (const [id, w] of workers) { + const st = workerState.get(id); + if (st && !st.busy) { + try { + w.send?.({ type: 'shutdown' }); + } catch {} + } try { w.kill('SIGTERM'); } catch {} @@ -259,7 +433,7 @@ export async function uploadPreferences( `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT • Ctrl+D EOF)\n`, ); }; - const cleanupSwitcher = installInteractiveSwitcher({ + cleanupSwitcher = installInteractiveSwitcher({ workers, onAttach: attachScreen, onDetach: detachScreen, @@ -286,15 +460,42 @@ export async function uploadPreferences( // wait for completion await new Promise((resolve) => { const check = setInterval(() => { + // If the queue is empty, request shutdown for **idle** workers only. + if (pending.length === 0) { + for (const [id, w] of workers) { + const st = workerState.get(id); + if (st && !st.busy) { + try { + w.send?.({ type: 'shutdown' }); + } catch {} + } + } + } + // Done when all workers have exited if (pending.length === 0 && workers.size === 0) { clearInterval(check); clearInterval(renderInterval); - detachScreen(); cleanupSwitcher(); - process.stdout.write('\nAll done.\n'); + + // Persist the final snapshot (don’t clear) + repaint(true); + + if ((agg as AnyTotals).mode === 'upload') { + const a = agg as UploadModeTotals; + process.stdout.write( + colors.green( + `\nAll done. Success:${a.success} Skipped:${a.skipped} Error:${a.error}\n`, + ), + ); + } else { + const a = agg as CheckModeTotals; + process.stdout.write( + colors.green( + `\nAll done. Pending:${a.totalPending} PendingConflicts:${a.pendingConflicts} PendingSafe:${a.pendingSafe} Skipped:${a.skipped}\n`, + ), + ); + } resolve(); - } else if (pending.length === 0) { - for (const [, w] of workers) w.send?.({ type: 'shutdown' }); } }, 300); }); diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 4f197a64..8dd98af4 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -4,6 +4,7 @@ import { join, dirname } from 'node:path'; import { uploadPreferenceManagementPreferencesInteractive } from '../../../lib/preference-management'; import { getFilePrefix } from './computeFiles'; import { splitCsvToList } from '../../../lib/requests'; +import type { TaskCommonOpts } from './impl'; export async function runChild(): Promise { const workerId = Number(process.env.WORKER_ID || '0'); @@ -25,6 +26,7 @@ export async function runChild(): Promise { process.on('message', async (msg: any) => { if (!msg || typeof msg !== 'object') return; + if (msg.type === 'task') { const { filePath, options } = msg.payload as { filePath: string; @@ -61,7 +63,11 @@ export async function runChild(): Promise { console.log(`[w${workerId}] DONE ${filePath}`); log(`SUCCESS ${filePath}`); - process.send?.({ type: 'result', payload: { ok: true, filePath } }); + + process.send?.({ + type: 'result', + payload: { ok: true, filePath, receiptFilepath }, + }); } catch (err: any) { const e = err?.stack || err?.message || String(err); console.error( @@ -70,7 +76,7 @@ export async function runChild(): Promise { log(`FAIL ${filePath}\n${e}`); process.send?.({ type: 'result', - payload: { ok: false, filePath, error: e }, + payload: { ok: false, filePath, error: e, receiptFilepath }, }); } } else if (msg.type === 'shutdown') { diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index b897db59..09d05f9a 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -13,6 +13,36 @@ export interface WorkerState { let lastFrame = ''; +/** + * + */ +type UploadModeTotals = { + /** */ mode: 'upload' /** */; + success: number /** */; + skipped: number /** */; + error: number; +}; +/** + * + */ +type CheckModeTotals = { + /** */ mode: 'check' /** */; + pendingConflicts: number /** */; + pendingSafe: number /** */; + totalPending: number /** */; + skipped: number; +}; +/** + * + */ +type AnyTotals = UploadModeTotals | CheckModeTotals; +/** + * + */ +type RenderOpts = { + /** */ final?: boolean; +}; + /** * * @param poolSize @@ -21,6 +51,8 @@ let lastFrame = ''; * @param completed * @param failed * @param workerState + * @param totals + * @param opts */ export function renderDashboard( poolSize: number, @@ -29,6 +61,8 @@ export function renderDashboard( completed: number, failed: number, workerState: Map, + totals?: AnyTotals, + opts?: RenderOpts, ): void { const inProgress = [...workerState.values()].filter((s) => s.busy).length; const pct = @@ -50,21 +84,43 @@ export function renderDashboard( const maxDigit = Math.min(poolSize - 1, 9); const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + + let totalsLine = ''; + if (totals) { + if (totals.mode === 'upload') { + totalsLine = `Receipts totals — Success: ${totals.success} Skipped: ${totals.skipped} Error: ${totals.error}`; + } else { + totalsLine = + `Receipts totals — Pending: ${totals.totalPending} PendingConflicts: ${totals.pendingConflicts} ` + + `PendingSafe: ${totals.pendingSafe} Skipped: ${totals.skipped}`; + } + } + const frame = [ `Parallel uploader — ${poolSize} workers (CPU avail: ${cpuCount})`, `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, + totalsLine, `[${bar}] ${pct}%`, '', ...lines, '', `Hotkeys: [${digitRange}] attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit`, - ].join('\n'); + ] + .filter(Boolean) + .join('\n'); - if (frame === lastFrame) return; + const isFinal = !!opts?.final; + + if (!isFinal && frame === lastFrame) return; lastFrame = frame; - process.stdout.write('\x1b[?25l'); - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); + if (!isFinal) { + process.stdout.write('\x1b[?25l'); + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + } else { + process.stdout.write('\x1b[?25h'); + } + process.stdout.write(`${frame}\n`); } diff --git a/src/lib/preference-management/checkIfPendingPreferenceUpdatesCauseConflict.ts b/src/lib/preference-management/checkIfPendingPreferenceUpdatesCauseConflict.ts index a14b4845..3c1d57eb 100644 --- a/src/lib/preference-management/checkIfPendingPreferenceUpdatesCauseConflict.ts +++ b/src/lib/preference-management/checkIfPendingPreferenceUpdatesCauseConflict.ts @@ -4,6 +4,7 @@ import { PreferenceTopicType, } from '@transcend-io/privacy-types'; import { PreferenceTopic } from '../graphql'; +import { logger } from '../../logger'; /** * Check if the pending set of updates will result in a change of @@ -16,6 +17,7 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ currentConsentRecord, pendingUpdates, preferenceTopics, + log, }: { /** The current consent record */ currentConsentRecord: PreferenceQueryResponseItem; @@ -25,6 +27,8 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ }; /** The preference topic configurations */ preferenceTopics: PreferenceTopic[]; + /** Whether to log the conflict */ + log?: boolean; }): boolean { // Check if any update has conflict return !!Object.entries(pendingUpdates).find( @@ -36,11 +40,22 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ // If no purpose exists, then it is not a conflict if (!currentPurpose) { + if (log) { + logger.warn( + `No existing purpose found for ${purposeName} in consent record for ${currentConsentRecord.userId}.`, + ); + } return false; } // If purpose.enabled value is off, this is a conflict if (currentPurpose.enabled !== enabled) { + if (log) { + logger.warn( + `Purpose ${purposeName} enabled value conflict for user ${currentConsentRecord.userId}. ` + + `Pending Value: ${enabled}, Current Value: ${currentPurpose.enabled}`, + ); + } return true; } @@ -53,6 +68,12 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ // if no topic exists, no conflict if (!currentPreference) { + if (log) { + logger.warn( + `No existing preference found for topic ${topic} in purpose ` + + `${purposeName} for user ${currentConsentRecord.userId}.`, + ); + } return false; } @@ -65,13 +86,31 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ } // Handle comparison based on type + let boolMatch: boolean; + let selectMatch: boolean; switch (preferenceTopic.type) { case PreferenceTopicType.Boolean: - return ( - currentPreference.choice.booleanValue !== choice.booleanValue - ); + boolMatch = + currentPreference.choice.booleanValue !== choice.booleanValue; + if (log) { + logger.warn( + `Preference topic ${topic} boolean value conflict for user ` + + `${currentConsentRecord.userId}. Expected: ${choice.booleanValue}, ` + + `Found: ${currentPreference.choice.booleanValue}`, + ); + } + return boolMatch; case PreferenceTopicType.Select: - return currentPreference.choice.selectValue !== choice.selectValue; + selectMatch = + currentPreference.choice.selectValue !== choice.selectValue; + if (log) { + logger.warn( + `Preference topic ${topic} select value conflict for user ` + + `${currentConsentRecord.userId}. Expected: ${choice.selectValue}, ` + + `Found: ${currentPreference.choice.selectValue}`, + ); + } + return selectMatch; case PreferenceTopicType.MultiSelect: // eslint-disable-next-line no-case-declarations const sortedCurrentValues = ( @@ -79,10 +118,20 @@ export function checkIfPendingPreferenceUpdatesCauseConflict({ ).sort(); // eslint-disable-next-line no-case-declarations const sortedNewValues = (choice.selectValues || []).sort(); - return ( + selectMatch = sortedCurrentValues.length !== sortedNewValues.length || - !sortedCurrentValues.every((x, i) => x === sortedNewValues[i]) - ); + !sortedCurrentValues.every((x, i) => x === sortedNewValues[i]); + if (log) { + logger.warn( + `Preference topic ${topic} multi-select value conflict for user ` + + `${ + currentConsentRecord.userId + }. Expected: ${sortedNewValues.join( + ', ', + )}, Found: ${sortedCurrentValues.join(', ')}`, + ); + } + return selectMatch; default: throw new Error( `Unknown preference topic type: ${preferenceTopic.type}`, diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index dc9b0dcc..f8365c95 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -115,6 +115,10 @@ export const RequestUploadReceipts = t.type({ * Mapping from userId to the upload metadata */ pendingUpdates: t.record(t.string, PreferenceUpdateItem), + /** + * The successful updates + */ + successfulUpdates: t.record(t.string, PreferenceUpdateItem), }); /** Override type */ diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 9aa5b66c..2076e8d6 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -229,9 +229,16 @@ export function getUniquePreferenceIdentifierNamesFromRow({ /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; }): string[] { - return Object.keys(columnToIdentifier).filter( + // FIXME remove email logic + const columns = Object.keys(columnToIdentifier).filter( (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, ); + // if email is present move it to front of list + if (columns.includes('email')) { + columns.splice(columns.indexOf('email'), 1); + columns.unshift('email'); + } + return columns; } /** @@ -239,7 +246,7 @@ export function getUniquePreferenceIdentifierNamesFromRow({ * * @param preferences - List of preferences * @returns The updated preferences with Transcend ID added - * // TODO: Remove this COSTCO specific logic + * // FIXME: Remove this COSTCO specific logic */ export async function addTranscendIdToPreferences( preferences: Record[], @@ -252,10 +259,12 @@ export async function addTranscendIdToPreferences( // return preferences; // } // Add a transcendent ID to each preference if it doesn't already exist - return preferences.map((pref) => { - if (!pref.person_id) { - throw new Error('person_id is required for this upload.'); - } - return { ...pref, transcendID: pref.person_id }; - }); + return preferences.map((pref) => ({ + ...pref, + person_id: pref.person_id !== '-2' ? pref.person_id : '', + transcendID: + pref.person_id && pref.person_id !== '-2' + ? pref.person_id + : pref.member_id, + })); } diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 7406fd12..7ba389e4 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -18,6 +18,7 @@ import { import { parsePreferenceAndPurposeValuesFromCsv } from './parsePreferenceAndPurposeValuesFromCsv'; import { checkIfPendingPreferenceUpdatesAreNoOp } from './checkIfPendingPreferenceUpdatesAreNoOp'; import { checkIfPendingPreferenceUpdatesCauseConflict } from './checkIfPendingPreferenceUpdatesCauseConflict'; +import type { ObjByString } from '@transcend-io/type-utils'; /** * Parse a file into the cache @@ -74,10 +75,12 @@ export async function parsePreferenceManagementCsvWithCache( // Read in the file logger.info(colors.magenta(`Reading in file: "${file}"`)); let preferences = readCsv(file, t.record(t.string, t.string)); + console.log(`${preferences.length}, '1'`); // TODO: Remove this COSTCO specific logic const updatedPreferences = await addTranscendIdToPreferences(preferences); preferences = updatedPreferences; + console.log(`${preferences.length}, '2'`); // Validate that all timestamps are present in the file await parsePreferenceFileFormatFromCsv(preferences, schemaState); @@ -90,6 +93,7 @@ export async function parsePreferenceManagementCsvWithCache( identifierColumns, }); preferences = result.preferences; + console.log(`${preferences.length}, '3'`); // Ensure all other columns are mapped to purpose and preference slug values await parsePreferenceAndPurposeValuesFromCsv(preferences, schemaState, { @@ -130,6 +134,9 @@ export async function parsePreferenceManagementCsvWithCache( const skippedUpdates: RequestUploadReceipts['skippedUpdates'] = {}; // Process each row + console.log(`${preferences.length}, '4'`); + + const seenAlready: Record = {}; preferences.forEach((pref) => { // Get the userIds that could be the primary key of the consent record const possiblePrimaryKeys = getUniquePreferenceIdentifierNamesFromRow({ @@ -152,6 +159,35 @@ export async function parsePreferenceManagementCsvWithCache( // If consent record is found use it, otherwise use the first unique identifier const primaryKey = currentConsentRecord?.userId || possiblePrimaryKeys[0]; + // Ensure this is unique + if (seenAlready[primaryKey]) { + if ( + !Object.entries(pref).every( + ([key, value]) => seenAlready[primaryKey][key] === value, + ) + ) { + throw new Error( + `Duplicate primary key found: "${primaryKey}" in row: ${JSON.stringify( + pref, + )}, previously seen in row: ${seenAlready[primaryKey]}`, + ); + } else { + console.log('SKIPPINNNG'); + skippedUpdates[primaryKey] = pref; + logger.warn( + colors.yellow( + `Duplicate primary key found: "${primaryKey}" in row: ${JSON.stringify( + pref, + )}, previously seen in row: ${ + seenAlready[primaryKey] + }. Skipping duplicate.`, + ), + ); + return; + } + } + seenAlready[primaryKey] = pref; + if (forceTriggerWorkflows && !currentConsentRecord) { throw new Error( `No existing consent record found for user with ids: ${possiblePrimaryKeys.join( @@ -172,6 +208,7 @@ export async function parsePreferenceManagementCsvWithCache( }) && !forceTriggerWorkflows ) { + console.log(`HERERERE${primaryKey}`); skippedUpdates[primaryKey] = pref; return; } @@ -183,6 +220,7 @@ export async function parsePreferenceManagementCsvWithCache( currentConsentRecord, pendingUpdates, preferenceTopics, + log: false, // FIXME }) ) { pendingConflictUpdates[primaryKey] = { diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 2a8a1544..f0b5bdbe 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -11,7 +11,6 @@ import colors from 'colors'; import { map } from 'bluebird'; import { chunk } from 'lodash-es'; import { logger } from '../../logger'; -import cliProgress from 'cli-progress'; import { parseAttributesFromString } from '../requests'; import { PersistedState } from '@transcend-io/persisted-state'; import { parsePreferenceManagementCsvWithCache } from './parsePreferenceManagementCsv'; @@ -92,10 +91,11 @@ export async function uploadPreferenceManagementPreferencesInteractive({ receiptFilepath, RequestUploadReceipts, { - pendingSafeUpdates: {}, + failingUpdates: {}, pendingConflictUpdates: {}, skippedUpdates: {}, - failingUpdates: {}, + pendingSafeUpdates: {}, + successfulUpdates: {}, pendingUpdates: {}, lastFetchedAt: new Date().toISOString(), }, @@ -226,8 +226,10 @@ export async function uploadPreferenceManagementPreferencesInteractive({ })), }; }); + // FIXME restart better await uploadState.setValue(pendingUpdates, 'pendingUpdates'); await uploadState.setValue({}, 'failingUpdates'); + await uploadState.setValue({}, 'successfulUpdates'); // Exist early if dry run if (dryRun) { @@ -252,16 +254,11 @@ export async function uploadPreferenceManagementPreferencesInteractive({ // Time duration const t0 = new Date().getTime(); - // create a new progress bar instance and use shades_classic theme - // const progressBar = new cliProgress.SingleBar( - // {}, - // cliProgress.Presets.shades_classic, - // ); - // Build a GraphQL client let total = 0; const updatesToRun = Object.entries(pendingUpdates); const chunkedUpdates = chunk(updatesToRun, skipWorkflowTriggers ? 50 : 10); + const successfulUpdates: Record = {}; // progressBar.start(updatesToRun.length, 0); await map( chunkedUpdates, @@ -277,6 +274,9 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }, }) .json(); + currentChunk.forEach(([userId, update]) => { + successfulUpdates[userId] = update; + }); } catch (err) { try { const parsed = JSON.parse(err?.response?.body || '{}'); @@ -322,6 +322,12 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }, ); + // FIXME do this on ctrl+c + await uploadState.setValue(successfulUpdates, 'successfulUpdates'); + await uploadState.setValue({}, 'pendingUpdates'); + await uploadState.setValue({}, 'pendingSafeUpdates'); + await uploadState.setValue({}, 'pendingConflictUpdates'); + // progressBar.stop(); const t1 = new Date().getTime(); const totalTime = t1 - t0; From b4a6a523084c796944b82301fde57d2bba5cdc85 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 15 Aug 2025 23:11:14 -0700 Subject: [PATCH 13/72] Working logs --- .../consent/upload-preferences/impl.ts | 134 +++++++++++------- src/lib/pooling/assignWorkToWorker.ts | 6 +- src/lib/pooling/renderDashboard.ts | 32 +++-- 3 files changed, 108 insertions(+), 64 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index f22bba9c..2b440749 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -74,25 +74,16 @@ export type TaskCommonOpts = Pick< | 'identifierColumns' | 'columnsToIgnore' > & { - /** Path to the schema file */ schemaFile: string; - /** Directory to store receipt files */ receiptsFolder: string; }; -/** In CJS, __filename is available; use it as the path to fork. */ function getCurrentModulePath(): string { - // @ts-ignore - __filename is present in CJS/ts-node + // @ts-ignore - __filename exists in CJS/ts-node if (typeof __filename !== 'undefined') return __filename as unknown as string; return process.argv[1]; } -/** - * Compute pool size from flags or CPU count - * - * @param concurrency - * @param filesCount - */ function computePoolSize( concurrency: number | undefined, filesCount: number, @@ -155,7 +146,7 @@ type CheckModeTotals = { type AnyTotals = UploadModeTotals | CheckModeTotals; /** - * Summarize receipt based on skipExistingRecordCheck flag (skipped counted in both modes). + * Summarize receipt (skipped counted in both modes). * * @param receiptPath * @param dryRun @@ -170,13 +161,11 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { ).length; if (!dryRun) { - // Post-upload view: show final results const success = Object.values(json?.successfulUpdates ?? {}).length; const failed = Object.values(json?.failingUpdates ?? {}).length; return { mode: 'upload', success, skipped: skippedCount, error: failed }; } - // Pre-upload check view: show pending breakdown const totalPending = Object.values(json?.pendingUpdates ?? {}).length; const pendingConflicts = Object.values( json?.pendingConflictUpdates ?? {}, @@ -245,11 +234,9 @@ export async function uploadPreferences( ); } - // Resolve I/O targets (receipts + schema) const receiptsFolder = computeReceiptsFolder(receiptFileDir, directory); const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); - // Size the worker pool const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); const common: TaskCommonOpts = { schemaFile, @@ -274,7 +261,6 @@ export async function uploadPreferences( const LOG_DIR = join(receiptsFolder, 'logs'); mkdirSync(LOG_DIR, { recursive: true }); - // Reset logs ONCE at start; then always append const RESET_MODE = (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate'; resetWorkerLogs(LOG_DIR, RESET_MODE); @@ -283,11 +269,15 @@ export async function uploadPreferences( const workers = new Map(); const workerState = new Map(); + const slotLogPaths = new Map< + number, + ReturnType | undefined + >(); const pending = [...files]; const totals = { completed: 0, failed: 0 }; + let activeWorkers = 0; // track only live workers - // Initialize totals in correct mode - const agg: AnyTotals = !dryRun + const agg: AnyTotals = !common.dryRun ? { mode: 'upload', success: 0, skipped: 0, error: 0 } : { mode: 'check', @@ -312,11 +302,6 @@ export async function uploadPreferences( ); }; - /** - * Assign one task to a specific worker id if available - * - * @param id - */ const assignWorkToWorker = (id: number) => { const w = workers.get(id); if (!w) return; @@ -335,7 +320,6 @@ export async function uploadPreferences( w.send?.({ type: 'task', payload: { filePath, options: common } }); }; - /** Fill all idle workers while there is pending work */ const refillIdleWorkers = () => { for (const [id] of workers) { const st = workerState.get(id); @@ -346,17 +330,19 @@ export async function uploadPreferences( } }; - // Spawn the initial pool + // Spawn the pool for (let i = 0; i < poolSize; i += 1) { const child = spawnWorkerProcess(i, modulePath, LOG_DIR, true, isSilent); workers.set(i, child); workerState.set(i, { busy: false, file: null, startedAt: null }); + slotLogPaths.set(i, getWorkerLogPaths(child)); + activeWorkers += 1; child.on('message', (msg: any) => { if (!msg || typeof msg !== 'object') return; if (msg.type === 'ready') { - refillIdleWorkers(); // fill as many as possible when workers come up + refillIdleWorkers(); repaint(); return; } @@ -366,7 +352,6 @@ export async function uploadPreferences( if (ok) totals.completed += 1; else totals.failed += 1; - // Update global receipt totals const resolved = (typeof receiptFilepath === 'string' && receiptFilepath) || resolveReceiptPath(common.receiptsFolder, filePath); @@ -384,7 +369,6 @@ export async function uploadPreferences( } } - // Mark idle and refill workerState.set(i, { busy: false, file: null, startedAt: null }); refillIdleWorkers(); repaint(); @@ -392,7 +376,9 @@ export async function uploadPreferences( }); child.on('exit', () => { - workers.delete(i); + // keep the slot so digits still map to a worker index + activeWorkers -= 1; + // (leave the ChildProcess object in the map; switcher will read logs via slotLogPaths) workerState.set(i, { busy: false, file: null, startedAt: null }); repaint(); }); @@ -400,17 +386,17 @@ export async function uploadPreferences( const renderInterval = setInterval(() => repaint(false), 350); - // graceful Ctrl+C (declare cleanup first so we can reference it) + // graceful Ctrl+C let cleanupSwitcher: () => void = () => {}; const onSigint = () => { clearInterval(renderInterval); cleanupSwitcher(); process.stdout.write('\nStopping workers...\n'); - for (const [id, w] of workers) { - const st = workerState.get(id); - if (st && !st.busy) { + // attempt graceful shutdown for any idles; busy ones will have exited already + for (const [, w] of workers) { + if (w && w.connected && (w as any).channel) { try { - w.send?.({ type: 'shutdown' }); + w.send({ type: 'shutdown' }); } catch {} } try { @@ -428,25 +414,38 @@ export async function uploadPreferences( }; const attachScreen = (id: number) => { dashboardPaused = true; - process.stdout.write('\x1b[2J\x1b[H'); // clear + process.stdout.write('\x1b[2J\x1b[H'); process.stdout.write( - `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT • Ctrl+D EOF)\n`, + `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT)\n`, ); }; + + // NEW: make a helper that avoids ?? and only uses explicit guards + function safeGetLogPathsForSlot(id: number) { + const live = workers.get(id); + // Prefer live worker paths if the IPC channel is still open + if (live && live.connected && (live as any).channel) { + try { + const p = getWorkerLogPaths(live); + if (p !== undefined && p !== null) return p; + } catch { + // fall through to snapshot + } + } + // Fall back to the snapshot we saved at spawn time + return slotLogPaths.get(id); + } + cleanupSwitcher = installInteractiveSwitcher({ workers, onAttach: attachScreen, onDetach: detachScreen, onCtrlC: onSigint, - getLogPaths: (id) => { - const w = workers.get(id); - return w ? getWorkerLogPaths(w) : undefined; - }, - replayBytes: 200 * 1024, // last ~200KB - replayWhich: ['out', 'err'], // also add 'structured' if you want + getLogPaths: (id) => safeGetLogPathsForSlot(id), // <— no ?? here + replayBytes: 200 * 1024, + replayWhich: ['out', 'err'], onEnterAttachScreen: attachScreen, }); - // hint const maxDigit = Math.min(poolSize - 1, 9); const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; @@ -457,29 +456,32 @@ export async function uploadPreferences( ), ); - // wait for completion - await new Promise((resolve) => { + // wait for completion of work (not viewer) + await new Promise((resolveWork) => { const check = setInterval(() => { // If the queue is empty, request shutdown for **idle** workers only. if (pending.length === 0) { for (const [id, w] of workers) { const st = workerState.get(id); - if (st && !st.busy) { + // Only try to signal live children; exited ones keep their slot for log viewing + if (st && !st.busy && w && w.connected && (w as any).channel) { try { - w.send?.({ type: 'shutdown' }); - } catch {} + w.send({ type: 'shutdown' }); + } catch { + /* ignore */ + } } } } - // Done when all workers have exited - if (pending.length === 0 && workers.size === 0) { + // Done when queue is empty and all live workers have exited. + if (pending.length === 0 && activeWorkers === 0) { clearInterval(check); clearInterval(renderInterval); - cleanupSwitcher(); - // Persist the final snapshot (don’t clear) + // Persist the final snapshot; keep switcher OPEN for viewer mode repaint(true); + // Print summary line (don’t exit yet) if ((agg as AnyTotals).mode === 'upload') { const a = agg as UploadModeTotals; process.stdout.write( @@ -495,11 +497,37 @@ export async function uploadPreferences( ), ); } - resolve(); + + // Tell the user they can browse logs and press 'q' to quit + process.stdout.write( + colors.dim( + '\nViewer mode — digits to view logs • Tab/Shift+Tab • Esc detach • press q to quit\n', + ), + ); + + resolveWork(); } }, 300); }); + // --- Viewer mode: leave switcher active until user presses 'q' --- + await new Promise((resolveViewer) => { + const onKeypress = (buf: Buffer) => { + const s = buf.toString('utf8'); + if (s === 'q' || s === 'Q') { + process.stdin.off('data', onKeypress); + resolveViewer(); + } + }; + try { + process.stdin.setRawMode?.(true); + } catch {} + process.stdin.resume(); + process.stdin.on('data', onKeypress); + }); + + // Cleanup switcher now that the user quit viewer mode + cleanupSwitcher(); process.removeListener('SIGINT', onSigint); } diff --git a/src/lib/pooling/assignWorkToWorker.ts b/src/lib/pooling/assignWorkToWorker.ts index 43b9ccfa..4776c559 100644 --- a/src/lib/pooling/assignWorkToWorker.ts +++ b/src/lib/pooling/assignWorkToWorker.ts @@ -21,7 +21,11 @@ export function assignWorkToWorker( repaint: () => void, ): void { const w = workers.get(id); - if (!w) return; + if (!w || !w.connected || !(w as any).channel) { + // mark slot idle and skip (slot is kept for post-run log viewing) + workerState.set(id, { busy: false, file: null, startedAt: null }); + return; + } const filePath = pending.shift(); if (!filePath) { diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 09d05f9a..3e47dc7e 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -17,19 +17,28 @@ let lastFrame = ''; * */ type UploadModeTotals = { - /** */ mode: 'upload' /** */; - success: number /** */; - skipped: number /** */; + /** */ + mode: 'upload'; + /** */ + success: number; + /** */ + skipped: number; + /** */ error: number; }; /** * */ type CheckModeTotals = { - /** */ mode: 'check' /** */; - pendingConflicts: number /** */; - pendingSafe: number /** */; - totalPending: number /** */; + /** */ + mode: 'check'; + /** */ + pendingConflicts: number; + /** */ + pendingSafe: number; + /** */ + totalPending: number; + /** */ skipped: number; }; /** @@ -96,6 +105,11 @@ export function renderDashboard( } } + const isFinal = !!opts?.final; + const hotkeysLine = isFinal + ? 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit' + : `Hotkeys: [${digitRange}] attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit`; + const frame = [ `Parallel uploader — ${poolSize} workers (CPU avail: ${cpuCount})`, `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, @@ -104,13 +118,11 @@ export function renderDashboard( '', ...lines, '', - `Hotkeys: [${digitRange}] attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit`, + hotkeysLine, ] .filter(Boolean) .join('\n'); - const isFinal = !!opts?.final; - if (!isFinal && frame === lastFrame) return; lastFrame = frame; From c0d20f13ec0e5142a7c1e6c56624ffed43fdf74b Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 15 Aug 2025 23:53:40 -0700 Subject: [PATCH 14/72] mostly wokring --- .../parsePreferenceIdentifiersFromCsv.ts | 2 ++ .../parsePreferenceManagementCsv.ts | 32 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 2076e8d6..30ceb871 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -262,6 +262,8 @@ export async function addTranscendIdToPreferences( return preferences.map((pref) => ({ ...pref, person_id: pref.person_id !== '-2' ? pref.person_id : '', + email_address: + pref.email_address === 'NOEMAIL@COSTCO.COM' ? '' : pref.email_address, transcendID: pref.person_id && pref.person_id !== '-2' ? pref.person_id diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 7ba389e4..1448397f 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -75,12 +75,10 @@ export async function parsePreferenceManagementCsvWithCache( // Read in the file logger.info(colors.magenta(`Reading in file: "${file}"`)); let preferences = readCsv(file, t.record(t.string, t.string)); - console.log(`${preferences.length}, '1'`); // TODO: Remove this COSTCO specific logic const updatedPreferences = await addTranscendIdToPreferences(preferences); preferences = updatedPreferences; - console.log(`${preferences.length}, '2'`); // Validate that all timestamps are present in the file await parsePreferenceFileFormatFromCsv(preferences, schemaState); @@ -93,7 +91,6 @@ export async function parsePreferenceManagementCsvWithCache( identifierColumns, }); preferences = result.preferences; - console.log(`${preferences.length}, '3'`); // Ensure all other columns are mapped to purpose and preference slug values await parsePreferenceAndPurposeValuesFromCsv(preferences, schemaState, { @@ -134,8 +131,6 @@ export async function parsePreferenceManagementCsvWithCache( const skippedUpdates: RequestUploadReceipts['skippedUpdates'] = {}; // Process each row - console.log(`${preferences.length}, '4'`); - const seenAlready: Record = {}; preferences.forEach((pref) => { // Get the userIds that could be the primary key of the consent record @@ -166,21 +161,27 @@ export async function parsePreferenceManagementCsvWithCache( ([key, value]) => seenAlready[primaryKey][key] === value, ) ) { + // Show a diff of what's changed between the duplicate rows + const previous = seenAlready[primaryKey]; + const diffs = Object.entries(pref) + .filter(([key, value]) => previous[key] !== value) + .map( + ([key, value]) => + ` "${key}": previous="${previous[key]}", current="${value}"`, + ) + .join('\n'); + const sameValues = Object.entries(pref) + .filter(([key, value]) => previous[key] === value) + .map(([key, value]) => ` "${key}": value="${value}"`) + .join('\n'); throw new Error( - `Duplicate primary key found: "${primaryKey}" in row: ${JSON.stringify( - pref, - )}, previously seen in row: ${seenAlready[primaryKey]}`, + `Duplicate primary key found: "${primaryKey}"\nDiff:\n${diffs}\nSame Values:\n${sameValues}`, ); } else { - console.log('SKIPPINNNG'); skippedUpdates[primaryKey] = pref; logger.warn( colors.yellow( - `Duplicate primary key found: "${primaryKey}" in row: ${JSON.stringify( - pref, - )}, previously seen in row: ${ - seenAlready[primaryKey] - }. Skipping duplicate.`, + `Duplicate primary key found: "${primaryKey}" but rows are identical.`, ), ); return; @@ -208,7 +209,6 @@ export async function parsePreferenceManagementCsvWithCache( }) && !forceTriggerWorkflows ) { - console.log(`HERERERE${primaryKey}`); skippedUpdates[primaryKey] = pref; return; } @@ -220,7 +220,7 @@ export async function parsePreferenceManagementCsvWithCache( currentConsentRecord, pendingUpdates, preferenceTopics, - log: false, // FIXME + log: false, }) ) { pendingConflictUpdates[primaryKey] = { From 41c509ce2e7985ed9fe98eb995bc44a6653941f8 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 02:53:34 -0700 Subject: [PATCH 15/72] Working in parallel --- .../consent/upload-preferences/impl.ts | 33 ++- .../consent/upload-preferences/runChild.ts | 3 +- src/lib/pooling/renderDashboard.ts | 12 +- src/lib/preference-management/codecs.ts | 15 +- .../getPreferenceUpdatesFromRow.ts | 10 +- .../parsePreferenceAndPurposeValuesFromCsv.ts | 4 +- .../parsePreferenceIdentifiersFromCsv.ts | 70 ++++- .../parsePreferenceManagementCsv.ts | 51 ++-- ...ferenceManagementPreferencesInteractive.ts | 255 +++++++++++++----- 9 files changed, 353 insertions(+), 100 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 2b440749..15027413 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -62,6 +62,7 @@ export type TaskCommonOpts = Pick< | 'auth' | 'partition' | 'sombraAuth' + | 'directory' | 'transcendUrl' | 'skipConflictUpdates' | 'skipWorkflowTriggers' @@ -135,6 +136,7 @@ type UploadModeTotals = { success: number; skipped: number; error: number; + errors: Record; }; type CheckModeTotals = { mode: 'check'; @@ -163,7 +165,22 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { if (!dryRun) { const success = Object.values(json?.successfulUpdates ?? {}).length; const failed = Object.values(json?.failingUpdates ?? {}).length; - return { mode: 'upload', success, skipped: skippedCount, error: failed }; + const errors: Record = {}; + Object.values(json?.failingUpdates ?? {}).forEach((value) => { + const errorMsg = (value as any).error; + if (!errors[errorMsg]) { + errors[errorMsg] = 1; + } else { + errors[errorMsg] += 1; + } + }); + return { + mode: 'upload', + success, + skipped: skippedCount, + error: failed, + errors, + }; } const totalPending = Object.values(json?.pendingUpdates ?? {}).length; @@ -181,7 +198,7 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { }; } catch { return !dryRun - ? { mode: 'upload', success: 0, skipped: 0, error: 0 } + ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } : { mode: 'check', totalPending: 0, @@ -242,6 +259,7 @@ export async function uploadPreferences( schemaFile, receiptsFolder, auth, + directory, sombraAuth, partition, transcendUrl, @@ -258,7 +276,7 @@ export async function uploadPreferences( }; // ---- Worker pool lifecycle ---- - const LOG_DIR = join(receiptsFolder, 'logs'); + const LOG_DIR = join(directory || receiptsFolder, 'logs'); mkdirSync(LOG_DIR, { recursive: true }); const RESET_MODE = @@ -278,7 +296,7 @@ export async function uploadPreferences( let activeWorkers = 0; // track only live workers const agg: AnyTotals = !common.dryRun - ? { mode: 'upload', success: 0, skipped: 0, error: 0 } + ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } : { mode: 'check', totalPending: 0, @@ -361,6 +379,13 @@ export async function uploadPreferences( agg.success += summary.success; agg.skipped += summary.skipped; agg.error += summary.error; + Object.entries(summary.errors).forEach(([key, value]) => { + if (!agg.errors[key]) { + agg.errors[key] = value; + } else { + agg.errors[key] += value; + } + }); } else if (summary.mode === 'check' && agg.mode === 'check') { agg.totalPending += summary.totalPending; agg.pendingConflicts += summary.pendingConflicts; diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 8dd98af4..082ef0d9 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -71,13 +71,14 @@ export async function runChild(): Promise { } catch (err: any) { const e = err?.stack || err?.message || String(err); console.error( - `[w${workerId}] ERROR ${filePath}: ${err?.message || err}`, + `[w${workerId}] ERROR ${filePath}: ${err?.message || err}\n\n${e}`, ); log(`FAIL ${filePath}\n${e}`); process.send?.({ type: 'result', payload: { ok: false, filePath, error: e, receiptFilepath }, }); + process.exit(1); } } else if (msg.type === 'shutdown') { console.log(`[w${workerId}] shutdown`); diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 3e47dc7e..7ea09c16 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -25,6 +25,8 @@ type UploadModeTotals = { skipped: number; /** */ error: number; + /** */ + totals: Record; }; /** * @@ -97,7 +99,15 @@ export function renderDashboard( let totalsLine = ''; if (totals) { if (totals.mode === 'upload') { - totalsLine = `Receipts totals — Success: ${totals.success} Skipped: ${totals.skipped} Error: ${totals.error}`; + totalsLine = `Receipts totals — Success: ${totals.success} Skipped: ${ + totals.skipped + } Error: ${ + totals.error + }\n\nThe individual error breakdown is:\n\n${Object.entries( + (totals as any).errors || {}, + ) + .map(([key, value]) => ` Count[${value}] ${key}`) + .join('\n')}`; } else { totalsLine = `Receipts totals — Pending: ${totals.totalPending} PendingConflicts: ${totals.pendingConflicts} ` + diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index f8365c95..06ee7298 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -78,7 +78,10 @@ export const RequestUploadReceipts = t.type({ * Mapping of userId to the rows in the file that need to be uploaded * These uploads are overwriting non-existent preferences and are safe */ - pendingSafeUpdates: t.record(t.string, t.record(t.string, t.string)), + pendingSafeUpdates: t.record( + t.string, + t.union([t.boolean, t.record(t.string, t.string)]), + ), /** * Mapping of userId to the rows in the file that need to be uploaded * these records have conflicts with existing consent preferences @@ -114,11 +117,17 @@ export const RequestUploadReceipts = t.type({ * The set of pending uploads to Transcend * Mapping from userId to the upload metadata */ - pendingUpdates: t.record(t.string, PreferenceUpdateItem), + pendingUpdates: t.record( + t.string, + t.union([t.boolean, PreferenceUpdateItem]), + ), /** * The successful updates */ - successfulUpdates: t.record(t.string, PreferenceUpdateItem), + successfulUpdates: t.record( + t.string, + t.union([t.boolean, PreferenceUpdateItem]), + ), }); /** Override type */ diff --git a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts index 32d63f68..92eadf9e 100644 --- a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts +++ b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts @@ -62,6 +62,14 @@ export function getPreferenceUpdatesFromRow({ ); } + // The value to parse + const rawValue = row[columnName]; + const rawMapping = valueMapping[rawValue]; + if (rawMapping === undefined) { + // FIXME + return; + } + // CHeck if parsing a preference or just the top level purpose if (preference) { const preferenceTopic = preferenceTopics.find( @@ -89,8 +97,6 @@ export function getPreferenceUpdatesFromRow({ result[purpose].preferences = []; } - // The value to parse - const rawValue = row[columnName]; const rawMapping = valueMapping[rawValue]; const trimmedMapping = typeof rawMapping === 'string' ? rawMapping.trim() || null : null; diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index 5451db5c..64ce5f84 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -62,7 +62,9 @@ export async function parsePreferenceAndPurposeValuesFromCsv( // Ensure all columns are accounted for await mapSeries(otherColumns, async (col) => { // Determine the unique values to map in this column - const uniqueValues = uniq(preferences.map((x) => x[col])); + const uniqueValues = uniq( + preferences.filter((x) => (x[col] || '').trim()).map((x) => x[col]), + ); // Map the column to a purpose const currentPurposeMapping = schemaState.getValue('columnToPurposeName'); diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 30ceb871..62d32d25 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -259,14 +259,64 @@ export async function addTranscendIdToPreferences( // return preferences; // } // Add a transcendent ID to each preference if it doesn't already exist - return preferences.map((pref) => ({ - ...pref, - person_id: pref.person_id !== '-2' ? pref.person_id : '', - email_address: - pref.email_address === 'NOEMAIL@COSTCO.COM' ? '' : pref.email_address, - transcendID: - pref.person_id && pref.person_id !== '-2' - ? pref.person_id - : pref.member_id, - })); + const disallowedEmails = [ + 'noemail@costco.com', + 'NOEMAILYET@GMAIL.COM', + 'noemail@gmail.com', + 'noemail@aol.com', + 'none@none.com', + 'noemail@mail.com', + 'no@email.com', + 'noemail@no.com', + '123@gmail.com', + 'no.no@gmail.com', + 'BC@GMAIL.COM', + 'NO@YAHOO.COM', + 'noemail@email.com', + 'NONAME@GMAIL.COM', + 'notoemail@gmail.com', + 'NOEMAILATM@YAHOO.COM', + 'NO@MAIL.COM', + 'NOGMAIL@GMAIL.COM', + 'NA@NA.COM', + 'NOPE@COSTCO.COM', + 'Noemail@outlook.com', + 'none@gmail.com', + 'GETEMAIL@GMAIL.COM', + 'EMAIL@EMAIL.COM', + 'NAME@AOL.COM', + 'NOTHING@GMAIL.COM', + 'NOMAIL@NOMAIL.COM', + 'NO@NE.COM', + 'donthave@hotmail.com', + 'NOEMAIL@COSTO.COM', + 'noemail@yahoo.com', + 'no@gmail.com', + 'na@gmail.com', + 'NOMAIL@GMAIL.COM', + 'costco@costco.com', + 'noemail@noemail.com', + 'replace@gmail.com', + 'NONE@YAHOO.COM', + 'a@gmail.com', + 'NEEDEMAIL@GMAIL.COM', + 'NONE1@YAHOO.COM', + 'no@no.com', + 'none@outlook.com', + 'none@yahoo.com', + ].map((email) => email.toLowerCase()); + + return preferences.map((pref) => { + const email = (pref.email_address || '').toLowerCase(); + return { + ...pref, + person_id: pref.person_id !== '-2' ? pref.person_id : '', + email_address: + !email || disallowedEmails.includes(email) ? '' : pref.email_address, + transcendID: + pref.person_id && pref.person_id !== '-2' + ? pref.person_id + : pref.member_id, + }; + }); } diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 1448397f..c67fdfdd 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -26,7 +26,6 @@ import type { ObjByString } from '@transcend-io/type-utils'; * * @param options - Options * @param schemaState - The schema state to use for parsing the file - * @param uploadState - The upload state to use for parsing the file * @returns The cache with the parsed file */ export async function parsePreferenceManagementCsvWithCache( @@ -67,14 +66,21 @@ export async function parsePreferenceManagementCsvWithCache( columnsToIgnore: string[]; }, schemaState: PersistedState, - uploadState: PersistedState, -): Promise { +): Promise<{ + /** Pending saf updates */ + pendingSafeUpdates: Record>; + /** Pending conflict updates */ + pendingConflictUpdates: RequestUploadReceipts['pendingConflictUpdates']; + /** SKipped updates */ + skippedUpdates: RequestUploadReceipts['skippedUpdates']; +}> { // Start the timer const t0 = new Date().getTime(); // Read in the file logger.info(colors.magenta(`Reading in file: "${file}"`)); let preferences = readCsv(file, t.record(t.string, t.string)); + logger.info(colors.magenta(`Read in ${preferences.length} rows`)); // TODO: Remove this COSTCO specific logic const updatedPreferences = await addTranscendIdToPreferences(preferences); @@ -127,12 +133,21 @@ export async function parsePreferenceManagementCsvWithCache( // Clear out previous updates const pendingConflictUpdates: RequestUploadReceipts['pendingConflictUpdates'] = {}; - const pendingSafeUpdates: RequestUploadReceipts['pendingSafeUpdates'] = {}; + const pendingSafeUpdates: Record> = {}; const skippedUpdates: RequestUploadReceipts['skippedUpdates'] = {}; // Process each row const seenAlready: Record = {}; - preferences.forEach((pref) => { + logger.log( + colors.green( + `Processing ${preferences.length} preferences with ${ + Object.keys(currentColumnToIdentifierMap).length + } identifiers and ${ + Object.keys(currentColumnToPurposeName).length + } purposes`, + ), + ); + preferences.forEach((pref, ind) => { // Get the userIds that could be the primary key of the consent record const possiblePrimaryKeys = getUniquePreferenceIdentifierNamesFromRow({ row: pref, @@ -153,7 +168,7 @@ export async function parsePreferenceManagementCsvWithCache( .find((record) => record); // If consent record is found use it, otherwise use the first unique identifier - const primaryKey = currentConsentRecord?.userId || possiblePrimaryKeys[0]; + let primaryKey = currentConsentRecord?.userId || possiblePrimaryKeys[0]; // Ensure this is unique if (seenAlready[primaryKey]) { if ( @@ -174,14 +189,17 @@ export async function parsePreferenceManagementCsvWithCache( .filter(([key, value]) => previous[key] === value) .map(([key, value]) => ` "${key}": value="${value}"`) .join('\n'); - throw new Error( - `Duplicate primary key found: "${primaryKey}"\nDiff:\n${diffs}\nSame Values:\n${sameValues}`, + logger.warn( + colors.yellow( + `Duplicate primary key found, merging: "${primaryKey}"\nDiff:\n${diffs}\nSame Values:\n${sameValues}`, + ), ); + primaryKey = `${primaryKey}___${ind}`; } else { - skippedUpdates[primaryKey] = pref; + skippedUpdates[`${primaryKey}___${ind}`] = pref; logger.warn( colors.yellow( - `Duplicate primary key found: "${primaryKey}" but rows are identical.`, + `Duplicate primary key found: "${primaryKey}" at index: "${ind}" but rows are identical.`, ), ); return; @@ -220,7 +238,7 @@ export async function parsePreferenceManagementCsvWithCache( currentConsentRecord, pendingUpdates, preferenceTopics, - log: false, + log: false, // FIXME }) ) { pendingConflictUpdates[primaryKey] = { @@ -234,15 +252,16 @@ export async function parsePreferenceManagementCsvWithCache( pendingSafeUpdates[primaryKey] = pref; }); - // Read in the file - uploadState.setValue(pendingSafeUpdates, 'pendingSafeUpdates'); - uploadState.setValue(pendingConflictUpdates, 'pendingConflictUpdates'); - uploadState.setValue(skippedUpdates, 'skippedUpdates'); - const t1 = new Date().getTime(); logger.info( colors.green( `Successfully pre-processed file: "${file}" in ${(t1 - t0) / 1000}s`, ), ); + + return { + pendingSafeUpdates, + pendingConflictUpdates, + skippedUpdates, + }; } diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index f0b5bdbe..a58b1494 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import { buildTranscendGraphQLClient, createSombraGotInstance, @@ -16,11 +17,15 @@ import { PersistedState } from '@transcend-io/persisted-state'; import { parsePreferenceManagementCsvWithCache } from './parsePreferenceManagementCsv'; import { FileFormatState, RequestUploadReceipts } from './codecs'; import { PreferenceUpdateItem } from '@transcend-io/privacy-types'; -import { apply } from '@transcend-io/type-utils'; +import { apply, getEntries } from '@transcend-io/type-utils'; import { NONE_PREFERENCE_MAP } from './parsePreferenceFileFormatFromCsv'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; +const LOG_RATE = 1000; // FIXMe set to 10k +const CONCURRENCY = 25; // FIXME +const MAX_CHUNK_SIZE = 50; // FIXME + /** * Upload a set of consent preferences * @@ -138,7 +143,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ ]); // Process the file - await parsePreferenceManagementCsvWithCache( + const result = await parsePreferenceManagementCsvWithCache( { file, purposeSlugs: purposes.map((x) => x.trackingType), @@ -153,14 +158,28 @@ export async function uploadPreferenceManagementPreferencesInteractive({ columnsToIgnore, }, schemaState, - uploadState, ); + // Read in the file + uploadState.setValue( + getEntries(result.pendingSafeUpdates).reduce( + (acc, [userId, value], ind) => { + if (ind < 10) { + acc[userId] = value; + } else { + acc[userId] = true; + } + return acc; + }, + {} as Record, + ), + 'pendingSafeUpdates', + ); + uploadState.setValue(result.pendingConflictUpdates, 'pendingConflictUpdates'); + uploadState.setValue(result.skippedUpdates, 'skippedUpdates'); + // Construct the pending updates const pendingUpdates: Record = {}; - const safeUpdatesInCache = uploadState.getValue('pendingSafeUpdates'); - const conflictUpdatesInCache = uploadState.getValue('pendingConflictUpdates'); - const skippedUpdatesInCache = uploadState.getValue('skippedUpdates'); const timestampColumn = schemaState.getValue('timestampColumn'); const columnToPurposeName = schemaState.getValue('columnToPurposeName'); const columnToIdentifier = schemaState.getValue('columnToIdentifier'); @@ -168,31 +187,28 @@ export async function uploadPreferenceManagementPreferencesInteractive({ logger.info( colors.magenta( `Found ${ - Object.entries(safeUpdatesInCache).length + Object.entries(result.pendingSafeUpdates).length } safe updates in ${file}`, ), ); - logger.info( - colors.magenta( - `Found ${ - Object.entries(conflictUpdatesInCache).length - } conflict updates in ${file}`, - ), - ); - logger.info( - colors.magenta( - `Found ${ - Object.entries(skippedUpdatesInCache).length - } skipped updates in ${file}`, - ), - ); - + const conflictCount = Object.entries(result.pendingConflictUpdates).length; + if (conflictCount) { + logger.warn( + colors.magenta(`Found ${conflictCount} conflict updates in ${file}`), + ); + } + const skippedCount = Object.entries(result.skippedUpdates).length; + if (skippedCount > 0) { + logger.warn( + colors.magenta(`Found ${skippedCount} skipped updates in ${file}`), + ); + } // Update either safe updates only or safe + conflict Object.entries({ - ...safeUpdatesInCache, + ...result.pendingSafeUpdates, ...(skipConflictUpdates ? {} - : apply(conflictUpdatesInCache, ({ row }) => row)), + : apply(result.pendingConflictUpdates, ({ row }) => row)), }).forEach(([userId, update]) => { // Determine timestamp const timestamp = @@ -227,9 +243,19 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }; }); // FIXME restart better - await uploadState.setValue(pendingUpdates, 'pendingUpdates'); + await uploadState.setValue( + Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { + if (ind < 10) { + acc[userId] = value; + } else { + acc[userId] = true; + } + return acc; + }, {} as Record), + 'pendingUpdates', + ); await uploadState.setValue({}, 'failingUpdates'); - await uploadState.setValue({}, 'successfulUpdates'); + // await uploadState.setValue({}, 'successfulUpdates'); dont reset // Exist early if dry run if (dryRun) { @@ -256,10 +282,40 @@ export async function uploadPreferenceManagementPreferencesInteractive({ // Build a GraphQL client let total = 0; - const updatesToRun = Object.entries(pendingUpdates); - const chunkedUpdates = chunk(updatesToRun, skipWorkflowTriggers ? 50 : 10); - const successfulUpdates: Record = {}; - // progressBar.start(updatesToRun.length, 0); + const allUpdatesPending = Object.entries(pendingUpdates); + const successfulUpdates = uploadState.getValue('successfulUpdates'); + const filteredUpdates = allUpdatesPending.filter( + ([userId]) => !successfulUpdates[userId], + ); + if (filteredUpdates.length === 0) { + logger.warn( + colors.yellow( + `No pending updates to upload to partition: ${partition}, ` + + `${allUpdatesPending.length} total already successfully uploaded.`, + ), + ); + await uploadState.setValue({}, 'pendingUpdates'); + await uploadState.setValue({}, 'pendingSafeUpdates'); + await uploadState.setValue({}, 'pendingConflictUpdates'); + return; + } + if (filteredUpdates.length < allUpdatesPending.length) { + logger.warn( + colors.yellow( + `Found ${allUpdatesPending.length} total updates, but only ` + + `${filteredUpdates.length} updates to upload to partition: ${partition}, ` + + `as ${ + allUpdatesPending.length - filteredUpdates.length + } were already successfully uploaded.`, + ), + ); + } + + const chunkedUpdates = chunk( + filteredUpdates, + // skipWorkflowTriggers ? 50 : 10 // FIXME + MAX_CHUNK_SIZE, + ); await map( chunkedUpdates, async (currentChunk) => { @@ -274,51 +330,117 @@ export async function uploadPreferenceManagementPreferencesInteractive({ }, }) .json(); - currentChunk.forEach(([userId, update]) => { - successfulUpdates[userId] = update; + currentChunk.forEach(([userId]) => { + successfulUpdates[userId] = true; + delete pendingUpdates[userId]; + delete result.pendingSafeUpdates[userId]; + delete result.pendingConflictUpdates[userId]; }); } catch (err) { - try { - const parsed = JSON.parse(err?.response?.body || '{}'); - if (parsed.error) { - logger.error(colors.red(`Error: ${parsed.error}`)); + // On batch error, try each update individually + for (const [userId, update] of currentChunk) { + try { + await sombra + .put('v1/preferences', { + json: { + records: [update], + skipWorkflowTriggers, + forceTriggerWorkflows, + }, + }) + .json(); + successfulUpdates[userId] = true; + delete pendingUpdates[userId]; + delete result.pendingSafeUpdates[userId]; + delete result.pendingConflictUpdates[userId]; + } catch (singleErr) { + let errorMsg = + singleErr?.response?.body || + singleErr?.message || + 'Unknown error'; + try { + const parsed = JSON.parse(errorMsg); + if (parsed.error) { + logger.error( + colors.red( + `Error for ${userId}: ${parsed.error}\n${JSON.stringify( + parsed, + null, + 2, + )}`, + ), + ); + errorMsg = ( + parsed.errors || + parsed.error.errors || [parsed.error] + ).join(','); + } + } catch (e) { + // continue + } + if (errorMsg.includes('Too many identifiers')) { + errorMsg += `____${userId.split('___')[0]}`; + } + logger.error( + colors.red( + `Failed to upload user preferences for ${userId} to partition ${partition}: ${errorMsg}`, + ), + ); + const failingUpdates = uploadState.getValue('failingUpdates'); + failingUpdates[userId] = { + uploadedAt: new Date().toISOString(), + update, + error: errorMsg, + }; + delete pendingUpdates[userId]; + delete result.pendingSafeUpdates[userId]; + delete result.pendingConflictUpdates[userId]; + await uploadState.setValue(failingUpdates, 'failingUpdates'); } - } catch (e) { - // continue } - logger.error( - colors.red( - `Failed to upload ${ - currentChunk.length - } user preferences to partition ${partition}: ${ - err?.response?.body || err?.message - }`, - ), - ); - const failingUpdates = uploadState.getValue('failingUpdates'); - currentChunk.forEach(([userId, update]) => { - failingUpdates[userId] = { - uploadedAt: new Date().toISOString(), - update, - error: err?.response?.body || err?.message || 'Unknown error', - }; - }); - await uploadState.setValue(failingUpdates, 'failingUpdates'); } total += currentChunk.length; - // progressBar.update(total); - // log every 1000 - if (total % 1000 === 0) { + if (total % LOG_RATE === 0) { logger.info( colors.green( - `Uploaded ${total}/${updatesToRun.length} user preferences to partition ${partition}`, + `Uploaded ${total}/${filteredUpdates.length} user preferences to partition ${partition}`, + ), + ); + await uploadState.setValue(successfulUpdates, 'successfulUpdates'); + await uploadState.setValue( + Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { + if (ind < 10) { + acc[userId] = value; + } else { + acc[userId] = true; + } + return acc; + }, {} as Record), + 'pendingUpdates', + ); + await uploadState.setValue( + Object.entries(result.pendingSafeUpdates).reduce( + (acc, [userId, value], ind) => { + if (ind < 10) { + acc[userId] = value; + } else { + acc[userId] = true; + } + return acc; + }, + {} as Record, ), + 'pendingSafeUpdates', + ); + await uploadState.setValue( + result.pendingConflictUpdates, + 'pendingConflictUpdates', ); } }, { - concurrency: 80, + concurrency: CONCURRENCY, }, ); @@ -334,10 +456,19 @@ export async function uploadPreferenceManagementPreferencesInteractive({ logger.info( colors.green( `Successfully uploaded ${ - updatesToRun.length + filteredUpdates.length } user preferences to partition ${partition} in "${ totalTime / 1000 }" seconds!`, ), ); + if (Object.values(failingRequests).length > 0) { + logger.error( + colors.red( + `There are ${ + Object.values(failingRequests).length + } requests that failed to upload. Please check the receipt file: ${receiptFilepath}`, + ), + ); + } } From e8f7d2c7c74f966495b4749b4e3dba1810011ec5 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 03:13:07 -0700 Subject: [PATCH 16/72] Working --- .../parsePreferenceIdentifiersFromCsv.ts | 7 + ...ferenceManagementPreferencesInteractive.ts | 372 ++++++++++++------ 2 files changed, 250 insertions(+), 129 deletions(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 62d32d25..ae6821c2 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -274,23 +274,30 @@ export async function addTranscendIdToPreferences( 'NO@YAHOO.COM', 'noemail@email.com', 'NONAME@GMAIL.COM', + 'NOEMAIL@HOTMAIL.COM', 'notoemail@gmail.com', 'NOEMAILATM@YAHOO.COM', 'NO@MAIL.COM', 'NOGMAIL@GMAIL.COM', 'NA@NA.COM', 'NOPE@COSTCO.COM', + 'USER@GMAIL.COM', 'Noemail@outlook.com', 'none@gmail.com', 'GETEMAIL@GMAIL.COM', 'EMAIL@EMAIL.COM', 'NAME@AOL.COM', 'NOTHING@GMAIL.COM', + 'NO2@GMAIL.COM', + 'NO@INFO.COM', 'NOMAIL@NOMAIL.COM', + 'COSTCO@GMAIL.COM', 'NO@NE.COM', + 'NONE@AOL.COM', 'donthave@hotmail.com', 'NOEMAIL@COSTO.COM', 'noemail@yahoo.com', + 'EMAIL@GMAIL.COM', 'no@gmail.com', 'na@gmail.com', 'NOMAIL@GMAIL.COM', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index a58b1494..78850ad7 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -23,9 +23,45 @@ import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 25; // FIXME +const CONCURRENCY = 50; // FIXME const MAX_CHUNK_SIZE = 50; // FIXME +// Treat these as "retry in place" errors (do NOT split on these). +// Note: 329 included defensively in case of custom upstream code; primarily handle 429 & 502. +const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 329] as const); + +const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +/** + * Extract HTTP status code from an error thrown by got + * + * @param err + */ +function getStatus(err: any): number | undefined { + return err?.response?.statusCode ?? err?.response?.status; +} + +/** + * Extract a readable error message from an error thrown by got / server + * + * @param err + */ +function extractErrorMessage(err: any): string { + let errorMsg = err?.response?.body || err?.message || 'Unknown error'; + try { + const parsed = JSON.parse(errorMsg); + if (parsed.error) { + // common GraphQL/REST patterns + const msgs = parsed.errors || + parsed.error?.errors || [parsed.error?.message || parsed.error]; + errorMsg = (Array.isArray(msgs) ? msgs : [msgs]).join(', '); + } + } catch { + // leave as-is + } + return errorMsg; +} + /** * Upload a set of consent preferences * @@ -223,12 +259,12 @@ export async function uploadPreferenceManagementPreferencesInteractive({ preferenceTopics, purposeSlugs: purposes.map((x) => x.trackingType), }); - const identifiers = getPreferenceIdentifiersFromRow({ + const identifiersForRow = getPreferenceIdentifiersFromRow({ row: update, columnToIdentifier, }); pendingUpdates[userId] = { - identifiers, + identifiers: identifiersForRow, partition, timestamp: timestamp.toISOString(), purposes: Object.entries(updates).map(([purpose, value]) => ({ @@ -281,7 +317,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ const t0 = new Date().getTime(); // Build a GraphQL client - let total = 0; + let uploadedCount = 0; const allUpdatesPending = Object.entries(pendingUpdates); const successfulUpdates = uploadState.getValue('successfulUpdates'); const filteredUpdates = allUpdatesPending.filter( @@ -311,152 +347,230 @@ export async function uploadPreferenceManagementPreferencesInteractive({ ); } - const chunkedUpdates = chunk( - filteredUpdates, - // skipWorkflowTriggers ? 50 : 10 // FIXME - MAX_CHUNK_SIZE, - ); - await map( - chunkedUpdates, - async (currentChunk) => { - // Make the request - try { - await sombra - .put('v1/preferences', { - json: { - records: currentChunk.map(([, update]) => update), - skipWorkflowTriggers, - forceTriggerWorkflows, - }, - }) - .json(); - currentChunk.forEach(([userId]) => { - successfulUpdates[userId] = true; - delete pendingUpdates[userId]; - delete result.pendingSafeUpdates[userId]; - delete result.pendingConflictUpdates[userId]; - }); - } catch (err) { - // On batch error, try each update individually - for (const [userId, update] of currentChunk) { - try { - await sombra - .put('v1/preferences', { - json: { - records: [update], - skipWorkflowTriggers, - forceTriggerWorkflows, - }, - }) - .json(); - successfulUpdates[userId] = true; - delete pendingUpdates[userId]; - delete result.pendingSafeUpdates[userId]; - delete result.pendingConflictUpdates[userId]; - } catch (singleErr) { - let errorMsg = - singleErr?.response?.body || - singleErr?.message || - 'Unknown error'; - try { - const parsed = JSON.parse(errorMsg); - if (parsed.error) { - logger.error( - colors.red( - `Error for ${userId}: ${parsed.error}\n${JSON.stringify( - parsed, - null, - 2, - )}`, - ), - ); - errorMsg = ( - parsed.errors || - parsed.error.errors || [parsed.error] - ).join(','); - } - } catch (e) { - // continue - } - if (errorMsg.includes('Too many identifiers')) { - errorMsg += `____${userId.split('___')[0]}`; - } - logger.error( - colors.red( - `Failed to upload user preferences for ${userId} to partition ${partition}: ${errorMsg}`, - ), - ); - const failingUpdates = uploadState.getValue('failingUpdates'); - failingUpdates[userId] = { - uploadedAt: new Date().toISOString(), - update, - error: errorMsg, - }; - delete pendingUpdates[userId]; - delete result.pendingSafeUpdates[userId]; - delete result.pendingConflictUpdates[userId]; - await uploadState.setValue(failingUpdates, 'failingUpdates'); - } - } - } + // --- Helpers bound to closure state --- + + const markSuccessFor = async ( + entries: Array<[string, PreferenceUpdateItem]>, + ) => { + for (const [userId] of entries) { + successfulUpdates[userId] = true; + delete pendingUpdates[userId]; + delete result.pendingSafeUpdates[userId]; + delete result.pendingConflictUpdates[userId]; + } + uploadedCount += entries.length; - total += currentChunk.length; - if (total % LOG_RATE === 0) { - logger.info( - colors.green( - `Uploaded ${total}/${filteredUpdates.length} user preferences to partition ${partition}`, - ), - ); - await uploadState.setValue(successfulUpdates, 'successfulUpdates'); - await uploadState.setValue( - Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { + // Periodic persistence + logging + if (uploadedCount % LOG_RATE === 0) { + logger.info( + colors.green( + `Uploaded ${uploadedCount}/${filteredUpdates.length} user preferences to partition ${partition}`, + ), + ); + await uploadState.setValue(successfulUpdates, 'successfulUpdates'); + await uploadState.setValue( + Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { + if (ind < 10) { + acc[userId] = value; + } else { + acc[userId] = true; + } + return acc; + }, {} as Record), + 'pendingUpdates', + ); + await uploadState.setValue( + Object.entries(result.pendingSafeUpdates).reduce( + (acc, [userId, value], ind) => { if (ind < 10) { acc[userId] = value; } else { acc[userId] = true; } return acc; - }, {} as Record), - 'pendingUpdates', - ); - await uploadState.setValue( - Object.entries(result.pendingSafeUpdates).reduce( - (acc, [userId, value], ind) => { - if (ind < 10) { - acc[userId] = value; - } else { - acc[userId] = true; - } - return acc; - }, - {} as Record, - ), - 'pendingSafeUpdates', - ); - await uploadState.setValue( - result.pendingConflictUpdates, - 'pendingConflictUpdates', - ); + }, + {} as Record, + ), + 'pendingSafeUpdates', + ); + await uploadState.setValue( + result.pendingConflictUpdates, + 'pendingConflictUpdates', + ); + } + }; + + const markFailureForSingle = async ( + userId: string, + update: PreferenceUpdateItem, + err: any, + ) => { + let errorMsg = extractErrorMessage(err); + if (errorMsg.includes('Too many identifiers')) { + errorMsg += `____${userId.split('___')[0]}`; + } + logger.error( + colors.red( + `Failed to upload user preferences for ${userId} to partition ${partition}: ${errorMsg}`, + ), + ); + const failing = uploadState.getValue('failingUpdates'); + failing[userId] = { + uploadedAt: new Date().toISOString(), + update, + error: errorMsg, + }; + delete pendingUpdates[userId]; + delete result.pendingSafeUpdates[userId]; + delete result.pendingConflictUpdates[userId]; + await uploadState.setValue(failing, 'failingUpdates'); + }; + + const markFailureForBatch = async ( + entries: Array<[string, PreferenceUpdateItem]>, + err: any, + ) => { + for (const [userId, update] of entries) { + await markFailureForSingle(userId, update, err); + } + }; + + const putBatch = async (entries: Array<[string, PreferenceUpdateItem]>) => + sombra + .put('v1/preferences', { + json: { + records: entries.map(([, update]) => update), + skipWorkflowTriggers, + forceTriggerWorkflows, + }, + }) + .json(); + + /** + * Try a batch, and if it fails: + * - On 429/502/329: retry same batch up to 3 times with 10s sleep (no splitting). + * - Otherwise: split batch in half and recurse, down to single-record requests. + * + * @param entries + */ + const uploadChunkWithSplit = async ( + entries: Array<[string, PreferenceUpdateItem]>, + ): Promise => { + // Fast path: attempt the whole batch + try { + await putBatch(entries); + await markSuccessFor(entries); + } catch (err) { + const status = getStatus(err); + if (status && RETRYABLE_BATCH_STATUSES.has(status as any)) { + // Retry this SAME batch up to 3 times with backoff 10s + let attemptsLeft = 3; + while (attemptsLeft > 0) { + attemptsLeft -= 1; + logger.warn( + colors.yellow( + `Received ${status} for a batch of ${ + entries.length + }. Retrying in 10s... (${3 - attemptsLeft}/3)`, + ), + ); + await sleep(10_000); + try { + await putBatch(entries); + await markSuccessFor(entries); + return; + } catch (retryErr) { + if ( + getStatus(retryErr) && + RETRYABLE_BATCH_STATUSES.has(getStatus(retryErr) as any) && + attemptsLeft > 0 + ) { + logger.warn( + colors.yellow( + `Retry attempt failed with ${getStatus( + retryErr, + )}, retrying again...`, + ), + ); + continue; // loop to retry again + } + // If status changed to non-retryable, fall through to split behavior + err = retryErr; + break; + } + } + + // Exhausted retries (still 429/502/329): mark the WHOLE batch as failed (do NOT split) + if ( + getStatus(err) && + RETRYABLE_BATCH_STATUSES.has(getStatus(err) as any) + ) { + logger.error( + colors.red( + `Exhausted retries for ${ + entries.length + } records due to ${getStatus( + err, + )}. Marking entire batch as failed.`, + ), + ); + await markFailureForBatch(entries, err); + return; + } + // Otherwise continue to split below (e.g., status changed to non-retryable). } + + // Non-retryable error path -> split in half recursively (down to singles) + if (entries.length === 1) { + // Single request: try once and mark failure if it errors + try { + await putBatch(entries); + await markSuccessFor(entries); + } catch (singleErr) { + await markFailureForSingle(entries[0][0], entries[0][1], singleErr); + } + return; + } + + const mid = Math.floor(entries.length / 2); + const left = entries.slice(0, mid); + const right = entries.slice(mid); + logger.warn( + colors.yellow( + `Failed to upload batch of ${ + entries.length + } records with status: ${getStatus( + err, + )}. Splitting into two batches: ${left.length} and ${right.length}.`, + ), + ); + await uploadChunkWithSplit(left); + await uploadChunkWithSplit(right); + } + }; + + // --- Kick off uploads in chunks (top-level), but each chunk may be recursively split if needed --- + const topLevelChunks = chunk(filteredUpdates, MAX_CHUNK_SIZE); + await map( + topLevelChunks, + async (currentChunk) => { + await uploadChunkWithSplit(currentChunk); }, - { - concurrency: CONCURRENCY, - }, + { concurrency: CONCURRENCY }, ); - // FIXME do this on ctrl+c + // Final persistence of state await uploadState.setValue(successfulUpdates, 'successfulUpdates'); await uploadState.setValue({}, 'pendingUpdates'); await uploadState.setValue({}, 'pendingSafeUpdates'); await uploadState.setValue({}, 'pendingConflictUpdates'); - // progressBar.stop(); const t1 = new Date().getTime(); const totalTime = t1 - t0; logger.info( colors.green( `Successfully uploaded ${ - filteredUpdates.length + Object.keys(successfulUpdates).length } user preferences to partition ${partition} in "${ totalTime / 1000 }" seconds!`, From 9ff89fd23f5ac23a1058e093a1433a822fe5bcc8 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 03:22:29 -0700 Subject: [PATCH 17/72] better concurrency --- .../parsePreferenceIdentifiersFromCsv.ts | 5 +++++ .../uploadPreferenceManagementPreferencesInteractive.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index ae6821c2..c27394be 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -302,11 +302,16 @@ export async function addTranscendIdToPreferences( 'na@gmail.com', 'NOMAIL@GMAIL.COM', 'costco@costco.com', + 'ABC@GMAIL.COM', 'noemail@noemail.com', 'replace@gmail.com', + 'j@gmail.com', 'NONE@YAHOO.COM', + 'JESUS@GMAIL.COM', 'a@gmail.com', + 'ME@ME.COM', 'NEEDEMAIL@GMAIL.COM', + '_NONE@EMAIL.COM', 'NONE1@YAHOO.COM', 'no@no.com', 'none@outlook.com', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 78850ad7..d243b15b 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -23,7 +23,7 @@ import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 50; // FIXME +const CONCURRENCY = 30; // FIXME const MAX_CHUNK_SIZE = 50; // FIXME // Treat these as "retry in place" errors (do NOT split on these). @@ -407,7 +407,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ ) => { let errorMsg = extractErrorMessage(err); if (errorMsg.includes('Too many identifiers')) { - errorMsg += `____${userId.split('___')[0]}`; + errorMsg += `\n ----> ${userId.split('___')[0]}`; } logger.error( colors.red( @@ -539,7 +539,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ colors.yellow( `Failed to upload batch of ${ entries.length - } records with status: ${getStatus( + } records with status: ${getStatus(err)} - ${extractErrorMessage( err, )}. Splitting into two batches: ${left.length} and ${right.length}.`, ), From 6379b4b71b5d67a607bb7adf1698894fb0a1cfe2 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 03:35:07 -0700 Subject: [PATCH 18/72] etter concurrency --- src/lib/pooling/renderDashboard.ts | 15 +++++++++------ .../parsePreferenceIdentifiersFromCsv.ts | 7 +++++++ ...dPreferenceManagementPreferencesInteractive.ts | 10 +++++++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 7ea09c16..8dae1a9d 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -99,14 +99,17 @@ export function renderDashboard( let totalsLine = ''; if (totals) { if (totals.mode === 'upload') { - totalsLine = `Receipts totals — Success: ${totals.success} Skipped: ${ - totals.skipped - } Error: ${ - totals.error - }\n\nThe individual error breakdown is:\n\n${Object.entries( + const formatNumber = (n: number) => n.toLocaleString(); + totalsLine = `Receipts totals — Success: ${formatNumber( + totals.success, + )} Skipped: ${formatNumber(totals.skipped)} Error: ${formatNumber( + totals.error, + )}\n\nThe individual error breakdown is:\n\n${Object.entries( (totals as any).errors || {}, ) - .map(([key, value]) => ` Count[${value}] ${key}`) + .map( + ([key, value]) => ` Count[${formatNumber(value as number)}] ${key}`, + ) .join('\n')}`; } else { totalsLine = diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index c27394be..942ef89a 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -264,6 +264,7 @@ export async function addTranscendIdToPreferences( 'NOEMAILYET@GMAIL.COM', 'noemail@gmail.com', 'noemail@aol.com', + 'IDONTCARE@YAHOO.COM', 'none@none.com', 'noemail@mail.com', 'no@email.com', @@ -313,9 +314,15 @@ export async function addTranscendIdToPreferences( 'NEEDEMAIL@GMAIL.COM', '_NONE@EMAIL.COM', 'NONE1@YAHOO.COM', + 'NOMEMBEREMAIL@COSTCO.COM', + 'MAILDUMP@MAIL.COM', + 'NONE@EMAIL.COM', 'no@no.com', 'none@outlook.com', 'none@yahoo.com', + '123@LIVE.COM', + // FIXME + ...(process.env.EMAIL_LIST || '').split(',').map((email) => email.trim()), ].map((email) => email.toLowerCase()); return preferences.map((pref) => { diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index d243b15b..6b392d7e 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -23,7 +23,7 @@ import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 30; // FIXME +const CONCURRENCY = 25; // FIXME const MAX_CHUNK_SIZE = 50; // FIXME // Treat these as "retry in place" errors (do NOT split on these). @@ -462,7 +462,10 @@ export async function uploadPreferenceManagementPreferencesInteractive({ await markSuccessFor(entries); } catch (err) { const status = getStatus(err); - if (status && RETRYABLE_BATCH_STATUSES.has(status as any)) { + if ( + (status && RETRYABLE_BATCH_STATUSES.has(status as any)) || + (status === 400 && extractErrorMessage(err).includes('Slow down')) + ) { // Retry this SAME batch up to 3 times with backoff 10s let attemptsLeft = 3; while (attemptsLeft > 0) { @@ -471,7 +474,8 @@ export async function uploadPreferenceManagementPreferencesInteractive({ colors.yellow( `Received ${status} for a batch of ${ entries.length - }. Retrying in 10s... (${3 - attemptsLeft}/3)`, + }. Retrying in 10s... (${3 - attemptsLeft}/3)\n + -> ${extractErrorMessage(err)}`, ), ); await sleep(10_000); From 1df22f3f4ff09681974af371634f0f205650b5ff Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 04:28:33 -0700 Subject: [PATCH 19/72] perfectly balanced --- .../parsePreferenceIdentifiersFromCsv.ts | 24 +++++++++++++++++++ ...ferenceManagementPreferencesInteractive.ts | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 942ef89a..7b005748 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -272,13 +272,33 @@ export async function addTranscendIdToPreferences( '123@gmail.com', 'no.no@gmail.com', 'BC@GMAIL.COM', + 'NA@COMCAST.NET', 'NO@YAHOO.COM', + 'R@GMAIL.COM', 'noemail@email.com', + 'NOEMAIL@ME.COM', 'NONAME@GMAIL.COM', 'NOEMAIL@HOTMAIL.COM', 'notoemail@gmail.com', 'NOEMAILATM@YAHOO.COM', 'NO@MAIL.COM', + 'N@N.COM', + 'NO@NOEMAIL.COM', + 'sam@gmail.com', + 'OPTOUT@NOEMAIL.NET', + 'M@GMAIL.COM', + 'ADDEMAIL@YAHOO.COM', + 'JM@YAHOO.COM', + 'NOEMAIL@INTERNET.COM', + 'sam@gmail.com', + 'NO@AOL.COM', + '111@GMAIL.COM', + 'NOTHING@HOTMAIL.COM', + 'CHEN@GMAIL.COM', + 'JM@YAHOO.COM', + 'M@GMAIL.COM', + 'WII@GMAIL.COM', + 'NOTHANKS@COSTCO.COM', 'NOGMAIL@GMAIL.COM', 'NA@NA.COM', 'NOPE@COSTCO.COM', @@ -296,7 +316,11 @@ export async function addTranscendIdToPreferences( 'NO@NE.COM', 'NONE@AOL.COM', 'donthave@hotmail.com', + 'FIRSTNAME.LASTNAME@GMAIL.COM', 'NOEMAIL@COSTO.COM', + 'update@gmail.com', + 'NOMAIL@MAIL.COM ', + 'JUNKMAILER@AOL.COM', 'noemail@yahoo.com', 'EMAIL@GMAIL.COM', 'no@gmail.com', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 6b392d7e..788c778e 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -23,7 +23,7 @@ import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 25; // FIXME +const CONCURRENCY = 20; // FIXME const MAX_CHUNK_SIZE = 50; // FIXME // Treat these as "retry in place" errors (do NOT split on these). @@ -478,7 +478,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ -> ${extractErrorMessage(err)}`, ), ); - await sleep(10_000); + await sleep(5_000); try { await putBatch(entries); await markSuccessFor(entries); From fe40f1738f71a2850e65b878ab79abbc6c634ca9 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 04:59:15 -0700 Subject: [PATCH 20/72] working --- .../parsePreferenceIdentifiersFromCsv.ts | 10 ++++++++++ ...uploadPreferenceManagementPreferencesInteractive.ts | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 7b005748..5fd335e1 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -280,6 +280,16 @@ export async function addTranscendIdToPreferences( 'NONAME@GMAIL.COM', 'NOEMAIL@HOTMAIL.COM', 'notoemail@gmail.com', + 'NOMAIL@MAIL.COM', + 'DONOTHAVE@YAHOO.COM', + 'NAME1@AOL.COM', + 'DAN@GMAIL.COM', + 'NA@YAHOO.COM', + 'NONE.NONE@GMAIL.COM', + 'KC@COSTCO.COM', + 'NONE1@GMAIL.COM', + 'NONE@HOTMAIL.COM', + 'COSTCO@NON.COM', 'NOEMAILATM@YAHOO.COM', 'NO@MAIL.COM', 'N@N.COM', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 788c778e..22b85e9e 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -361,7 +361,11 @@ export async function uploadPreferenceManagementPreferencesInteractive({ uploadedCount += entries.length; // Periodic persistence + logging - if (uploadedCount % LOG_RATE === 0) { + if ( + uploadedCount % LOG_RATE === 0 || + Math.floor((uploadedCount - entries.length) / LOG_RATE) < + Math.floor(uploadedCount / LOG_RATE) + ) { logger.info( colors.green( `Uploaded ${uploadedCount}/${filteredUpdates.length} user preferences to partition ${partition}`, From 1f42f36bd8b0677392fa01c08dafff1f7498a165 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 11:46:40 -0700 Subject: [PATCH 21/72] Mostly working --- .../consent/upload-preferences/impl.ts | 200 +++++++++++++----- src/lib/pooling/renderDashboard.ts | 40 ++-- .../parsePreferenceIdentifiersFromCsv.ts | 3 + ...ferenceManagementPreferencesInteractive.ts | 8 +- 4 files changed, 184 insertions(+), 67 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 15027413..7e052c32 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -97,6 +97,34 @@ function computePoolSize( return { poolSize: desired, cpuCount }; } +/** IPC helpers ------------------------------------------------------------ */ + +function isIpcOpen(w: ChildProcess | undefined | null): boolean { + // @ts-ignore - channel is internal but exists for forked children + const ch = w && (w as any).channel; + // @ts-ignore + return !!(w && w.connected && ch && !ch.destroyed); +} + +function safeSend(w: ChildProcess, msg: unknown): boolean { + if (!isIpcOpen(w)) return false; + try { + w.send?.(msg as any); + return true; + } catch (err: any) { + if ( + err?.code === 'ERR_IPC_CHANNEL_CLOSED' || + err?.code === 'EPIPE' || + err?.errno === -32 + ) { + return false; + } + throw err; + } +} + +/** Receipt helpers -------------------------------------------------------- */ + /** * Find the receipt JSON for a given input file (supports suffixes like __1). * @@ -166,13 +194,9 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { const success = Object.values(json?.successfulUpdates ?? {}).length; const failed = Object.values(json?.failingUpdates ?? {}).length; const errors: Record = {}; - Object.values(json?.failingUpdates ?? {}).forEach((value) => { - const errorMsg = (value as any).error; - if (!errors[errorMsg]) { - errors[errorMsg] = 1; - } else { - errors[errorMsg] += 1; - } + Object.values(json?.failingUpdates ?? {}).forEach((v) => { + const msg = (v as any)?.error ?? 'Unknown error'; + errors[msg] = (errors[msg] ?? 0) + 1; }); return { mode: 'upload', @@ -209,6 +233,44 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { } } +/** stderr → ERROR/WARN indicator helpers ---------------------------------- */ + +// --- helpers to parse stderr lines and classify warn/error --- +function makeLineSplitter(onLine: (line: string) => void) { + let buf = ''; + return (chunk: Buffer | string) => { + buf += chunk.toString('utf8'); + let nl: number; + // eslint-disable-next-line no-cond-assign + while ((nl = buf.indexOf('\n')) !== -1) { + const line = buf.slice(0, nl); + onLine(line); + buf = buf.slice(nl + 1); + } + }; +} + +/** + * Return 'warn' | 'error' when the line is an *explicit* tagged message we care about. + * Otherwise return null so the dashboard ignores it. + * + * @param line + */ +function classifyLevel(line: string): 'warn' | 'error' | null { + // strip ANSI (very light pass; good enough for our tags) + const noAnsi = line.replace(/\x1B\[[0-9;]*m/g, ''); + // look for our child prefixes: "[wN] ERROR ..." or "[wN] WARN ..." + const m = + /^\s*\[w\d+\]\s+(ERROR|WARN|uncaughtException|unhandledRejection)\b/i.exec( + noAnsi, + ); + if (!m) return null; + const tag = m[1].toLowerCase(); + if (tag === 'warn') return 'warn'; + return 'error'; // ERROR, uncaughtException, unhandledRejection → error +} +/** Main ------------------------------------------------------------------- */ + export async function uploadPreferences( this: LocalContext, { @@ -293,7 +355,7 @@ export async function uploadPreferences( >(); const pending = [...files]; const totals = { completed: 0, failed: 0 }; - let activeWorkers = 0; // track only live workers + let activeWorkers = 0; const agg: AnyTotals = !common.dryRun ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } @@ -322,7 +384,15 @@ export async function uploadPreferences( const assignWorkToWorker = (id: number) => { const w = workers.get(id); - if (!w) return; + if (!isIpcOpen(w)) { + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok' as any, + }); + return; + } const filePath = pending.shift(); if (!filePath) { const prev = workerState.get(id) || ({} as WorkerState); @@ -331,11 +401,28 @@ export async function uploadPreferences( busy: false, file: null, startedAt: null, + lastLevel: 'ok' as any, }); return; } - workerState.set(id, { busy: true, file: filePath, startedAt: Date.now() }); - w.send?.({ type: 'task', payload: { filePath, options: common } }); + workerState.set(id, { + busy: true, + file: filePath, + startedAt: Date.now(), + lastLevel: 'ok' as any, + }); + if ( + !safeSend(w!, { type: 'task', payload: { filePath, options: common } }) + ) { + // IPC closed between check and send; requeue and mark idle + pending.unshift(filePath); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok' as any, + }); + } }; const refillIdleWorkers = () => { @@ -352,10 +439,38 @@ export async function uploadPreferences( for (let i = 0; i < poolSize; i += 1) { const child = spawnWorkerProcess(i, modulePath, LOG_DIR, true, isSilent); workers.set(i, child); - workerState.set(i, { busy: false, file: null, startedAt: null }); + workerState.set(i, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok' as any, + }); slotLogPaths.set(i, getWorkerLogPaths(child)); activeWorkers += 1; + // Surface WARN/ERROR status live from stderr + const errLine = makeLineSplitter((line) => { + const lvl = classifyLevel(line); + if (!lvl) return; // ignore untagged stderr noise + const prev = workerState.get(i) || ({} as WorkerState); + if (prev.lastLevel !== lvl) { + workerState.set(i, { ...prev, lastLevel: lvl }); + repaint(); + } + }); + child.stderr?.on('data', errLine); + + child.on('error', (err: any) => { + if ( + err?.code === 'ERR_IPC_CHANNEL_CLOSED' || + err?.code === 'EPIPE' || + err?.errno === -32 + ) { + return; // benign during shutdown/restarts + } + logger.error(colors.red(`Worker ${i} error: ${err?.stack || err}`)); + }); + child.on('message', (msg: any) => { if (!msg || typeof msg !== 'object') return; @@ -379,12 +494,9 @@ export async function uploadPreferences( agg.success += summary.success; agg.skipped += summary.skipped; agg.error += summary.error; - Object.entries(summary.errors).forEach(([key, value]) => { - if (!agg.errors[key]) { - agg.errors[key] = value; - } else { - agg.errors[key] += value; - } + Object.entries(summary.errors).forEach(([k, v]) => { + (agg.errors as Record)[k] = + (agg.errors[k] ?? 0) + (v as number); }); } else if (summary.mode === 'check' && agg.mode === 'check') { agg.totalPending += summary.totalPending; @@ -394,17 +506,25 @@ export async function uploadPreferences( } } - workerState.set(i, { busy: false, file: null, startedAt: null }); + workerState.set(i, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok' as any, + }); refillIdleWorkers(); repaint(); } }); child.on('exit', () => { - // keep the slot so digits still map to a worker index activeWorkers -= 1; - // (leave the ChildProcess object in the map; switcher will read logs via slotLogPaths) - workerState.set(i, { busy: false, file: null, startedAt: null }); + workerState.set(i, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok' as any, + }); repaint(); }); } @@ -417,15 +537,10 @@ export async function uploadPreferences( clearInterval(renderInterval); cleanupSwitcher(); process.stdout.write('\nStopping workers...\n'); - // attempt graceful shutdown for any idles; busy ones will have exited already for (const [, w] of workers) { - if (w && w.connected && (w as any).channel) { - try { - w.send({ type: 'shutdown' }); - } catch {} - } + if (isIpcOpen(w)) safeSend(w!, { type: 'shutdown' }); try { - w.kill('SIGTERM'); + w?.kill('SIGTERM'); } catch {} } this.process.exit(130); @@ -445,19 +560,16 @@ export async function uploadPreferences( ); }; - // NEW: make a helper that avoids ?? and only uses explicit guards function safeGetLogPathsForSlot(id: number) { const live = workers.get(id); - // Prefer live worker paths if the IPC channel is still open - if (live && live.connected && (live as any).channel) { + if (isIpcOpen(live)) { try { - const p = getWorkerLogPaths(live); + const p = getWorkerLogPaths(live!); if (p !== undefined && p !== null) return p; } catch { - // fall through to snapshot + /* fall back */ } } - // Fall back to the snapshot we saved at spawn time return slotLogPaths.get(id); } @@ -466,11 +578,12 @@ export async function uploadPreferences( onAttach: attachScreen, onDetach: detachScreen, onCtrlC: onSigint, - getLogPaths: (id) => safeGetLogPathsForSlot(id), // <— no ?? here + getLogPaths: (id) => safeGetLogPathsForSlot(id), replayBytes: 200 * 1024, replayWhich: ['out', 'err'], onEnterAttachScreen: attachScreen, }); + // hint const maxDigit = Math.min(poolSize - 1, 9); const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; @@ -484,29 +597,20 @@ export async function uploadPreferences( // wait for completion of work (not viewer) await new Promise((resolveWork) => { const check = setInterval(() => { - // If the queue is empty, request shutdown for **idle** workers only. if (pending.length === 0) { for (const [id, w] of workers) { const st = workerState.get(id); - // Only try to signal live children; exited ones keep their slot for log viewing - if (st && !st.busy && w && w.connected && (w as any).channel) { - try { - w.send({ type: 'shutdown' }); - } catch { - /* ignore */ - } + if (st && !st.busy && isIpcOpen(w)) { + safeSend(w!, { type: 'shutdown' }); } } } - // Done when queue is empty and all live workers have exited. if (pending.length === 0 && activeWorkers === 0) { clearInterval(check); clearInterval(renderInterval); - // Persist the final snapshot; keep switcher OPEN for viewer mode repaint(true); - // Print summary line (don’t exit yet) if ((agg as AnyTotals).mode === 'upload') { const a = agg as UploadModeTotals; process.stdout.write( @@ -523,7 +627,6 @@ export async function uploadPreferences( ); } - // Tell the user they can browse logs and press 'q' to quit process.stdout.write( colors.dim( '\nViewer mode — digits to view logs • Tab/Shift+Tab • Esc detach • press q to quit\n', @@ -551,7 +654,6 @@ export async function uploadPreferences( process.stdin.on('data', onKeypress); }); - // Cleanup switcher now that the user quit viewer mode cleanupSwitcher(); process.removeListener('SIGINT', onSigint); } diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 8dae1a9d..11bea65b 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -9,6 +9,8 @@ export interface WorkerState { file?: string | null; /** */ startedAt?: number | null; + /** last severity seen from worker stderr */ + lastLevel?: 'ok' | 'warn' | 'error'; } let lastFrame = ''; @@ -25,8 +27,8 @@ type UploadModeTotals = { skipped: number; /** */ error: number; - /** */ - totals: Record; + /** aggregated error message -> count */ + errors: Record; }; /** * @@ -54,6 +56,10 @@ type RenderOpts = { /** */ final?: boolean; }; +// minimal color helpers (avoid extra deps) +const red = (s: string) => `\x1b[31m${s}\x1b[0m`; +const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`; + /** * * @param poolSize @@ -85,7 +91,10 @@ export function renderDashboard( const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); const lines = [...workerState.entries()].map(([id, s]) => { - const label = s.busy ? 'WORKING' : 'IDLE '; + let label = s.busy ? 'WORKING' : 'IDLE '; + if (s.lastLevel === 'error') label = red('ERROR '); + else if (s.lastLevel === 'warn') label = yellow('WARN '); + const fname = s.file ? basename(s.file) : '-'; const elapsed = s.startedAt ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` @@ -99,18 +108,19 @@ export function renderDashboard( let totalsLine = ''; if (totals) { if (totals.mode === 'upload') { - const formatNumber = (n: number) => n.toLocaleString(); - totalsLine = `Receipts totals — Success: ${formatNumber( - totals.success, - )} Skipped: ${formatNumber(totals.skipped)} Error: ${formatNumber( - totals.error, - )}\n\nThe individual error breakdown is:\n\n${Object.entries( - (totals as any).errors || {}, + const fmt = (n: number) => n.toLocaleString(); + const errorBreakdown = Object.entries( + (totals as UploadModeTotals).errors || {}, ) - .map( - ([key, value]) => ` Count[${formatNumber(value as number)}] ${key}`, - ) - .join('\n')}`; + .map(([k, v]) => ` Count[${fmt(v as number)}] ${k}`) + .join('\n'); + totalsLine = `${ + errorBreakdown + ? `\n\nThe individual error breakdown is:\n\n${errorBreakdown}\n\n` + : '' + }Receipts totals — Success: ${fmt(totals.success)} Skipped: ${fmt( + totals.skipped, + )} Error: ${fmt(totals.error)}`; } else { totalsLine = `Receipts totals — Pending: ${totals.totalPending} PendingConflicts: ${totals.pendingConflicts} ` + @@ -125,8 +135,8 @@ export function renderDashboard( const frame = [ `Parallel uploader — ${poolSize} workers (CPU avail: ${cpuCount})`, - `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, totalsLine, + `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, `[${bar}] ${pct}%`, '', ...lines, diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 5fd335e1..91edae20 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -293,6 +293,9 @@ export async function addTranscendIdToPreferences( 'NOEMAILATM@YAHOO.COM', 'NO@MAIL.COM', 'N@N.COM', + 'NOEMAIL@COSTOC.COM', + 'DONTHAVEEMAIL@YAHOO.COM', + 'PAT@GMAIL.COM', 'NO@NOEMAIL.COM', 'sam@gmail.com', 'OPTOUT@NOEMAIL.NET', diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 22b85e9e..87153335 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -23,7 +23,7 @@ import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 20; // FIXME +const CONCURRENCY = 75; // FIXME 20 const MAX_CHUNK_SIZE = 50; // FIXME // Treat these as "retry in place" errors (do NOT split on these). @@ -468,7 +468,9 @@ export async function uploadPreferenceManagementPreferencesInteractive({ const status = getStatus(err); if ( (status && RETRYABLE_BATCH_STATUSES.has(status as any)) || - (status === 400 && extractErrorMessage(err).includes('Slow down')) + (status === 400 && + (extractErrorMessage(err).includes('Slow down') || + extractErrorMessage(err).includes('please try again shortly'))) ) { // Retry this SAME batch up to 3 times with backoff 10s let attemptsLeft = 3; @@ -482,7 +484,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ -> ${extractErrorMessage(err)}`, ), ); - await sleep(5_000); + await sleep(3_000); try { await putBatch(entries); await markSuccessFor(entries); From 497f2a5b33d1ded4889f961bf1f0efa4fc6829ef Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 14:58:00 -0700 Subject: [PATCH 22/72] Uses new function --- .gitignore | 1 + src/lib/pooling/attachWorkerHandlers.ts | 16 ++++++---------- ...PreferenceManagementPreferencesInteractive.ts | 2 +- tsconfig.tsbuildinfo | 1 + 4 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 tsconfig.tsbuildinfo diff --git a/.gitignore b/.gitignore index 9c66142e..bb8890aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ transcend.yml +notes.txt transcend-privacy-requests-cache.json cron-identifiers.csv manual-enrichment-identifiers.csv diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts index 04db77df..2fe2002c 100644 --- a/src/lib/pooling/attachWorkerHandlers.ts +++ b/src/lib/pooling/attachWorkerHandlers.ts @@ -76,14 +76,12 @@ export function attachWorkerHandlers( if (msg.type === 'ready') { // First work assignment - assignWorkToWorker( - id, - filesPending, + assignWorkToWorker(id, common, { + pending: filesPending, workers, workerState, - common, repaint, - ); + }); } else if (msg.type === 'result') { // Update success/failure counters const { ok, filePath, error } = msg.payload; @@ -105,14 +103,12 @@ export function attachWorkerHandlers( } // Keep the worker busy until the queue is empty - assignWorkToWorker( - id, - filesPending, + assignWorkToWorker(id, common, { + pending: filesPending, workers, workerState, - common, repaint, - ); + }); } }); diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts index 87153335..14f75ca0 100644 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts @@ -287,7 +287,7 @@ export async function uploadPreferenceManagementPreferencesInteractive({ acc[userId] = true; } return acc; - }, {} as Record), + }, {} as Record), 'pendingUpdates', ); await uploadState.setValue({}, 'failingUpdates'); diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 00000000..3ca9d27a --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vitest.config.ts","./scripts/buildpathfinderjsonschema.ts","./scripts/buildreadmedocs.ts","./scripts/buildtranscendjsonschema.ts","./src/app.ts","./src/codecs.ts","./src/constants.ts","./src/context.ts","./src/enums.ts","./src/index.ts","./src/logger.ts","./src/bin/bash-complete.ts","./src/bin/cli.ts","./src/bin/deprecated-command.ts","./src/commands/admin/routes.ts","./src/commands/admin/generate-api-keys/command.ts","./src/commands/admin/generate-api-keys/impl.ts","./src/commands/admin/generate-api-keys/readme.ts","./src/commands/consent/routes.ts","./src/commands/consent/build-xdi-sync-endpoint/command.ts","./src/commands/consent/build-xdi-sync-endpoint/impl.ts","./src/commands/consent/build-xdi-sync-endpoint/readme.ts","./src/commands/consent/pull-consent-metrics/command.ts","./src/commands/consent/pull-consent-metrics/impl.ts","./src/commands/consent/pull-consent-metrics/readme.ts","./src/commands/consent/pull-consent-preferences/command.ts","./src/commands/consent/pull-consent-preferences/impl.ts","./src/commands/consent/pull-consent-preferences/readme.ts","./src/commands/consent/update-consent-manager/command.ts","./src/commands/consent/update-consent-manager/impl.ts","./src/commands/consent/update-consent-manager/readme.ts","./src/commands/consent/upload-consent-preferences/command.ts","./src/commands/consent/upload-consent-preferences/impl.ts","./src/commands/consent/upload-consent-preferences/readme.ts","./src/commands/consent/upload-cookies-from-csv/command.ts","./src/commands/consent/upload-cookies-from-csv/impl.ts","./src/commands/consent/upload-cookies-from-csv/readme.ts","./src/commands/consent/upload-data-flows-from-csv/command.ts","./src/commands/consent/upload-data-flows-from-csv/impl.ts","./src/commands/consent/upload-data-flows-from-csv/readme.ts","./src/commands/consent/upload-preferences/collectcsvfilesorexit.ts","./src/commands/consent/upload-preferences/command.ts","./src/commands/consent/upload-preferences/computefiles.ts","./src/commands/consent/upload-preferences/impl.ts","./src/commands/consent/upload-preferences/readme.ts","./src/commands/consent/upload-preferences/runchild.ts","./src/commands/inventory/routes.ts","./src/commands/inventory/consent-manager-service-json-to-yml/command.ts","./src/commands/inventory/consent-manager-service-json-to-yml/impl.ts","./src/commands/inventory/consent-manager-service-json-to-yml/readme.ts","./src/commands/inventory/consent-managers-to-business-entities/command.ts","./src/commands/inventory/consent-managers-to-business-entities/impl.ts","./src/commands/inventory/consent-managers-to-business-entities/readme.ts","./src/commands/inventory/derive-data-silos-from-data-flows/command.ts","./src/commands/inventory/derive-data-silos-from-data-flows/impl.ts","./src/commands/inventory/derive-data-silos-from-data-flows/readme.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/command.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/impl.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/readme.ts","./src/commands/inventory/discover-silos/command.ts","./src/commands/inventory/discover-silos/impl.ts","./src/commands/inventory/discover-silos/readme.ts","./src/commands/inventory/pull/command.ts","./src/commands/inventory/pull/impl.ts","./src/commands/inventory/pull/readme.ts","./src/commands/inventory/pull-datapoints/command.ts","./src/commands/inventory/pull-datapoints/impl.ts","./src/commands/inventory/pull-datapoints/readme.ts","./src/commands/inventory/pull-unstructured-discovery-files/command.ts","./src/commands/inventory/pull-unstructured-discovery-files/impl.ts","./src/commands/inventory/pull-unstructured-discovery-files/readme.ts","./src/commands/inventory/push/command.ts","./src/commands/inventory/push/impl.ts","./src/commands/inventory/push/readme.ts","./src/commands/inventory/scan-packages/command.ts","./src/commands/inventory/scan-packages/impl.ts","./src/commands/inventory/scan-packages/readme.ts","./src/commands/migration/routes.ts","./src/commands/migration/sync-ot/command.ts","./src/commands/migration/sync-ot/impl.ts","./src/commands/migration/sync-ot/readme.ts","./src/commands/request/routes.ts","./src/commands/request/approve/command.ts","./src/commands/request/approve/impl.ts","./src/commands/request/approve/readme.ts","./src/commands/request/cancel/command.ts","./src/commands/request/cancel/impl.ts","./src/commands/request/cancel/readme.ts","./src/commands/request/cron/routes.ts","./src/commands/request/cron/mark-identifiers-completed/command.ts","./src/commands/request/cron/mark-identifiers-completed/impl.ts","./src/commands/request/cron/mark-identifiers-completed/readme.ts","./src/commands/request/cron/pull-identifiers/command.ts","./src/commands/request/cron/pull-identifiers/impl.ts","./src/commands/request/cron/pull-identifiers/readme.ts","./src/commands/request/cron/pull-profiles/command.ts","./src/commands/request/cron/pull-profiles/impl.ts","./src/commands/request/download-files/command.ts","./src/commands/request/download-files/impl.ts","./src/commands/request/download-files/readme.ts","./src/commands/request/enricher-restart/command.ts","./src/commands/request/enricher-restart/impl.ts","./src/commands/request/enricher-restart/readme.ts","./src/commands/request/export/command.ts","./src/commands/request/export/impl.ts","./src/commands/request/export/readme.ts","./src/commands/request/mark-silent/command.ts","./src/commands/request/mark-silent/impl.ts","./src/commands/request/mark-silent/readme.ts","./src/commands/request/notify-additional-time/command.ts","./src/commands/request/notify-additional-time/impl.ts","./src/commands/request/notify-additional-time/readme.ts","./src/commands/request/preflight/routes.ts","./src/commands/request/preflight/pull-identifiers/command.ts","./src/commands/request/preflight/pull-identifiers/impl.ts","./src/commands/request/preflight/pull-identifiers/readme.ts","./src/commands/request/preflight/push-identifiers/command.ts","./src/commands/request/preflight/push-identifiers/impl.ts","./src/commands/request/preflight/push-identifiers/readme.ts","./src/commands/request/reject-unverified-identifiers/command.ts","./src/commands/request/reject-unverified-identifiers/impl.ts","./src/commands/request/reject-unverified-identifiers/readme.ts","./src/commands/request/restart/command.ts","./src/commands/request/restart/impl.ts","./src/commands/request/restart/readme.ts","./src/commands/request/skip-preflight-jobs/command.ts","./src/commands/request/skip-preflight-jobs/impl.ts","./src/commands/request/skip-preflight-jobs/readme.ts","./src/commands/request/system/routes.ts","./src/commands/request/system/mark-request-data-silos-completed/command.ts","./src/commands/request/system/mark-request-data-silos-completed/impl.ts","./src/commands/request/system/mark-request-data-silos-completed/readme.ts","./src/commands/request/system/retry-request-data-silos/command.ts","./src/commands/request/system/retry-request-data-silos/impl.ts","./src/commands/request/system/retry-request-data-silos/readme.ts","./src/commands/request/system/skip-request-data-silos/command.ts","./src/commands/request/system/skip-request-data-silos/impl.ts","./src/commands/request/system/skip-request-data-silos/readme.ts","./src/commands/request/upload/command.ts","./src/commands/request/upload/impl.ts","./src/commands/request/upload/readme.ts","./src/lib/bluebird-replace.ts","./src/lib/mergetranscendinputs.ts","./src/lib/readtranscendyaml.ts","./src/lib/@types/fuzzysearch.d.ts","./src/lib/@types/inquirer-autocomplete-prompt.d.ts","./src/lib/ai/transcendpromptmanager.ts","./src/lib/ai/filternullishvaluesfromobject.ts","./src/lib/ai/getgitfilesthatchanged.ts","./src/lib/ai/index.ts","./src/lib/ai/removelinks.ts","./src/lib/api-keys/generatecrossaccountapikeys.ts","./src/lib/api-keys/index.ts","./src/lib/api-keys/listdirectories.ts","./src/lib/api-keys/listfiles.ts","./src/lib/api-keys/validatetranscendauth.ts","./src/lib/cli/common-parameters.ts","./src/lib/cli/done-input-validation.ts","./src/lib/cli/legacy-commands.ts","./src/lib/cli/parsers.ts","./src/lib/code-scanning/constants.ts","./src/lib/code-scanning/findcodepackagesinfolder.ts","./src/lib/code-scanning/findfilestoscan.ts","./src/lib/code-scanning/index.ts","./src/lib/code-scanning/types.ts","./src/lib/code-scanning/integrations/cocoapods.ts","./src/lib/code-scanning/integrations/composerjson.ts","./src/lib/code-scanning/integrations/gemfile.ts","./src/lib/code-scanning/integrations/gradle.ts","./src/lib/code-scanning/integrations/index.ts","./src/lib/code-scanning/integrations/javascriptpackagejson.ts","./src/lib/code-scanning/integrations/pubspec.ts","./src/lib/code-scanning/integrations/pythonrequirementstxt.ts","./src/lib/code-scanning/integrations/swift.ts","./src/lib/consent-manager/buildxdisyncendpoint.ts","./src/lib/consent-manager/consentmanagerstobusinessentities.ts","./src/lib/consent-manager/createconsenttoken.ts","./src/lib/consent-manager/dataflowstodatasilos.ts","./src/lib/consent-manager/domaintohost.ts","./src/lib/consent-manager/fetchconsentpreferences.ts","./src/lib/consent-manager/index.ts","./src/lib/consent-manager/pullconsentmanagermetrics.ts","./src/lib/consent-manager/types.ts","./src/lib/consent-manager/updateconsentmanagerversiontolatest.ts","./src/lib/consent-manager/uploadconsents.ts","./src/lib/consent-manager/uploadcookiesfromcsv.ts","./src/lib/consent-manager/uploaddataflowsfromcsv.ts","./src/lib/cron/index.ts","./src/lib/cron/markcronidentifiercompleted.ts","./src/lib/cron/markrequestdatasiloidscompleted.ts","./src/lib/cron/pullchunkedcustomsilooutstandingidentifiers.ts","./src/lib/cron/pullcronpageofidentifiers.ts","./src/lib/cron/pullcustomsilooutstandingidentifiers.ts","./src/lib/cron/pushcronidentifiersfromcsv.ts","./src/lib/cron/writecsv.ts","./src/lib/data-inventory/index.ts","./src/lib/data-inventory/pullalldatapoints.ts","./src/lib/data-inventory/pullunstructuredsubdatapointrecommendations.ts","./src/lib/docgen/buildexamples.ts","./src/lib/docgen/getexampledate.ts","./src/lib/graphql/addmessagestopromptrun.ts","./src/lib/graphql/buildtranscendgraphqlclient.ts","./src/lib/graphql/createsombragotinstance.ts","./src/lib/graphql/createtranscendconsentgotinstance.ts","./src/lib/graphql/deployconsentmanager.ts","./src/lib/graphql/fetchactivesilodiscoplugin.ts","./src/lib/graphql/fetchallactionitemcollections.ts","./src/lib/graphql/fetchallactionitems.ts","./src/lib/graphql/fetchallactions.ts","./src/lib/graphql/fetchallagentfiles.ts","./src/lib/graphql/fetchallagentfunctions.ts","./src/lib/graphql/fetchallagents.ts","./src/lib/graphql/fetchallassessmenttemplates.ts","./src/lib/graphql/fetchallassessments.ts","./src/lib/graphql/fetchallattributekeys.ts","./src/lib/graphql/fetchallattributes.ts","./src/lib/graphql/fetchallbusinessentities.ts","./src/lib/graphql/fetchallcodepackages.ts","./src/lib/graphql/fetchallcookies.ts","./src/lib/graphql/fetchalldatacategories.ts","./src/lib/graphql/fetchalldataflows.ts","./src/lib/graphql/fetchallmessages.ts","./src/lib/graphql/fetchallpolicies.ts","./src/lib/graphql/fetchallpreferencetopics.ts","./src/lib/graphql/fetchallprivacycenters.ts","./src/lib/graphql/fetchallprocessingactivities.ts","./src/lib/graphql/fetchallprocessingpurposes.ts","./src/lib/graphql/fetchallpurposes.ts","./src/lib/graphql/fetchallpurposesandpreferences.ts","./src/lib/graphql/fetchallrepositories.ts","./src/lib/graphql/fetchallrequestenrichers.ts","./src/lib/graphql/fetchallrequestidentifiermetadata.ts","./src/lib/graphql/fetchallrequestidentifiers.ts","./src/lib/graphql/fetchallrequests.ts","./src/lib/graphql/fetchallsoftwaredevelopmentkits.ts","./src/lib/graphql/fetchallteams.ts","./src/lib/graphql/fetchallusers.ts","./src/lib/graphql/fetchallvendors.ts","./src/lib/graphql/fetchapikeys.ts","./src/lib/graphql/fetchcatalogs.ts","./src/lib/graphql/fetchconsentmanagerid.ts","./src/lib/graphql/fetchdatasubjects.ts","./src/lib/graphql/fetchidentifiers.ts","./src/lib/graphql/fetchlargelanguagemodels.ts","./src/lib/graphql/fetchprivacycenterid.ts","./src/lib/graphql/fetchpromptgroups.ts","./src/lib/graphql/fetchpromptpartials.ts","./src/lib/graphql/fetchpromptthreads.ts","./src/lib/graphql/fetchprompts.ts","./src/lib/graphql/fetchrequestdatasilo.ts","./src/lib/graphql/fetchrequestdatasiloactivecount.ts","./src/lib/graphql/fetchrequestfilesforrequest.ts","./src/lib/graphql/formatattributevalues.ts","./src/lib/graphql/formatregions.ts","./src/lib/graphql/index.ts","./src/lib/graphql/loginuser.ts","./src/lib/graphql/makegraphqlrequest.ts","./src/lib/graphql/manageapikeys.ts","./src/lib/graphql/parseassessmentdisplaylogic.ts","./src/lib/graphql/parseassessmentrisklogic.ts","./src/lib/graphql/pulltranscendconfiguration.ts","./src/lib/graphql/reportpromptrun.ts","./src/lib/graphql/retryrequestenricher.ts","./src/lib/graphql/setresourceattributes.ts","./src/lib/graphql/syncaction.ts","./src/lib/graphql/syncactionitemcollections.ts","./src/lib/graphql/syncactionitems.ts","./src/lib/graphql/syncagentfiles.ts","./src/lib/graphql/syncagentfunctions.ts","./src/lib/graphql/syncagents.ts","./src/lib/graphql/syncattribute.ts","./src/lib/graphql/syncbusinessentities.ts","./src/lib/graphql/synccodepackages.ts","./src/lib/graphql/syncconfigurationtotranscend.ts","./src/lib/graphql/syncconsentmanager.ts","./src/lib/graphql/synccookies.ts","./src/lib/graphql/syncdatacategories.ts","./src/lib/graphql/syncdataflows.ts","./src/lib/graphql/syncdatasilos.ts","./src/lib/graphql/syncdatasubject.ts","./src/lib/graphql/syncenrichers.ts","./src/lib/graphql/syncidentifier.ts","./src/lib/graphql/syncintlmessages.ts","./src/lib/graphql/syncpartitions.ts","./src/lib/graphql/syncpolicies.ts","./src/lib/graphql/syncprivacycenter.ts","./src/lib/graphql/syncprocessingactivities.ts","./src/lib/graphql/syncprocessingpurposes.ts","./src/lib/graphql/syncpromptgroups.ts","./src/lib/graphql/syncpromptpartials.ts","./src/lib/graphql/syncprompts.ts","./src/lib/graphql/syncrepositories.ts","./src/lib/graphql/syncsoftwaredevelopmentkits.ts","./src/lib/graphql/syncteams.ts","./src/lib/graphql/synctemplates.ts","./src/lib/graphql/syncvendors.ts","./src/lib/graphql/uploadsilodiscoveryresults.ts","./src/lib/graphql/gqls/requestdatasilo.ts","./src/lib/graphql/gqls/requestenricher.ts","./src/lib/graphql/gqls/requestidentifier.ts","./src/lib/graphql/gqls/action.ts","./src/lib/graphql/gqls/actionitem.ts","./src/lib/graphql/gqls/actionitemcollection.ts","./src/lib/graphql/gqls/agent.ts","./src/lib/graphql/gqls/agentfile.ts","./src/lib/graphql/gqls/agentfunction.ts","./src/lib/graphql/gqls/apikey.ts","./src/lib/graphql/gqls/assessment.ts","./src/lib/graphql/gqls/assessmenttemplate.ts","./src/lib/graphql/gqls/attribute.ts","./src/lib/graphql/gqls/attributekey.ts","./src/lib/graphql/gqls/auth.ts","./src/lib/graphql/gqls/businessentity.ts","./src/lib/graphql/gqls/catalog.ts","./src/lib/graphql/gqls/codepackage.ts","./src/lib/graphql/gqls/consentmanager.ts","./src/lib/graphql/gqls/consentmanagermetrics.ts","./src/lib/graphql/gqls/datacategory.ts","./src/lib/graphql/gqls/datapoint.ts","./src/lib/graphql/gqls/datasilo.ts","./src/lib/graphql/gqls/datasubject.ts","./src/lib/graphql/gqls/enricher.ts","./src/lib/graphql/gqls/entry.ts","./src/lib/graphql/gqls/identifier.ts","./src/lib/graphql/gqls/index.ts","./src/lib/graphql/gqls/largelanguagemodel.ts","./src/lib/graphql/gqls/message.ts","./src/lib/graphql/gqls/organization.ts","./src/lib/graphql/gqls/policy.ts","./src/lib/graphql/gqls/preferencetopic.ts","./src/lib/graphql/gqls/privacycenter.ts","./src/lib/graphql/gqls/processingactivity.ts","./src/lib/graphql/gqls/processingpurpose.ts","./src/lib/graphql/gqls/prompt.ts","./src/lib/graphql/gqls/promptrun.ts","./src/lib/graphql/gqls/promptthread.ts","./src/lib/graphql/gqls/purpose.ts","./src/lib/graphql/gqls/repository.ts","./src/lib/graphql/gqls/request.ts","./src/lib/graphql/gqls/requestfile.ts","./src/lib/graphql/gqls/silodiscovery.ts","./src/lib/graphql/gqls/softwaredevelopmentkit.ts","./src/lib/graphql/gqls/sombraversion.ts","./src/lib/graphql/gqls/team.ts","./src/lib/graphql/gqls/template.ts","./src/lib/graphql/gqls/user.ts","./src/lib/graphql/gqls/vendor.ts","./src/lib/helpers/buildaiintegrationtype.ts","./src/lib/helpers/buildenabledroutetype.ts","./src/lib/helpers/index.ts","./src/lib/helpers/inquirer.ts","./src/lib/helpers/parsevariablesfromstring.ts","./src/lib/manual-enrichment/enrichprivacyrequest.ts","./src/lib/manual-enrichment/index.ts","./src/lib/manual-enrichment/pullmanualenrichmentidentifierstocsv.ts","./src/lib/manual-enrichment/pushmanualenrichmentidentifiersfromcsv.ts","./src/lib/onetrust/createonetrustgotinstance.ts","./src/lib/onetrust/index.ts","./src/lib/onetrust/endpoints/getlistofonetrustassessments.ts","./src/lib/onetrust/endpoints/getonetrustassessment.ts","./src/lib/onetrust/endpoints/getonetrustrisk.ts","./src/lib/onetrust/endpoints/getonetrustuser.ts","./src/lib/onetrust/endpoints/index.ts","./src/lib/onetrust/helpers/converttoemptystrings.ts","./src/lib/onetrust/helpers/enrichonetrustassessment.ts","./src/lib/onetrust/helpers/index.ts","./src/lib/onetrust/helpers/onetrustassessmenttojson.ts","./src/lib/onetrust/helpers/parseclisyncotarguments.ts","./src/lib/onetrust/helpers/synconetrustassessmenttodisk.ts","./src/lib/onetrust/helpers/synconetrustassessmenttotranscend.ts","./src/lib/onetrust/helpers/synconetrustassessmentsfromfile.ts","./src/lib/onetrust/helpers/synconetrustassessmentsfromonetrust.ts","./src/lib/onetrust/helpers/tests/converttoemptystrings.test.ts","./src/lib/pooling/assignworktoworker.ts","./src/lib/pooling/attachworkerhandlers.ts","./src/lib/pooling/computepoolsize.ts","./src/lib/pooling/ensurelogfile.ts","./src/lib/pooling/index.ts","./src/lib/pooling/installinteractiveswitcher.ts","./src/lib/pooling/logrotation.ts","./src/lib/pooling/openterminal.ts","./src/lib/pooling/renderdashboard.ts","./src/lib/pooling/spawnworkerprocess.ts","./src/lib/preference-management/checkifpendingpreferenceupdatesarenoop.ts","./src/lib/preference-management/checkifpendingpreferenceupdatescauseconflict.ts","./src/lib/preference-management/codecs.ts","./src/lib/preference-management/getpreferenceupdatesfromrow.ts","./src/lib/preference-management/getpreferencesforidentifiers.ts","./src/lib/preference-management/index.ts","./src/lib/preference-management/parsepreferenceandpurposevaluesfromcsv.ts","./src/lib/preference-management/parsepreferencefileformatfromcsv.ts","./src/lib/preference-management/parsepreferenceidentifiersfromcsv.ts","./src/lib/preference-management/parsepreferencemanagementcsv.ts","./src/lib/preference-management/uploadpreferencemanagementpreferencesinteractive.ts","./src/lib/preference-management/tests/checkifpendingpreferenceupdatesarenoop.test.ts","./src/lib/preference-management/tests/checkifpendingpreferenceupdatescauseconflict.test.ts","./src/lib/preference-management/tests/getpreferenceupdatesfromrow.test.ts","./src/lib/requests/approveprivacyrequests.ts","./src/lib/requests/bulkrestartrequests.ts","./src/lib/requests/bulkretryenrichers.ts","./src/lib/requests/cancelprivacyrequests.ts","./src/lib/requests/constants.ts","./src/lib/requests/downloadprivacyrequestfiles.ts","./src/lib/requests/extractclienterror.ts","./src/lib/requests/filterrows.ts","./src/lib/requests/fuzzymatchcolumns.ts","./src/lib/requests/getfilemetadataforprivacyrequests.ts","./src/lib/requests/getuniquevaluesforcolumn.ts","./src/lib/requests/index.ts","./src/lib/requests/mapcolumnstoattributes.ts","./src/lib/requests/mapcolumnstoidentifiers.ts","./src/lib/requests/mapcsvcolumnstoapi.ts","./src/lib/requests/mapcsvrowstorequestinputs.ts","./src/lib/requests/mapenumvalues.ts","./src/lib/requests/maprequestenumvalues.ts","./src/lib/requests/marksilentprivacyrequests.ts","./src/lib/requests/notifyprivacyrequestsadditionaltime.ts","./src/lib/requests/parseattributesfromstring.ts","./src/lib/requests/pullprivacyrequests.ts","./src/lib/requests/readcsv.ts","./src/lib/requests/removeunverifiedrequestidentifiers.ts","./src/lib/requests/restartprivacyrequest.ts","./src/lib/requests/retryrequestdatasilos.ts","./src/lib/requests/skippreflightjobs.ts","./src/lib/requests/skiprequestdatasilos.ts","./src/lib/requests/splitcsvtolist.ts","./src/lib/requests/streamprivacyrequestfiles.ts","./src/lib/requests/submitprivacyrequest.ts","./src/lib/requests/uploadprivacyrequestsfromcsv.ts","./src/lib/requests/tests/fuzzymatchcolumns.test.ts","./src/lib/requests/tests/getuniquevaluesforcolumn.test.ts","./src/lib/requests/tests/mapcsvrowstorequestinputs.test.ts","./src/lib/requests/tests/parseattributesfromstring.test.ts","./src/lib/requests/tests/readcsv.test.ts","./src/lib/requests/tests/splitcsvtolist.test.ts","./src/lib/tests/transcendpromptmanager.test.ts","./src/lib/tests/codebase.test.ts","./src/lib/tests/examples.test.ts","./src/lib/tests/filternullvaluesfromobject.test.ts","./src/lib/tests/findcodepackagesinfolder.test.ts","./src/lib/tests/getgitfilesthatchanged.test.ts","./src/lib/tests/legacycommands.test.ts","./src/lib/tests/mergetranscendinputs.test.ts","./src/lib/tests/readtranscendyaml.test.ts","./src/lib/tests/removelinks.test.ts","./src/lib/tests/helpers/capturelogs.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file From 1344aa911ca228fc4186bf3f4b8a65bda9306307 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 14:59:54 -0700 Subject: [PATCH 23/72] rm --- package.json | 3 +-- tsconfig.tsbuildinfo | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 tsconfig.tsbuildinfo diff --git a/package.json b/package.json index 40915f8b..1de8d0a9 100644 --- a/package.json +++ b/package.json @@ -131,8 +131,7 @@ "query-string": "=7.0.0", "semver": "^7.6.0", "undici": "^5.22.1", - "yargs-parser": "^21.1.1", - "bluebird": "^3.7.2" + "yargs-parser": "^21.1.1" }, "devDependencies": { "@types/JSONStream": "npm:@types/jsonstream@^0.8.33", diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo deleted file mode 100644 index 3ca9d27a..00000000 --- a/tsconfig.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"root":["./vitest.config.ts","./scripts/buildpathfinderjsonschema.ts","./scripts/buildreadmedocs.ts","./scripts/buildtranscendjsonschema.ts","./src/app.ts","./src/codecs.ts","./src/constants.ts","./src/context.ts","./src/enums.ts","./src/index.ts","./src/logger.ts","./src/bin/bash-complete.ts","./src/bin/cli.ts","./src/bin/deprecated-command.ts","./src/commands/admin/routes.ts","./src/commands/admin/generate-api-keys/command.ts","./src/commands/admin/generate-api-keys/impl.ts","./src/commands/admin/generate-api-keys/readme.ts","./src/commands/consent/routes.ts","./src/commands/consent/build-xdi-sync-endpoint/command.ts","./src/commands/consent/build-xdi-sync-endpoint/impl.ts","./src/commands/consent/build-xdi-sync-endpoint/readme.ts","./src/commands/consent/pull-consent-metrics/command.ts","./src/commands/consent/pull-consent-metrics/impl.ts","./src/commands/consent/pull-consent-metrics/readme.ts","./src/commands/consent/pull-consent-preferences/command.ts","./src/commands/consent/pull-consent-preferences/impl.ts","./src/commands/consent/pull-consent-preferences/readme.ts","./src/commands/consent/update-consent-manager/command.ts","./src/commands/consent/update-consent-manager/impl.ts","./src/commands/consent/update-consent-manager/readme.ts","./src/commands/consent/upload-consent-preferences/command.ts","./src/commands/consent/upload-consent-preferences/impl.ts","./src/commands/consent/upload-consent-preferences/readme.ts","./src/commands/consent/upload-cookies-from-csv/command.ts","./src/commands/consent/upload-cookies-from-csv/impl.ts","./src/commands/consent/upload-cookies-from-csv/readme.ts","./src/commands/consent/upload-data-flows-from-csv/command.ts","./src/commands/consent/upload-data-flows-from-csv/impl.ts","./src/commands/consent/upload-data-flows-from-csv/readme.ts","./src/commands/consent/upload-preferences/collectcsvfilesorexit.ts","./src/commands/consent/upload-preferences/command.ts","./src/commands/consent/upload-preferences/computefiles.ts","./src/commands/consent/upload-preferences/impl.ts","./src/commands/consent/upload-preferences/readme.ts","./src/commands/consent/upload-preferences/runchild.ts","./src/commands/inventory/routes.ts","./src/commands/inventory/consent-manager-service-json-to-yml/command.ts","./src/commands/inventory/consent-manager-service-json-to-yml/impl.ts","./src/commands/inventory/consent-manager-service-json-to-yml/readme.ts","./src/commands/inventory/consent-managers-to-business-entities/command.ts","./src/commands/inventory/consent-managers-to-business-entities/impl.ts","./src/commands/inventory/consent-managers-to-business-entities/readme.ts","./src/commands/inventory/derive-data-silos-from-data-flows/command.ts","./src/commands/inventory/derive-data-silos-from-data-flows/impl.ts","./src/commands/inventory/derive-data-silos-from-data-flows/readme.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/command.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/impl.ts","./src/commands/inventory/derive-data-silos-from-data-flows-cross-instance/readme.ts","./src/commands/inventory/discover-silos/command.ts","./src/commands/inventory/discover-silos/impl.ts","./src/commands/inventory/discover-silos/readme.ts","./src/commands/inventory/pull/command.ts","./src/commands/inventory/pull/impl.ts","./src/commands/inventory/pull/readme.ts","./src/commands/inventory/pull-datapoints/command.ts","./src/commands/inventory/pull-datapoints/impl.ts","./src/commands/inventory/pull-datapoints/readme.ts","./src/commands/inventory/pull-unstructured-discovery-files/command.ts","./src/commands/inventory/pull-unstructured-discovery-files/impl.ts","./src/commands/inventory/pull-unstructured-discovery-files/readme.ts","./src/commands/inventory/push/command.ts","./src/commands/inventory/push/impl.ts","./src/commands/inventory/push/readme.ts","./src/commands/inventory/scan-packages/command.ts","./src/commands/inventory/scan-packages/impl.ts","./src/commands/inventory/scan-packages/readme.ts","./src/commands/migration/routes.ts","./src/commands/migration/sync-ot/command.ts","./src/commands/migration/sync-ot/impl.ts","./src/commands/migration/sync-ot/readme.ts","./src/commands/request/routes.ts","./src/commands/request/approve/command.ts","./src/commands/request/approve/impl.ts","./src/commands/request/approve/readme.ts","./src/commands/request/cancel/command.ts","./src/commands/request/cancel/impl.ts","./src/commands/request/cancel/readme.ts","./src/commands/request/cron/routes.ts","./src/commands/request/cron/mark-identifiers-completed/command.ts","./src/commands/request/cron/mark-identifiers-completed/impl.ts","./src/commands/request/cron/mark-identifiers-completed/readme.ts","./src/commands/request/cron/pull-identifiers/command.ts","./src/commands/request/cron/pull-identifiers/impl.ts","./src/commands/request/cron/pull-identifiers/readme.ts","./src/commands/request/cron/pull-profiles/command.ts","./src/commands/request/cron/pull-profiles/impl.ts","./src/commands/request/download-files/command.ts","./src/commands/request/download-files/impl.ts","./src/commands/request/download-files/readme.ts","./src/commands/request/enricher-restart/command.ts","./src/commands/request/enricher-restart/impl.ts","./src/commands/request/enricher-restart/readme.ts","./src/commands/request/export/command.ts","./src/commands/request/export/impl.ts","./src/commands/request/export/readme.ts","./src/commands/request/mark-silent/command.ts","./src/commands/request/mark-silent/impl.ts","./src/commands/request/mark-silent/readme.ts","./src/commands/request/notify-additional-time/command.ts","./src/commands/request/notify-additional-time/impl.ts","./src/commands/request/notify-additional-time/readme.ts","./src/commands/request/preflight/routes.ts","./src/commands/request/preflight/pull-identifiers/command.ts","./src/commands/request/preflight/pull-identifiers/impl.ts","./src/commands/request/preflight/pull-identifiers/readme.ts","./src/commands/request/preflight/push-identifiers/command.ts","./src/commands/request/preflight/push-identifiers/impl.ts","./src/commands/request/preflight/push-identifiers/readme.ts","./src/commands/request/reject-unverified-identifiers/command.ts","./src/commands/request/reject-unverified-identifiers/impl.ts","./src/commands/request/reject-unverified-identifiers/readme.ts","./src/commands/request/restart/command.ts","./src/commands/request/restart/impl.ts","./src/commands/request/restart/readme.ts","./src/commands/request/skip-preflight-jobs/command.ts","./src/commands/request/skip-preflight-jobs/impl.ts","./src/commands/request/skip-preflight-jobs/readme.ts","./src/commands/request/system/routes.ts","./src/commands/request/system/mark-request-data-silos-completed/command.ts","./src/commands/request/system/mark-request-data-silos-completed/impl.ts","./src/commands/request/system/mark-request-data-silos-completed/readme.ts","./src/commands/request/system/retry-request-data-silos/command.ts","./src/commands/request/system/retry-request-data-silos/impl.ts","./src/commands/request/system/retry-request-data-silos/readme.ts","./src/commands/request/system/skip-request-data-silos/command.ts","./src/commands/request/system/skip-request-data-silos/impl.ts","./src/commands/request/system/skip-request-data-silos/readme.ts","./src/commands/request/upload/command.ts","./src/commands/request/upload/impl.ts","./src/commands/request/upload/readme.ts","./src/lib/bluebird-replace.ts","./src/lib/mergetranscendinputs.ts","./src/lib/readtranscendyaml.ts","./src/lib/@types/fuzzysearch.d.ts","./src/lib/@types/inquirer-autocomplete-prompt.d.ts","./src/lib/ai/transcendpromptmanager.ts","./src/lib/ai/filternullishvaluesfromobject.ts","./src/lib/ai/getgitfilesthatchanged.ts","./src/lib/ai/index.ts","./src/lib/ai/removelinks.ts","./src/lib/api-keys/generatecrossaccountapikeys.ts","./src/lib/api-keys/index.ts","./src/lib/api-keys/listdirectories.ts","./src/lib/api-keys/listfiles.ts","./src/lib/api-keys/validatetranscendauth.ts","./src/lib/cli/common-parameters.ts","./src/lib/cli/done-input-validation.ts","./src/lib/cli/legacy-commands.ts","./src/lib/cli/parsers.ts","./src/lib/code-scanning/constants.ts","./src/lib/code-scanning/findcodepackagesinfolder.ts","./src/lib/code-scanning/findfilestoscan.ts","./src/lib/code-scanning/index.ts","./src/lib/code-scanning/types.ts","./src/lib/code-scanning/integrations/cocoapods.ts","./src/lib/code-scanning/integrations/composerjson.ts","./src/lib/code-scanning/integrations/gemfile.ts","./src/lib/code-scanning/integrations/gradle.ts","./src/lib/code-scanning/integrations/index.ts","./src/lib/code-scanning/integrations/javascriptpackagejson.ts","./src/lib/code-scanning/integrations/pubspec.ts","./src/lib/code-scanning/integrations/pythonrequirementstxt.ts","./src/lib/code-scanning/integrations/swift.ts","./src/lib/consent-manager/buildxdisyncendpoint.ts","./src/lib/consent-manager/consentmanagerstobusinessentities.ts","./src/lib/consent-manager/createconsenttoken.ts","./src/lib/consent-manager/dataflowstodatasilos.ts","./src/lib/consent-manager/domaintohost.ts","./src/lib/consent-manager/fetchconsentpreferences.ts","./src/lib/consent-manager/index.ts","./src/lib/consent-manager/pullconsentmanagermetrics.ts","./src/lib/consent-manager/types.ts","./src/lib/consent-manager/updateconsentmanagerversiontolatest.ts","./src/lib/consent-manager/uploadconsents.ts","./src/lib/consent-manager/uploadcookiesfromcsv.ts","./src/lib/consent-manager/uploaddataflowsfromcsv.ts","./src/lib/cron/index.ts","./src/lib/cron/markcronidentifiercompleted.ts","./src/lib/cron/markrequestdatasiloidscompleted.ts","./src/lib/cron/pullchunkedcustomsilooutstandingidentifiers.ts","./src/lib/cron/pullcronpageofidentifiers.ts","./src/lib/cron/pullcustomsilooutstandingidentifiers.ts","./src/lib/cron/pushcronidentifiersfromcsv.ts","./src/lib/cron/writecsv.ts","./src/lib/data-inventory/index.ts","./src/lib/data-inventory/pullalldatapoints.ts","./src/lib/data-inventory/pullunstructuredsubdatapointrecommendations.ts","./src/lib/docgen/buildexamples.ts","./src/lib/docgen/getexampledate.ts","./src/lib/graphql/addmessagestopromptrun.ts","./src/lib/graphql/buildtranscendgraphqlclient.ts","./src/lib/graphql/createsombragotinstance.ts","./src/lib/graphql/createtranscendconsentgotinstance.ts","./src/lib/graphql/deployconsentmanager.ts","./src/lib/graphql/fetchactivesilodiscoplugin.ts","./src/lib/graphql/fetchallactionitemcollections.ts","./src/lib/graphql/fetchallactionitems.ts","./src/lib/graphql/fetchallactions.ts","./src/lib/graphql/fetchallagentfiles.ts","./src/lib/graphql/fetchallagentfunctions.ts","./src/lib/graphql/fetchallagents.ts","./src/lib/graphql/fetchallassessmenttemplates.ts","./src/lib/graphql/fetchallassessments.ts","./src/lib/graphql/fetchallattributekeys.ts","./src/lib/graphql/fetchallattributes.ts","./src/lib/graphql/fetchallbusinessentities.ts","./src/lib/graphql/fetchallcodepackages.ts","./src/lib/graphql/fetchallcookies.ts","./src/lib/graphql/fetchalldatacategories.ts","./src/lib/graphql/fetchalldataflows.ts","./src/lib/graphql/fetchallmessages.ts","./src/lib/graphql/fetchallpolicies.ts","./src/lib/graphql/fetchallpreferencetopics.ts","./src/lib/graphql/fetchallprivacycenters.ts","./src/lib/graphql/fetchallprocessingactivities.ts","./src/lib/graphql/fetchallprocessingpurposes.ts","./src/lib/graphql/fetchallpurposes.ts","./src/lib/graphql/fetchallpurposesandpreferences.ts","./src/lib/graphql/fetchallrepositories.ts","./src/lib/graphql/fetchallrequestenrichers.ts","./src/lib/graphql/fetchallrequestidentifiermetadata.ts","./src/lib/graphql/fetchallrequestidentifiers.ts","./src/lib/graphql/fetchallrequests.ts","./src/lib/graphql/fetchallsoftwaredevelopmentkits.ts","./src/lib/graphql/fetchallteams.ts","./src/lib/graphql/fetchallusers.ts","./src/lib/graphql/fetchallvendors.ts","./src/lib/graphql/fetchapikeys.ts","./src/lib/graphql/fetchcatalogs.ts","./src/lib/graphql/fetchconsentmanagerid.ts","./src/lib/graphql/fetchdatasubjects.ts","./src/lib/graphql/fetchidentifiers.ts","./src/lib/graphql/fetchlargelanguagemodels.ts","./src/lib/graphql/fetchprivacycenterid.ts","./src/lib/graphql/fetchpromptgroups.ts","./src/lib/graphql/fetchpromptpartials.ts","./src/lib/graphql/fetchpromptthreads.ts","./src/lib/graphql/fetchprompts.ts","./src/lib/graphql/fetchrequestdatasilo.ts","./src/lib/graphql/fetchrequestdatasiloactivecount.ts","./src/lib/graphql/fetchrequestfilesforrequest.ts","./src/lib/graphql/formatattributevalues.ts","./src/lib/graphql/formatregions.ts","./src/lib/graphql/index.ts","./src/lib/graphql/loginuser.ts","./src/lib/graphql/makegraphqlrequest.ts","./src/lib/graphql/manageapikeys.ts","./src/lib/graphql/parseassessmentdisplaylogic.ts","./src/lib/graphql/parseassessmentrisklogic.ts","./src/lib/graphql/pulltranscendconfiguration.ts","./src/lib/graphql/reportpromptrun.ts","./src/lib/graphql/retryrequestenricher.ts","./src/lib/graphql/setresourceattributes.ts","./src/lib/graphql/syncaction.ts","./src/lib/graphql/syncactionitemcollections.ts","./src/lib/graphql/syncactionitems.ts","./src/lib/graphql/syncagentfiles.ts","./src/lib/graphql/syncagentfunctions.ts","./src/lib/graphql/syncagents.ts","./src/lib/graphql/syncattribute.ts","./src/lib/graphql/syncbusinessentities.ts","./src/lib/graphql/synccodepackages.ts","./src/lib/graphql/syncconfigurationtotranscend.ts","./src/lib/graphql/syncconsentmanager.ts","./src/lib/graphql/synccookies.ts","./src/lib/graphql/syncdatacategories.ts","./src/lib/graphql/syncdataflows.ts","./src/lib/graphql/syncdatasilos.ts","./src/lib/graphql/syncdatasubject.ts","./src/lib/graphql/syncenrichers.ts","./src/lib/graphql/syncidentifier.ts","./src/lib/graphql/syncintlmessages.ts","./src/lib/graphql/syncpartitions.ts","./src/lib/graphql/syncpolicies.ts","./src/lib/graphql/syncprivacycenter.ts","./src/lib/graphql/syncprocessingactivities.ts","./src/lib/graphql/syncprocessingpurposes.ts","./src/lib/graphql/syncpromptgroups.ts","./src/lib/graphql/syncpromptpartials.ts","./src/lib/graphql/syncprompts.ts","./src/lib/graphql/syncrepositories.ts","./src/lib/graphql/syncsoftwaredevelopmentkits.ts","./src/lib/graphql/syncteams.ts","./src/lib/graphql/synctemplates.ts","./src/lib/graphql/syncvendors.ts","./src/lib/graphql/uploadsilodiscoveryresults.ts","./src/lib/graphql/gqls/requestdatasilo.ts","./src/lib/graphql/gqls/requestenricher.ts","./src/lib/graphql/gqls/requestidentifier.ts","./src/lib/graphql/gqls/action.ts","./src/lib/graphql/gqls/actionitem.ts","./src/lib/graphql/gqls/actionitemcollection.ts","./src/lib/graphql/gqls/agent.ts","./src/lib/graphql/gqls/agentfile.ts","./src/lib/graphql/gqls/agentfunction.ts","./src/lib/graphql/gqls/apikey.ts","./src/lib/graphql/gqls/assessment.ts","./src/lib/graphql/gqls/assessmenttemplate.ts","./src/lib/graphql/gqls/attribute.ts","./src/lib/graphql/gqls/attributekey.ts","./src/lib/graphql/gqls/auth.ts","./src/lib/graphql/gqls/businessentity.ts","./src/lib/graphql/gqls/catalog.ts","./src/lib/graphql/gqls/codepackage.ts","./src/lib/graphql/gqls/consentmanager.ts","./src/lib/graphql/gqls/consentmanagermetrics.ts","./src/lib/graphql/gqls/datacategory.ts","./src/lib/graphql/gqls/datapoint.ts","./src/lib/graphql/gqls/datasilo.ts","./src/lib/graphql/gqls/datasubject.ts","./src/lib/graphql/gqls/enricher.ts","./src/lib/graphql/gqls/entry.ts","./src/lib/graphql/gqls/identifier.ts","./src/lib/graphql/gqls/index.ts","./src/lib/graphql/gqls/largelanguagemodel.ts","./src/lib/graphql/gqls/message.ts","./src/lib/graphql/gqls/organization.ts","./src/lib/graphql/gqls/policy.ts","./src/lib/graphql/gqls/preferencetopic.ts","./src/lib/graphql/gqls/privacycenter.ts","./src/lib/graphql/gqls/processingactivity.ts","./src/lib/graphql/gqls/processingpurpose.ts","./src/lib/graphql/gqls/prompt.ts","./src/lib/graphql/gqls/promptrun.ts","./src/lib/graphql/gqls/promptthread.ts","./src/lib/graphql/gqls/purpose.ts","./src/lib/graphql/gqls/repository.ts","./src/lib/graphql/gqls/request.ts","./src/lib/graphql/gqls/requestfile.ts","./src/lib/graphql/gqls/silodiscovery.ts","./src/lib/graphql/gqls/softwaredevelopmentkit.ts","./src/lib/graphql/gqls/sombraversion.ts","./src/lib/graphql/gqls/team.ts","./src/lib/graphql/gqls/template.ts","./src/lib/graphql/gqls/user.ts","./src/lib/graphql/gqls/vendor.ts","./src/lib/helpers/buildaiintegrationtype.ts","./src/lib/helpers/buildenabledroutetype.ts","./src/lib/helpers/index.ts","./src/lib/helpers/inquirer.ts","./src/lib/helpers/parsevariablesfromstring.ts","./src/lib/manual-enrichment/enrichprivacyrequest.ts","./src/lib/manual-enrichment/index.ts","./src/lib/manual-enrichment/pullmanualenrichmentidentifierstocsv.ts","./src/lib/manual-enrichment/pushmanualenrichmentidentifiersfromcsv.ts","./src/lib/onetrust/createonetrustgotinstance.ts","./src/lib/onetrust/index.ts","./src/lib/onetrust/endpoints/getlistofonetrustassessments.ts","./src/lib/onetrust/endpoints/getonetrustassessment.ts","./src/lib/onetrust/endpoints/getonetrustrisk.ts","./src/lib/onetrust/endpoints/getonetrustuser.ts","./src/lib/onetrust/endpoints/index.ts","./src/lib/onetrust/helpers/converttoemptystrings.ts","./src/lib/onetrust/helpers/enrichonetrustassessment.ts","./src/lib/onetrust/helpers/index.ts","./src/lib/onetrust/helpers/onetrustassessmenttojson.ts","./src/lib/onetrust/helpers/parseclisyncotarguments.ts","./src/lib/onetrust/helpers/synconetrustassessmenttodisk.ts","./src/lib/onetrust/helpers/synconetrustassessmenttotranscend.ts","./src/lib/onetrust/helpers/synconetrustassessmentsfromfile.ts","./src/lib/onetrust/helpers/synconetrustassessmentsfromonetrust.ts","./src/lib/onetrust/helpers/tests/converttoemptystrings.test.ts","./src/lib/pooling/assignworktoworker.ts","./src/lib/pooling/attachworkerhandlers.ts","./src/lib/pooling/computepoolsize.ts","./src/lib/pooling/ensurelogfile.ts","./src/lib/pooling/index.ts","./src/lib/pooling/installinteractiveswitcher.ts","./src/lib/pooling/logrotation.ts","./src/lib/pooling/openterminal.ts","./src/lib/pooling/renderdashboard.ts","./src/lib/pooling/spawnworkerprocess.ts","./src/lib/preference-management/checkifpendingpreferenceupdatesarenoop.ts","./src/lib/preference-management/checkifpendingpreferenceupdatescauseconflict.ts","./src/lib/preference-management/codecs.ts","./src/lib/preference-management/getpreferenceupdatesfromrow.ts","./src/lib/preference-management/getpreferencesforidentifiers.ts","./src/lib/preference-management/index.ts","./src/lib/preference-management/parsepreferenceandpurposevaluesfromcsv.ts","./src/lib/preference-management/parsepreferencefileformatfromcsv.ts","./src/lib/preference-management/parsepreferenceidentifiersfromcsv.ts","./src/lib/preference-management/parsepreferencemanagementcsv.ts","./src/lib/preference-management/uploadpreferencemanagementpreferencesinteractive.ts","./src/lib/preference-management/tests/checkifpendingpreferenceupdatesarenoop.test.ts","./src/lib/preference-management/tests/checkifpendingpreferenceupdatescauseconflict.test.ts","./src/lib/preference-management/tests/getpreferenceupdatesfromrow.test.ts","./src/lib/requests/approveprivacyrequests.ts","./src/lib/requests/bulkrestartrequests.ts","./src/lib/requests/bulkretryenrichers.ts","./src/lib/requests/cancelprivacyrequests.ts","./src/lib/requests/constants.ts","./src/lib/requests/downloadprivacyrequestfiles.ts","./src/lib/requests/extractclienterror.ts","./src/lib/requests/filterrows.ts","./src/lib/requests/fuzzymatchcolumns.ts","./src/lib/requests/getfilemetadataforprivacyrequests.ts","./src/lib/requests/getuniquevaluesforcolumn.ts","./src/lib/requests/index.ts","./src/lib/requests/mapcolumnstoattributes.ts","./src/lib/requests/mapcolumnstoidentifiers.ts","./src/lib/requests/mapcsvcolumnstoapi.ts","./src/lib/requests/mapcsvrowstorequestinputs.ts","./src/lib/requests/mapenumvalues.ts","./src/lib/requests/maprequestenumvalues.ts","./src/lib/requests/marksilentprivacyrequests.ts","./src/lib/requests/notifyprivacyrequestsadditionaltime.ts","./src/lib/requests/parseattributesfromstring.ts","./src/lib/requests/pullprivacyrequests.ts","./src/lib/requests/readcsv.ts","./src/lib/requests/removeunverifiedrequestidentifiers.ts","./src/lib/requests/restartprivacyrequest.ts","./src/lib/requests/retryrequestdatasilos.ts","./src/lib/requests/skippreflightjobs.ts","./src/lib/requests/skiprequestdatasilos.ts","./src/lib/requests/splitcsvtolist.ts","./src/lib/requests/streamprivacyrequestfiles.ts","./src/lib/requests/submitprivacyrequest.ts","./src/lib/requests/uploadprivacyrequestsfromcsv.ts","./src/lib/requests/tests/fuzzymatchcolumns.test.ts","./src/lib/requests/tests/getuniquevaluesforcolumn.test.ts","./src/lib/requests/tests/mapcsvrowstorequestinputs.test.ts","./src/lib/requests/tests/parseattributesfromstring.test.ts","./src/lib/requests/tests/readcsv.test.ts","./src/lib/requests/tests/splitcsvtolist.test.ts","./src/lib/tests/transcendpromptmanager.test.ts","./src/lib/tests/codebase.test.ts","./src/lib/tests/examples.test.ts","./src/lib/tests/filternullvaluesfromobject.test.ts","./src/lib/tests/findcodepackagesinfolder.test.ts","./src/lib/tests/getgitfilesthatchanged.test.ts","./src/lib/tests/legacycommands.test.ts","./src/lib/tests/mergetranscendinputs.test.ts","./src/lib/tests/readtranscendyaml.test.ts","./src/lib/tests/removelinks.test.ts","./src/lib/tests/helpers/capturelogs.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file From ef0d0f93d7f44c7b69947c0588565527c5508729 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 15:13:07 -0700 Subject: [PATCH 24/72] adds docs to codecs --- .../consent/upload-preferences/impl.ts | 4 +- src/lib/preference-management/codecs.ts | 62 +++++++++++++++---- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 7e052c32..d9a29d55 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -186,9 +186,7 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { const raw = readFileSync(receiptPath, 'utf8'); const json = JSON.parse(raw) as any; - const skippedCount = Object.values( - json?.skippedUpdated ?? json?.skippedUpdates ?? {}, - ).length; + const skippedCount = Object.values(json?.skippedUpdates ?? {}).length; if (!dryRun) { const success = Object.values(json?.successfulUpdates ?? {}).length; diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index 06ee7298..c9e26f58 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -75,32 +75,57 @@ export const RequestUploadReceipts = t.type({ /** Last time the file was last parsed at */ lastFetchedAt: t.string, /** - * Mapping of userId to the rows in the file that need to be uploaded - * These uploads are overwriting non-existent preferences and are safe + * Mapping of primaryKey to the rows in the file that need to be uploaded + * + * These uploads are overwriting non-existent preferences and are not in + * conflict with existing consent preferences. + * + * Note: If --skipExistingRecordCheck=true is set, there will not be on check + * for existing record conflicts in order to speed up the upload. + * So this will say the updates were safe when in fact we don't know. + * We just let the default consent resolution logic handle it. */ pendingSafeUpdates: t.record( t.string, + // This can either be true to indicate the record is safe + // or it can be an object showing the object + // We only return a fixed number of results to avoid + // making the JSON file too large t.union([t.boolean, t.record(t.string, t.string)]), ), /** - * Mapping of userId to the rows in the file that need to be uploaded - * these records have conflicts with existing consent preferences + * Mapping of primaryKey to the rows in the file that need to be uploaded + * these records have conflicts with existing consent preferences. + * Normally the default consent resolution logic will handle these + * conflicts, but these are useful situations in which to investigate + * and ensure consent resolution is working as expected. + * + * Note: If --skipExistingRecordCheck=true is set, there will not be on check + * for existing record conflicts in order to speed up the upload. and this will + * be under-counted. + * + * Set to `--skipExistingRecordCheck=false --dryRun=true` to get the list of conflicts. */ pendingConflictUpdates: t.record( t.string, + // We always return the conflicts for investigation t.type({ record: PreferenceQueryResponseItem, row: t.record(t.string, t.string), }), ), /** - * Mapping of userId to the rows in the file that can be skipped because - * their preferences are already in the store + * Mapping of primaryKey to the rows in the file that can be skipped because + * their preferences are already in the store. These records may be skipped + * as they could be a duplicate row in the CSV file. + * + * If `--skipExistingRecordCheck=false` - then no-ops will be filtered out. */ skippedUpdates: t.record(t.string, t.record(t.string, t.string)), /** - * The set of successful uploads to Transcend - * Mapping from userId to the upload metadata + * The set of failing updates + * Mapping from primaryKey to the request payload, time upload happened + * and error message. */ failingUpdates: t.record( t.string, @@ -114,18 +139,33 @@ export const RequestUploadReceipts = t.type({ }), ), /** - * The set of pending uploads to Transcend - * Mapping from userId to the upload metadata + * The set of uploads that were pending at the time that the cache file + * was last written to. When using `--dryRun=true` this list will be full. + * + * When running `--dryRun=false` this set will shrink as updates are processed. */ pendingUpdates: t.record( t.string, + // This can either be true to indicate the record is pending + // or it can be an object showing the object + // We only return a fixed number of results to avoid + // making the JSON file too large t.union([t.boolean, PreferenceUpdateItem]), ), /** - * The successful updates + * The updates that were successfully processed + * Mapping from primaryKey to the request response. + * + * This will be empty if `--dryRun=true` is set. + * If `--dryRun=false` is set, this will contain + * the updates that were successfully processed. */ successfulUpdates: t.record( t.string, + // This can either be true to indicate the record is successful + // or it can be an object showing the object + // We only return a fixed number of results to avoid + // making the JSON file too large t.union([t.boolean, PreferenceUpdateItem]), ), }); From 41ceb8c9de05975ca96b181a219e08b889298d99 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 15:15:12 -0700 Subject: [PATCH 25/72] gitignore and bb --- .gitignore | 1 + .../preference-management/parsePreferenceIdentifiersFromCsv.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bb8890aa..b8616ce8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ transcend.yml notes.txt +tsconfig.tsbuildinfo transcend-privacy-requests-cache.json cron-identifiers.csv manual-enrichment-identifiers.csv diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 91edae20..87db1c67 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -5,7 +5,7 @@ import inquirer from 'inquirer'; import type { FileFormatState } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; -import { mapSeries } from '../bluebird-replace'; +import { mapSeries } from 'bluebird'; import type { Identifier } from '../graphql'; import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; import type { PersistedState } from '@transcend-io/persisted-state'; From 84342d668de02a7f8c635bcdc848b553d534df08 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 18:08:47 -0700 Subject: [PATCH 26/72] Refactors --- .vscode/settings.json | 1 + README.md | 27 +- .../consent/upload-preferences/command.ts | 49 +- .../consent/upload-preferences/impl.ts | 22 +- .../upload-preferences/receiptsState.ts | 96 +++ .../consent/upload-preferences/runChild.ts | 73 ++- .../consent/upload-preferences/schemaState.ts | 46 ++ .../transform/buildPendingUpdates.ts | 125 ++++ .../upload/batchUploader.ts | 126 ++++ .../upload/buildInteractiveUploadPlan.ts | 160 +++++ .../interactivePreferenceUploaderFromPlan.ts | 303 +++++++++ .../upload/loadReferenceData.ts | 61 ++ src/constants.ts | 8 + src/lib/graphql/makeGraphQLRequest.ts | 13 +- src/lib/helpers/extractErrorMessage.ts | 31 + src/lib/helpers/getErrorStatus.ts | 12 + src/lib/helpers/index.ts | 6 + src/lib/helpers/limitRecords.ts | 17 + src/lib/helpers/retrySamePromise.ts | 69 ++ src/lib/helpers/sleepPromise.ts | 11 + src/lib/helpers/splitInHalf.ts | 10 + src/lib/pooling/attachWorkerHandlers.ts | 3 +- src/lib/pooling/renderDashboard.ts | 12 +- src/lib/preference-management/codecs.ts | 188 ++++-- src/lib/preference-management/index.ts | 1 - .../parsePreferenceManagementCsv.ts | 17 +- ...ferenceManagementPreferencesInteractive.ts | 598 ------------------ 27 files changed, 1369 insertions(+), 716 deletions(-) create mode 100644 src/commands/consent/upload-preferences/receiptsState.ts create mode 100644 src/commands/consent/upload-preferences/schemaState.ts create mode 100644 src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts create mode 100644 src/commands/consent/upload-preferences/upload/batchUploader.ts create mode 100644 src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts create mode 100644 src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts create mode 100644 src/commands/consent/upload-preferences/upload/loadReferenceData.ts create mode 100644 src/lib/helpers/extractErrorMessage.ts create mode 100644 src/lib/helpers/getErrorStatus.ts create mode 100644 src/lib/helpers/limitRecords.ts create mode 100644 src/lib/helpers/retrySamePromise.ts create mode 100644 src/lib/helpers/sleepPromise.ts create mode 100644 src/lib/helpers/splitInHalf.ts delete mode 100644 src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 91cb9a6b..bf64b790 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -61,6 +61,7 @@ "preact", "pubspec", "Requirize", + "retryable", "sombra", "subdatapoint", "subdatapoints", diff --git a/README.md b/README.md index afd26fd7..fc183165 100644 --- a/README.md +++ b/README.md @@ -2079,7 +2079,7 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] [--file value] [--directory value] [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] [--file value] [--directory value] [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] transcend consent upload-preferences --help Upload preference management data to your Preference Store. @@ -2100,20 +2100,25 @@ FLAGS --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" --partition The partition key to download consent preferences to [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] [--file] Path to the CSV file to load preferences from [--directory] Path to the directory of CSV files to load preferences from - [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] - [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] + [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] + [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] [--receiptFileDir] Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided. [--schemaFilePath] The path to where the schema for the file should be saved. If file is provided, it will default to ./-preference-upload-schema.json If directory is provided, it will default to /../preference-upload-schema.json - [--skipWorkflowTriggers] Whether to skip workflow triggers when uploading to preference store [default = false] - [--forceTriggerWorkflows] Whether to force trigger workflows for existing consent records [default = false] - [--skipConflictUpdates] Whether to skip uploading of any records where the preference store and file have a hard conflict [default = false] - [--isSilent/--noIsSilent] Whether to skip sending emails in workflows [default = true] - [--attributes] Attributes to add to any DSR request if created. Comma-separated list of key:value pairs. [default = Tags:transcend-cli,Source:transcend-cli] - [--receiptFilepath] Store resulting, continuing where left off [default = ./preference-management-upload-receipts.json] - [--concurrency] The concurrency to use when uploading in parallel - otherwise uses the number of CPU cores available + [--skipWorkflowTriggers] Whether to skip workflow triggers when uploading to preference store [default = false] + [--forceTriggerWorkflows] Whether to force trigger workflows for existing consent records [default = false] + [--skipConflictUpdates] Whether to skip uploading of any records where the preference store and file have a hard conflict [default = false] + [--isSilent/--noIsSilent] Whether to skip sending emails in workflows [default = true] + [--attributes] Attributes to add to any DSR request if created. Comma-separated list of key:value pairs. [default = Tags:transcend-cli,Source:transcend-cli] + [--receiptFilepath] Store resulting, continuing where left off [default = ./preference-management-upload-receipts.json] + [--concurrency] The number of concurrent processes to use to upload the files. When this is not set, it defaults to the number of CPU cores available on the machine. e.g. if there are 5 concurrent processes for 15 files, each parallel job would get 3 files to process. + [--uploadConcurrency] When uploading preferences to v1/preferences - this is the number of concurrent requests made at any given time by a single process.This is NOT the batch size—it's how many batch *tasks* run in parallel. The number of total concurrent requests is maxed out at concurrency * uploadConcurrency. [default = 75] + [--maxChunkSize] When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency. [default = 50] + [--rateLimitRetryDelay] When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. This is only used if the request is rate limited by the Transcend API. If the request fails for any other reason, it will not be retried. [default = 3000] + [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] + [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 50] --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 532c68f6..dce30106 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -106,9 +106,56 @@ export const uploadPreferencesCommand = buildCommand({ kind: 'parsed', parse: numberParser, brief: - 'The concurrency to use when uploading in parallel - otherwise uses the number of CPU cores available', + 'The number of concurrent processes to use to upload the files. When this is not set, it defaults ' + + 'to the number of CPU cores available on the machine. ' + + 'e.g. if there are 5 concurrent processes for 15 files, each parallel job would get 3 files to process. ', optional: true, }, + uploadConcurrency: { + kind: 'parsed', + parse: numberParser, + brief: + 'When uploading preferences to v1/preferences - this is the number of concurrent requests made at any given time by a single process.' + + "This is NOT the batch size—it's how many batch *tasks* run in parallel. " + + 'The number of total concurrent requests is maxed out at concurrency * uploadConcurrency.', + default: '75', // FIXME 25 + }, + maxChunkSize: { + kind: 'parsed', + parse: numberParser, + brief: + 'When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.' + + 'The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency.', + default: '50', + }, + rateLimitRetryDelay: { + kind: 'parsed', + parse: numberParser, + brief: + 'When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. ' + + 'This is only used if the request is rate limited by the Transcend API. ' + + 'If the request fails for any other reason, it will not be retried. ', + default: '3000', + }, + uploadLogInterval: { + kind: 'parsed', + parse: numberParser, + brief: + 'When uploading preferences to v1/preferences - this is the number of records after which to log progress. ' + + 'Output will be logged to console and also to the receipt file. ' + + 'Setting this value lower will allow for you to more easily pick up where you left off. ' + + 'Setting this value higher can avoid excessive i/o operations slowing down the upload. ' + + 'Default is a good optimization for most cases.', + default: '1000', + }, + maxRecordsToReceipt: { + kind: 'parsed', + parse: numberParser, + brief: + 'When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. ' + + 'This is to avoid the receipt file getting too large for JSON.parse/stringify.', + default: '50', + }, allowedIdentifierNames: { kind: 'parsed', parse: (value: string) => value.split(',').map((s) => s.trim()), diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index d9a29d55..64111668 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -27,10 +27,10 @@ import { getWorkerLogPaths, renderDashboard, spawnWorkerProcess, - type WorkerState, } from '../../../lib/pooling'; import { installInteractiveSwitcher } from '../../../lib/pooling/installInteractiveSwitcher'; import { resetWorkerLogs } from '../../../lib/pooling/logRotation'; +import type { WorkerState } from '../../../lib/pooling/assignWorkToWorker'; /** CLI flags */ export interface UploadPreferencesCommandFlags { @@ -51,6 +51,11 @@ export interface UploadPreferencesCommandFlags { attributes: string; receiptFilepath: string; concurrency?: number; + uploadConcurrency: number; + maxChunkSize: number; + rateLimitRetryDelay: number; + uploadLogInterval: number; + maxRecordsToReceipt: number; allowedIdentifierNames: string[]; identifierColumns: string[]; columnsToIgnore?: string[]; @@ -65,6 +70,11 @@ export type TaskCommonOpts = Pick< | 'directory' | 'transcendUrl' | 'skipConflictUpdates' + | 'uploadConcurrency' + | 'uploadLogInterval' + | 'maxChunkSize' + | 'rateLimitRetryDelay' + | 'maxRecordsToReceipt' | 'skipWorkflowTriggers' | 'skipExistingRecordCheck' | 'isSilent' @@ -289,7 +299,12 @@ export async function uploadPreferences( attributes, concurrency, allowedIdentifierNames, + maxChunkSize, + maxRecordsToReceipt, + rateLimitRetryDelay, identifierColumns, + uploadConcurrency, + uploadLogInterval, columnsToIgnore = [], }: UploadPreferencesCommandFlags, ): Promise { @@ -332,6 +347,11 @@ export async function uploadPreferences( forceTriggerWorkflows, allowedIdentifierNames, identifierColumns, + uploadConcurrency, + maxChunkSize, + rateLimitRetryDelay, + maxRecordsToReceipt, + uploadLogInterval, columnsToIgnore, }; diff --git a/src/commands/consent/upload-preferences/receiptsState.ts b/src/commands/consent/upload-preferences/receiptsState.ts new file mode 100644 index 00000000..46f56252 --- /dev/null +++ b/src/commands/consent/upload-preferences/receiptsState.ts @@ -0,0 +1,96 @@ +import { PersistedState } from '@transcend-io/persisted-state'; +import { + RequestUploadReceipts, + type FailingPreferenceUpdates, + type PendingSafePreferenceUpdates, + type PendingWithConflictPreferenceUpdates, + type PreferenceUpdateMap, +} from '../../../lib/preference-management'; + +export type PreferenceReceiptsInterface = { + /** Path to file */ + receiptsFilepath: string; + /** + * Get the successfully updated records + */ + getSuccessful(): PreferenceUpdateMap; + /** + * Get the records pending upload + */ + getPending(): PreferenceUpdateMap; + /** + * Get the failing to upload records + */ + getFailing(): FailingPreferenceUpdates; + /** + * Set the new map of successful records + */ + setSuccessful(next: PreferenceUpdateMap): Promise; + /** + * Set the new map of pending records + */ + setPending(next: PreferenceUpdateMap): Promise; + /** + * Set the new map of safe to upload records + */ + setPendingSafe(next: PendingSafePreferenceUpdates): Promise; + /** + * Set the new map of conflict upload records + */ + setPendingConflict(next: PendingWithConflictPreferenceUpdates): Promise; + /** + * Set the new map of failing records + */ + setFailing(next: FailingPreferenceUpdates): Promise; + /** + * Reset the pending records + */ + resetPending(): Promise; +}; + +/** + * Build a receipts state adapter for the given file path. + * + * @param filepath - Where to persist/read upload receipts + * @returns Receipt state port with strongly-named methods + */ +export function makeReceiptsState( + filepath: string, +): PreferenceReceiptsInterface { + const s = new PersistedState(filepath, RequestUploadReceipts, { + failingUpdates: {}, + pendingConflictUpdates: {}, + skippedUpdates: {}, + pendingSafeUpdates: {}, + successfulUpdates: {}, + pendingUpdates: {}, + lastFetchedAt: new Date().toISOString(), + }); + + return { + receiptsFilepath: filepath, + getSuccessful: () => s.getValue('successfulUpdates'), + getPending: () => s.getValue('pendingUpdates'), + getFailing: () => s.getValue('failingUpdates'), + async setSuccessful(v: PreferenceUpdateMap) { + await s.setValue(v, 'successfulUpdates'); + }, + async setPending(v: PreferenceUpdateMap) { + await s.setValue(v, 'pendingUpdates'); + }, + async setPendingSafe(v: PendingSafePreferenceUpdates) { + await s.setValue(v, 'pendingSafeUpdates'); + }, + async setPendingConflict(v: PendingWithConflictPreferenceUpdates) { + await s.setValue(v, 'pendingConflictUpdates'); + }, + async setFailing(v: FailingPreferenceUpdates) { + await s.setValue(v, 'failingUpdates'); + }, + async resetPending() { + await s.setValue({}, 'pendingUpdates'); + await s.setValue({}, 'pendingSafeUpdates'); + await s.setValue({}, 'pendingConflictUpdates'); + }, + }; +} diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 082ef0d9..999f992d 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -1,10 +1,18 @@ // runChild.ts import { mkdirSync, createWriteStream } from 'node:fs'; import { join, dirname } from 'node:path'; -import { uploadPreferenceManagementPreferencesInteractive } from '../../../lib/preference-management'; import { getFilePrefix } from './computeFiles'; import { splitCsvToList } from '../../../lib/requests'; import type { TaskCommonOpts } from './impl'; +import { interactivePreferenceUploaderFromPlan } from './upload/interactivePreferenceUploaderFromPlan'; +import { makeSchemaState } from './schemaState'; +import { makeReceiptsState } from './receiptsState'; +import { + buildTranscendGraphQLClient, + createSombraGotInstance, +} from '../../../lib/graphql'; +import { logger } from '../../../logger'; +import { buildInteractiveUploadPreferencePlan } from './upload/buildInteractiveUploadPlan'; export async function runChild(): Promise { const workerId = Number(process.env.WORKER_ID || '0'); @@ -21,7 +29,7 @@ export async function runChild(): Promise { logStream.write(line); }; - console.log(`[w${workerId}] ready pid=${process.pid}`); + logger.info(`[w${workerId}] ready pid=${process.pid}`); process.send?.({ type: 'ready' }); process.on('message', async (msg: any) => { @@ -38,30 +46,55 @@ export async function runChild(): Promise { ); try { mkdirSync(dirname(receiptFilepath), { recursive: true }); - console.log(`[w${workerId}] START ${filePath}`); + logger.info(`[w${workerId}] START ${filePath}`); log(`START ${filePath}`); - await uploadPreferenceManagementPreferencesInteractive({ - receiptFilepath, - schemaFilePath: options.schemaFile, - auth: options.auth, - sombraAuth: options.sombraAuth, + // Construct common options + const receipts = makeReceiptsState(receiptFilepath); + const schema = makeSchemaState(options.schemaFile); + const client = buildTranscendGraphQLClient( + options.transcendUrl, + options.auth, + ); + const sombra = await createSombraGotInstance( + options.transcendUrl, + options.auth, + options.sombraAuth, + ); + + // Step 1: Build the plan (validation-only) + const plan = await buildInteractiveUploadPreferencePlan({ + sombra, + client, file: filePath, partition: options.partition, - transcendUrl: options.transcendUrl, - skipConflictUpdates: options.skipConflictUpdates, - skipWorkflowTriggers: options.skipWorkflowTriggers, + receipts, + schema, skipExistingRecordCheck: options.skipExistingRecordCheck, - isSilent: options.isSilent, - dryRun: options.dryRun, - attributes: splitCsvToList(options.attributes), forceTriggerWorkflows: options.forceTriggerWorkflows, allowedIdentifierNames: options.allowedIdentifierNames, + maxRecordsToReceipt: options.maxRecordsToReceipt, identifierColumns: options.identifierColumns, - columnsToIgnore: options.columnsToIgnore || [], + columnsToIgnore: options.columnsToIgnore, + attributes: splitCsvToList(options.attributes), + }); + + // Step 2: Execute the upload (no parsing/validation here) + await interactivePreferenceUploaderFromPlan(plan, { + receipts, + sombra, + dryRun: options.dryRun, + isSilent: options.isSilent, + skipWorkflowTriggers: options.skipWorkflowTriggers, + skipConflictUpdates: options.skipConflictUpdates, + forceTriggerWorkflows: options.forceTriggerWorkflows, + uploadLogInterval: options.uploadLogInterval, + maxChunkSize: options.maxChunkSize, + uploadConcurrency: options.uploadConcurrency, + maxRecordsToReceipt: options.maxRecordsToReceipt, }); - console.log(`[w${workerId}] DONE ${filePath}`); + logger.info(`[w${workerId}] DONE ${filePath}`); log(`SUCCESS ${filePath}`); process.send?.({ @@ -70,7 +103,7 @@ export async function runChild(): Promise { }); } catch (err: any) { const e = err?.stack || err?.message || String(err); - console.error( + logger.error( `[w${workerId}] ERROR ${filePath}: ${err?.message || err}\n\n${e}`, ); log(`FAIL ${filePath}\n${e}`); @@ -81,19 +114,19 @@ export async function runChild(): Promise { process.exit(1); } } else if (msg.type === 'shutdown') { - console.log(`[w${workerId}] shutdown`); + logger.info(`[w${workerId}] shutdown`); log('Shutting down.'); logStream.end(() => process.exit(0)); } }); process.on('uncaughtException', (err) => { - console.error(`[w${workerId}] uncaughtException: ${err?.stack || err}`); + logger.error(`[w${workerId}] uncaughtException: ${err?.stack || err}`); log(`uncaughtException\n${err?.stack || err}`); logStream.end(() => process.exit(1)); }); process.on('unhandledRejection', (reason) => { - console.error(`[w${workerId}] unhandledRejection: ${String(reason)}`); + logger.error(`[w${workerId}] unhandledRejection: ${String(reason)}`); log(`unhandledRejection\n${String(reason)}`); logStream.end(() => process.exit(1)); }); diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts new file mode 100644 index 00000000..2724f63f --- /dev/null +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -0,0 +1,46 @@ +/** + * Module: state/schemaState + * + * Thin wrapper over PersistedState(FileFormatState) for schema/config + * discovered during CSV parsing. + */ +import { PersistedState } from '@transcend-io/persisted-state'; +import { + FileFormatState, + type ColumnIdentifierMap, + type ColumnPurposeMap, +} from '../../../lib/preference-management'; + +export interface PreferenceSchemaInterface { + /** Name of the column used as timestamp, if any */ + getTimestampColumn(): string | undefined; + /** CSV column name -> Purpose/Preference mapping */ + getColumnToPurposeName(): ColumnPurposeMap; + /** CSV column name -> Identifier mapping */ + getColumnToIdentifier(): ColumnIdentifierMap; + /** The persisted cache */ // FIXME remove this + state: PersistedState; +} + +/** + * Build a schema state adapter holding CSV→purpose/identifier mappings. + * + * @param filepath - Path to the schema cache file + * @returns Schema state port with strongly-named methods + */ +export function makeSchemaState(filepath: string): PreferenceSchemaInterface { + const s = new PersistedState(filepath, FileFormatState, { + columnToPurposeName: {}, + lastFetchedAt: new Date().toISOString(), + columnToIdentifier: {}, + }); + + return { + state: s, + getTimestampColumn: (): string | undefined => s.getValue('timestampColumn'), + getColumnToPurposeName: (): ColumnPurposeMap => + s.getValue('columnToPurposeName'), + getColumnToIdentifier: (): ColumnIdentifierMap => + s.getValue('columnToIdentifier'), + }; +} diff --git a/src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts b/src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts new file mode 100644 index 00000000..2b29d12b --- /dev/null +++ b/src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts @@ -0,0 +1,125 @@ +/** + * Module: transform/buildPendingUpdates + * + * Pure transformation from parsed CSV rows + schema mappings into + * PreferenceUpdateItem payloads, ready for upload. + */ +import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; +import type { PreferenceTopic, Purpose } from '../../../../lib/graphql'; +import { + getPreferenceIdentifiersFromRow, + getPreferenceUpdatesFromRow, + NONE_PREFERENCE_MAP, + type ColumnIdentifierMap, + type ColumnPurposeMap, + type PendingSafePreferenceUpdates, + type PendingWithConflictPreferenceUpdates, +} from '../../../../lib/preference-management'; +import type { FormattedAttribute } from '../../../../lib/graphql/formatAttributeValues'; + +export interface BuildPendingParams { + /** Safe updates keyed by user/primaryKey */ + safe: PendingSafePreferenceUpdates; + /** Conflict updates keyed by user/primaryKey (value.row contains row data) */ + conflicts: PendingWithConflictPreferenceUpdates; + /** Only upload safe updates (ignore conflicts entirely) */ + skipConflictUpdates: boolean; + /** Name of the column to use as the preference timestamp (if available) */ + timestampColumn?: string; + /** CSV column -> purpose/preference mapping */ + columnToPurposeName: ColumnPurposeMap; + /** CSV column -> identifier mapping */ + columnToIdentifier: ColumnIdentifierMap; + /** Full set of preference topics for resolving row → preference values */ + preferenceTopics: PreferenceTopic[]; + /** Full set of purposes for resolving slugs/trackingTypes */ + purposes: Purpose[]; + /** Partition to attribute to every record */ + partition: string; + /** Static attributes injected into workflow settings */ + workflowAttrs: FormattedAttribute[]; + /** If true, downstream should avoid user-visible notifications */ + isSilent: boolean; + /** If true, skip triggering workflows downstream */ + skipWorkflowTriggers: boolean; +} + +/** + * Convert parsed CSV rows into a map of PreferenceUpdateItem payloads. + * + * This function is *pure* (no IO, logging or state writes) and therefore easy to test. + * + * @param params - Transformation inputs + * @returns Map of primaryKey -> PreferenceUpdateItem + */ +export function buildPendingUpdates( + params: BuildPendingParams, +): Record { + const { + safe, + conflicts, + skipConflictUpdates, + timestampColumn, + columnToPurposeName, + columnToIdentifier, + preferenceTopics, + purposes, + partition, + workflowAttrs, + isSilent, + skipWorkflowTriggers, + } = params; + + // If conflicts are to be included, normalize the shape to match `safe` rows. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const merged: Record = skipConflictUpdates + ? { ...safe } + : { + ...safe, + ...Object.fromEntries( + Object.entries(conflicts).map(([id, v]) => [id, v.row]), + ), + }; + + const purposeSlugs = purposes.map((x) => x.trackingType); + const out: Record = {}; + + for (const [userId, row] of Object.entries(merged)) { + // Determine timestamp used for the store + const ts = + timestampColumn === NONE_PREFERENCE_MAP || !timestampColumn + ? new Date() + : new Date(row[timestampColumn]); + + // Resolve purposes/preferences from columns using schema mappings + topics + const updates = getPreferenceUpdatesFromRow({ + row, + columnToPurposeName, + preferenceTopics, + purposeSlugs, + }); + + // Resolve identifiers per row (email, phone, userId, etc.) + const identifiers = getPreferenceIdentifiersFromRow({ + row, + columnToIdentifier, + }); + + out[userId] = { + identifiers, + partition, + timestamp: ts.toISOString(), + purposes: Object.entries(updates).map(([purpose, value]) => ({ + ...value, + purpose, + workflowSettings: { + attributes: workflowAttrs, + isSilent, + skipWorkflowTrigger: skipWorkflowTriggers, + }, + })), + }; + } + + return out; +} diff --git a/src/commands/consent/upload-preferences/upload/batchUploader.ts b/src/commands/consent/upload-preferences/upload/batchUploader.ts new file mode 100644 index 00000000..50c22a05 --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/batchUploader.ts @@ -0,0 +1,126 @@ +import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; +import colors from 'colors'; +import { + extractErrorMessage, + getErrorStatus, + retrySamePromise, + splitInHalf, + type RetryPolicy, +} from '../../../../lib/helpers'; +import { logger } from '../../../../logger'; + +type Entry = [string, PreferenceUpdateItem]; + +export interface BatchUploadPreferenceOptions { + /** When true - don't trigger workflow runs */ + skipWorkflowTriggers: boolean; + /** Always trigger a workflow run regardless of whether a purpose changed */ + forceTriggerWorkflows: boolean; +} + +export interface BatchUploaderDeps { + /** Network transport used for PUT uploads */ + putBatch: ( + /** The set of updates to put */ + updates: PreferenceUpdateItem[], + /** The global options for each update */ + opts: BatchUploadPreferenceOptions, + ) => Promise; + /** Retry policy for retryable statuses */ + retryPolicy: RetryPolicy; + /** Endpoint behavior flags */ + options: BatchUploadPreferenceOptions; + /** Decide if a status is retryable *in place* (no splitting) */ + isRetryableStatus: (status?: number) => boolean; +} + +/** + * Upload a batch of entries with retry + split fallback. + * + * Orchestrates the per-chunk upload flow with: + * 1) Whole-batch attempt + * 2) In-place retries for retryable statuses + * 3) Recursive splitting for non-retryable errors (down to singletons) + * + * @param entries - Array of [primaryKey, update] pairs + * @param deps - Injected transport + policy + logger + * @param callbacks - Callback functions + */ +export async function uploadChunkWithSplit( + entries: Entry[], + deps: BatchUploaderDeps, + callbacks: { + /** Callback invoked after a successful upload of `entries` */ + onSuccess: (entries: Entry[]) => Promise; + /** Callback for single-entry failure terminal case */ + onFailureSingle: (entry: Entry, err: unknown) => Promise; + /** Callback for terminal failure of the entire batch */ + onFailureBatch: (entries: Entry[], err: unknown) => Promise; + }, +): Promise { + // Run the batch job + const putAll = (): Promise => + deps.putBatch( + entries.map(([, u]) => u), + deps.options, + ); + + try { + // 1) Try the whole batch once. + await putAll(); + await callbacks.onSuccess(entries); + } catch (errRaw) { + let err = errRaw; + const status = getErrorStatus(err); + const msg = extractErrorMessage(err); + + // 2) For retryable statuses, attempt in-place retries without splitting. + const isSoftRateLimit = + status === 400 && /slow down|please try again shortly/i.test(msg); + + if (deps.isRetryableStatus(status) || isSoftRateLimit) { + try { + await retrySamePromise(putAll, deps.retryPolicy, (note) => + logger.warn(colors.yellow(note)), + ); + await callbacks.onSuccess(entries); + return; + } catch (err2) { + // If we *still* have a retryable status after exhausting attempts, + // mark the entire batch as failed (do NOT split). + if (deps.isRetryableStatus(getErrorStatus(err2))) { + logger.error( + colors.red( + `Exhausted retries for batch of ${entries.length}. Marking entire batch as failed.`, + ), + ); + await callbacks.onFailureBatch(entries, err2); + return; + } + // Otherwise, fall through to split behavior with the new error. + err = err2; + } + } + + // 3) Non-retryable path: split the batch and recurse down to singletons. + if (entries.length === 1) { + // Terminal case: one record left and it still fails → mark failure. + try { + await putAll(); + await callbacks.onSuccess(entries); + } catch (singleErr) { + await callbacks.onFailureSingle(entries[0], singleErr); + } + return; + } + + const [left, right] = splitInHalf(entries); + logger.warn( + `Non-retryable error for batch of ${entries.length} (status=${status}): ${msg}. ` + + `Splitting into ${left.length} and ${right.length}.`, + ); + + await uploadChunkWithSplit(left, deps, callbacks); + await uploadChunkWithSplit(right, deps, callbacks); + } +} diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts new file mode 100644 index 00000000..df18a749 --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -0,0 +1,160 @@ +import colors from 'colors'; +import type { Got } from 'got'; +import { logger } from '../../../../logger'; +import { parseAttributesFromString } from '../../../../lib/requests'; +import { + loadReferenceData, + type PreferenceUploadReferenceData, +} from './loadReferenceData'; +import { type PreferenceReceiptsInterface } from '../receiptsState'; +import { type PreferenceSchemaInterface } from '../schemaState'; +import { parsePreferenceManagementCsvWithCache } from '../../../../lib/preference-management'; +import type { + FileFormatState, + PendingSafePreferenceUpdates, + PendingWithConflictPreferenceUpdates, + SkippedPreferenceUpdates, +} from '../../../../lib/preference-management/codecs'; +import type { FormattedAttribute } from '../../../../lib/graphql/formatAttributeValues'; +import type { GraphQLClient } from 'graphql-request'; +import { limitRecords } from '../../../../lib/helpers'; + +export interface InteractiveUploadPreferencePlan { + /** CSV file path to load preference records from */ + file: string; + /** Partition key used throughout the upload */ + partition: string; + + /** Parsed "workflow attributes" (Key:Value pairs) */ + parsedAttributes: FormattedAttribute[]; + /** Reference data for transforming rows → PreferenceUpdateItem payloads */ + references: PreferenceUploadReferenceData; + /** Result sets derived entirely from validation/pre-processing */ + result: { + pendingSafeUpdates: PendingSafePreferenceUpdates; + pendingConflictUpdates: PendingWithConflictPreferenceUpdates; + skippedUpdates: SkippedPreferenceUpdates; + }; + + /** Snapshot of schema mappings to use during payload building */ + schema: Omit; +} + +/** + * Build an InteractiveUploadPreferencePlan by performing *validation-only* work. + * + * This performs *all pre-processing and validation* up front: + * - Reads the CSV + * - Validates timestamp column and identifier mappings (schema cache) + * - Maps columns to purposes/preferences + * - Loads current consent records (unless skipExistingRecordCheck=true) + * - Computes: pendingSafeUpdates / pendingConflictUpdates / skippedUpdates + * - Seeds the receipts file with snapshots of the pending sets + * + * The returned plan can be passed to `interactivePreferenceUploaderFromPlan` + * to perform the actual upload, keeping responsibilities cleanly separated. + * + * @param opts - Input options required to parse & validate the CSV + * @returns A fully-resolved plan ready to pass to the uploader + */ +export async function buildInteractiveUploadPreferencePlan({ + sombra, + client, + file, + partition, + receipts, + schema, + skipExistingRecordCheck = false, + forceTriggerWorkflows = false, + allowedIdentifierNames, + maxRecordsToReceipt = 50, + identifierColumns, + columnsToIgnore = [], + attributes = [], +}: { + /** Transcend GraphQL client */ + client: GraphQLClient; + /** Sombra instance to make requests to */ + sombra: Got; + /** CSV file to process */ + file: string; + /** Partition used to scope reads/writes */ + partition: string; + /** Receipts snapshots */ + receipts: PreferenceReceiptsInterface; + /** Schema information */ + schema: PreferenceSchemaInterface; + /** Skip the preflight existing-record check for speed (initial loads only) */ + skipExistingRecordCheck?: boolean; + /** Force workflow triggers; requires existing consent records for all rows */ + forceTriggerWorkflows?: boolean; + /** Allowed identifier names configured for the org/run */ + allowedIdentifierNames: string[]; + /** CSV columns that correspond to identifiers */ + identifierColumns: string[]; + /** CSV columns to ignore entirely */ + columnsToIgnore?: string[]; + /** Extra workflow attributes (pre-parsed Key:Value strings) */ + attributes?: string[]; + /** Maximum records to write out to the receipt file */ + maxRecordsToReceipt?: number; +}): Promise { + const parsedAttributes = parseAttributesFromString(attributes); + + // Informative status about prior runs (resume/diagnostics) + const failing = receipts.getFailing(); + const pending = receipts.getPending(); + logger.info( + colors.magenta( + 'Restored cache:\n' + + `${Object.values(failing).length} failing requests queued for retry\n` + + `${Object.values(pending).length} pending requests to process\n` + + `Processing file: ${file}\n`, + ), + ); + + // Build clients + reference data (purposes/topics/identifiers) + const references = await loadReferenceData(client, forceTriggerWorkflows); + + // Parse & validate CSV → derive safe/conflict/skipped sets (no uploading) + const parsed = await parsePreferenceManagementCsvWithCache( + { + file, + purposeSlugs: references.purposes.map((x) => x.trackingType), + preferenceTopics: references.preferenceTopics, + sombra, + partitionKey: partition, + skipExistingRecordCheck, + forceTriggerWorkflows, + orgIdentifiers: references.identifiers, + allowedIdentifierNames, + identifierColumns, + columnsToIgnore, + }, + schema.state, + ); + + // Persist small snapshots of the pending sets into receipts for resumability. + await receipts.setPendingSafe( + limitRecords(parsed.pendingSafeUpdates, maxRecordsToReceipt), + ); + await receipts.setPendingConflict(parsed.pendingConflictUpdates); + + // Return a compact, self-contained plan for the upload stage. + return { + file, + partition, + parsedAttributes, + references, + result: { + pendingSafeUpdates: parsed.pendingSafeUpdates, + pendingConflictUpdates: parsed.pendingConflictUpdates, + skippedUpdates: parsed.skippedUpdates, + }, + schema: { + timestampColumn: schema.getTimestampColumn(), + columnToPurposeName: schema.getColumnToPurposeName(), + columnToIdentifier: schema.getColumnToIdentifier(), + }, + }; +} diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts new file mode 100644 index 00000000..7ef7e028 --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -0,0 +1,303 @@ +import colors from 'colors'; +import { map as pMap } from 'bluebird'; +import { chunk } from 'lodash-es'; +import { logger } from '../../../../logger'; +import { buildPendingUpdates } from '../transform/buildPendingUpdates'; +import { uploadChunkWithSplit } from './batchUploader'; +import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; +import { RETRYABLE_BATCH_STATUSES } from '../../../../constants'; +import { extractErrorMessage, limitRecords } from '../../../../lib/helpers'; +import type { InteractiveUploadPreferencePlan } from './buildInteractiveUploadPlan'; +import type { PreferenceReceiptsInterface } from '../receiptsState'; +import type { Got } from 'got'; + +/** + * Execute the upload using a pre-built InteractiveUploadPlan. + * + * This function performs *no CSV parsing or validation*. It: + * - Converts pre-validated safe/conflict sets into PreferenceUpdateItem payloads + * - Batches + uploads with retry/split semantics + * - Writes progress snapshots to receipts + * + * @param plan - Output of `buildInteractiveUploadPlan` + * @param options - Upload-only options (batch size, concurrency, etc.) + */ +export async function interactivePreferenceUploaderFromPlan( + { + partition, + parsedAttributes, + references: { purposes, preferenceTopics }, + result: { pendingSafeUpdates, pendingConflictUpdates }, + schema, + }: InteractiveUploadPreferencePlan, + { + receipts, + sombra, + dryRun = false, + isSilent = true, + skipWorkflowTriggers = false, + skipConflictUpdates = false, + forceTriggerWorkflows = false, + uploadLogInterval = 1_000, + maxChunkSize = 50, + uploadConcurrency = 20, + maxRecordsToReceipt = 50, + }: { + /** Receipts interface */ + receipts: PreferenceReceiptsInterface; + /** Sombra got instance */ + sombra: Got; + /** Compute-only mode: do not PUT; still writes receipts snapshots */ + dryRun?: boolean; + /** Avoid downstream visible notifications */ + isSilent?: boolean; + /** Skip workflow triggers for each update */ + skipWorkflowTriggers?: boolean; + /** Only upload safe updates (ignore conflicts entirely) */ + skipConflictUpdates?: boolean; + /** Force triggering workflows for each update (use sparingly) */ + forceTriggerWorkflows?: boolean; + /** Log/persist cadence for progress updates */ + uploadLogInterval?: number; + /** Max records in a single batch PUT to v1/preferences */ + maxChunkSize?: number; + /** Max concurrent batch tasks at once */ + uploadConcurrency?: number; + /** Maximum records to write out to the receipt file */ + maxRecordsToReceipt?: number; + }, +): Promise { + // Build final payloads (pure transform; no network) + const pendingUpdates: Record = + buildPendingUpdates({ + safe: pendingSafeUpdates, + conflicts: pendingConflictUpdates, + skipConflictUpdates, + timestampColumn: schema.timestampColumn, + columnToPurposeName: schema.columnToPurposeName, + columnToIdentifier: schema.columnToIdentifier, + preferenceTopics, + purposes, + partition, + workflowAttrs: parsedAttributes, + isSilent, + skipWorkflowTriggers, + }); + + // Seed pending uploads into receipts (first 10 expanded to keep file size small) + await receipts.setPending(limitRecords(pendingUpdates, maxRecordsToReceipt)); + + // Dry-run exits before any network calls + if (dryRun) { + logger.info( + colors.green( + `Dry run complete — ${ + Object.values(pendingUpdates).length + } pending updates. ` + + `See receipts file: ${receipts.receiptsFilepath}`, + ), + ); + return; + } + + logger.info( + colors.magenta( + `Uploading ${ + Object.values(pendingUpdates).length + } preferences to partition: ${partition}`, + ), + ); + + const t0 = Date.now(); + let uploadedCount = 0; + + const successful = receipts.getSuccessful(); + const allEntries = Object.entries(pendingUpdates) as Array< + [string, PreferenceUpdateItem] + >; + const filtered = allEntries.filter(([userId]) => !successful[userId]); + + if (filtered.length === 0) { + logger.warn( + colors.yellow( + `No pending updates to upload (all ${allEntries.length} are already marked successful).`, + ), + ); + await receipts.resetPending(); + return; + } + if (filtered.length < allEntries.length) { + logger.warn( + colors.yellow( + `Filtered ${ + allEntries.length - filtered.length + } already-successful updates. ` + + `${filtered.length} remain to upload.`, + ), + ); + } + + // Retry policy for "retry in place" statuses + const retryPolicy = { + maxAttempts: 3, + delayMs: 10_000, + shouldRetry: (status?: number) => + !!status && RETRYABLE_BATCH_STATUSES.has(status as any), + }; + + /** + * Mark a batch as successfully uploaded. Persists progress periodically based on + * `uploadLogInterval` to throttle IO and keep receipts compact. + * + * @param entries - Entries to mark as successful + */ + const markSuccessFor = async ( + entries: Array<[string, PreferenceUpdateItem]>, + ): Promise => { + const successfulUpdates = receipts.getSuccessful(); + + for (const [userId] of entries) { + successfulUpdates[userId] = true; + delete (pendingUpdates as any)[userId]; + // Also keep the safe/conflict mirrors in sync in case of resume + delete (pendingSafeUpdates as any)[userId]; + delete (pendingConflictUpdates as any)[userId]; + } + uploadedCount += entries.length; + + const shouldLog = + uploadedCount % uploadLogInterval === 0 || + Math.floor((uploadedCount - entries.length) / uploadLogInterval) < + Math.floor(uploadedCount / uploadLogInterval); + + if (shouldLog) { + logger.info( + colors.green( + `Uploaded ${uploadedCount}/${filtered.length} user preferences to partition ${partition}`, + ), + ); + await receipts.setSuccessful(successfulUpdates); + + await receipts.setPending( + limitRecords(pendingUpdates, maxRecordsToReceipt), + ); + await receipts.setPendingSafe( + limitRecords(pendingSafeUpdates, maxRecordsToReceipt), + ); + await receipts.setPendingConflict(pendingConflictUpdates); + } + }; + + /** + * Mark a single record failure with a concise, actionable error message. + * Mirrors are kept in sync to avoid reprocessing this row on resume. + * + * @param userId - User ID to mark as failed + * @param update - The update item that failed + * @param err - The error that occurred + */ + const markFailureForSingle = async ( + userId: string, + update: PreferenceUpdateItem, + err: unknown, + ): Promise => { + let msg = extractErrorMessage(err); + if (msg.includes('Too many identifiers')) { + // Add first identifier clue to speed up triage + msg += `\n ----> ${userId.split('___')[0]}`; + } + logger.error( + colors.red( + `Failed to upload preferences for ${userId} (partition=${partition}): ${msg}`, + ), + ); + const failing = receipts.getFailing(); + failing[userId] = { + uploadedAt: new Date().toISOString(), + update, + error: msg, + }; + + delete (pendingUpdates as any)[userId]; + delete (pendingSafeUpdates as any)[userId]; + delete (pendingConflictUpdates as any)[userId]; + + await receipts.setFailing(failing); + }; + + /** + * Mark an entire batch as failed (used when we exhaust in-place retries for + * retryable statuses). Delegates to the single-failure handler per entry. + * + * @param entries - Entries to mark as failed + * @param err - The error that occurred + */ + const markFailureForBatch = async ( + entries: Array<[string, PreferenceUpdateItem]>, + err: unknown, + ): Promise => { + for (const [userId, update] of entries) { + await markFailureForSingle(userId, update, err); + } + }; + + // Kick off uploads in chunks; each chunk may be recursively split on errors + const chunks = chunk(filtered, maxChunkSize); + await pMap( + chunks, + async (currentChunk) => { + await uploadChunkWithSplit( + currentChunk, + { + // Minimal transport surface for the uploader + putBatch: async (updates, opts) => { + await sombra + .put('v1/preferences', { + json: { + records: updates, + skipWorkflowTriggers: opts.skipWorkflowTriggers, + forceTriggerWorkflows: opts.forceTriggerWorkflows, + }, + }) + .json(); + }, + retryPolicy, + options: { skipWorkflowTriggers, forceTriggerWorkflows }, + isRetryableStatus: (s) => + !!s && RETRYABLE_BATCH_STATUSES.has(s as any), + }, + { + onSuccess: markSuccessFor, + onFailureSingle: ([userId, update], err) => + markFailureForSingle(userId, update, err), + onFailureBatch: markFailureForBatch, + }, + ); + }, + { concurrency: uploadConcurrency }, + ); + + // Finalize receipts: persist success map and clear pending mirrors + await receipts.setSuccessful(receipts.getSuccessful()); + await receipts.resetPending(); + + const elapsedSec = (Date.now() - t0) / 1000; + logger.info( + colors.green( + `Successfully uploaded ${ + Object.keys(receipts.getSuccessful()).length + } user preferences ` + + `to partition ${partition} in "${elapsedSec}" seconds!`, + ), + ); + + const remainingFailures = Object.values(receipts.getFailing()).length; + if (remainingFailures > 0) { + logger.error( + colors.red( + `There are ${remainingFailures} requests that failed to upload. ` + + `Please check the receipts file for details: ${receipts.receiptsFilepath}`, + ), + ); + } +} diff --git a/src/commands/consent/upload-preferences/upload/loadReferenceData.ts b/src/commands/consent/upload-preferences/upload/loadReferenceData.ts new file mode 100644 index 00000000..0e0dc8ed --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/loadReferenceData.ts @@ -0,0 +1,61 @@ +/** + * Module: clients/graphql + * + * Fetch and shape the reference data needed to transform CSV rows into + * PreferenceUpdateItem payloads (purposes, topics, identifiers). + */ +import type { GraphQLClient } from 'graphql-request'; +import { + buildTranscendGraphQLClient, + fetchAllPurposes, + fetchAllPreferenceTopics, + fetchAllIdentifiers, + type PreferenceTopic, + type Purpose, + type Identifier, +} from '../../../../lib/graphql'; + +export type PreferenceUploadReferenceData = { + /** + * List of purposes in the organization + */ + purposes: Purpose[]; + /** + * List of preference topics in the organization + */ + preferenceTopics: PreferenceTopic[]; + /** + * List of identifiers in the organization + */ + identifiers: Identifier[]; +}; + +/** + * Load all required reference data for an upload run. + * + * @param client - GraphQL client + * @param forceTriggerWorkflows - If true, skip loading purposes/topics + * @returns GraphQL client and reference data arrays + */ +export async function loadReferenceData( + client: GraphQLClient, + forceTriggerWorkflows: boolean, +): Promise< + { + /** + * GraphQL client to use for making requests + */ + client: ReturnType; + } & PreferenceUploadReferenceData +> { + const [purposes, preferenceTopics, identifiers] = await Promise.all([ + forceTriggerWorkflows + ? Promise.resolve([] as Purpose[]) + : fetchAllPurposes(client), + forceTriggerWorkflows + ? Promise.resolve([] as PreferenceTopic[]) + : fetchAllPreferenceTopics(client), + fetchAllIdentifiers(client), + ]); + return { client, purposes, preferenceTopics, identifiers }; +} diff --git a/src/constants.ts b/src/constants.ts index 08def6e2..c0aaff54 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -190,3 +190,11 @@ export const SCOPES_BY_TITLE = keyBy( >; export const SCOPE_TITLES = Object.keys(SCOPES_BY_TITLE); + +/** + * HTTP statuses that should be retried *in place* without splitting. + * 429: Rate-limited + * 502: Upstream/edge gateway error + * 329: Reserved for custom infra (kept defensively) + */ +export const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 329] as const); diff --git a/src/lib/graphql/makeGraphQLRequest.ts b/src/lib/graphql/makeGraphQLRequest.ts index b29bbe44..8e60c9f2 100644 --- a/src/lib/graphql/makeGraphQLRequest.ts +++ b/src/lib/graphql/makeGraphQLRequest.ts @@ -5,21 +5,10 @@ import type { } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; +import { sleepPromise } from '../helpers'; const MAX_RETRIES = 4; -/** - * Sleep in a promise - * - * @param sleepTime - The time to sleep in milliseconds. - * @returns Resolves promise - */ -function sleepPromise(sleepTime: number): Promise { - return new Promise((resolve) => { - setTimeout(() => resolve(sleepTime), sleepTime); - }); -} - const KNOWN_ERRORS = [ 'syntax error', 'got invalid value', diff --git a/src/lib/helpers/extractErrorMessage.ts b/src/lib/helpers/extractErrorMessage.ts new file mode 100644 index 00000000..ef28286a --- /dev/null +++ b/src/lib/helpers/extractErrorMessage.ts @@ -0,0 +1,31 @@ +/** + * Extract a human-readable error message from a thrown error. + * + * Tries to parse JSON bodies that follow common REST/GraphQL error patterns: + * { error: { message: string } } + * { errors: [{ message: string }, ...] } + * + * Falls back to `err.message` or 'Unknown error'. + * + * @param err - Unknown error thrown by network call + * @returns A concise error string safe to log/show + */ +export function extractErrorMessage(err: unknown): string { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const anyErr = err as any; + let errorMsg = anyErr?.response?.body || anyErr?.message || 'Unknown error'; + + // Try to parse as JSON; if not parsable, leave as-is. + try { + const parsed = JSON.parse(errorMsg); + // Typical shapes: errors[], error.errors[], error.message + const candidates = parsed.errors || + parsed.error?.errors || [parsed.error?.message || parsed.error]; + + const msgs = Array.isArray(candidates) ? candidates : [candidates]; + errorMsg = msgs.filter(Boolean).join(', '); + } catch { + // not JSON, ignore + } + return errorMsg; +} diff --git a/src/lib/helpers/getErrorStatus.ts b/src/lib/helpers/getErrorStatus.ts new file mode 100644 index 00000000..2b4eacc2 --- /dev/null +++ b/src/lib/helpers/getErrorStatus.ts @@ -0,0 +1,12 @@ +/** + * Extract an HTTP status code from a thrown error (got compatible). + * + * @param err - Unknown error thrown by network call + * @returns HTTP status code, if present + */ +export function getErrorStatus(err: unknown): number | undefined { + // Swallow unknowns carefully—never throw from an error handler. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const anyErr = err as any; + return anyErr?.response?.statusCode ?? anyErr?.response?.status; +} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index 73d30623..71adceca 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -2,3 +2,9 @@ export * from './buildAIIntegrationType'; export * from './buildEnabledRouteType'; export * from './inquirer'; export * from './parseVariablesFromString'; +export * from './getErrorStatus'; +export * from './extractErrorMessage'; +export * from './sleepPromise'; +export * from './splitInHalf'; +export * from './retrySamePromise'; +export * from './limitRecords'; diff --git a/src/lib/helpers/limitRecords.ts b/src/lib/helpers/limitRecords.ts new file mode 100644 index 00000000..ff26aaf2 --- /dev/null +++ b/src/lib/helpers/limitRecords.ts @@ -0,0 +1,17 @@ +/** + * Limits the number of records in the returned object to a maximum. + * For entries beyond the max, sets their value to `true`. + * + * @param obj - Object + * @param max - Maximum number of entries to retain original value. + * @returns Object with keys mapped to their value or `true` if over the limit. + */ +export function limitRecords( + obj: Record, + max: number, +): Record { + return Object.entries(obj).reduce((acc, [userId, value], i) => { + acc[userId] = i < max ? value : true; + return acc; + }, {} as Record); +} diff --git a/src/lib/helpers/retrySamePromise.ts b/src/lib/helpers/retrySamePromise.ts new file mode 100644 index 00000000..0edb76b7 --- /dev/null +++ b/src/lib/helpers/retrySamePromise.ts @@ -0,0 +1,69 @@ +/** + * Module: upload/retry + * + * Generic, reusable "retry-same-batch" helper used prior to any split logic. + */ + +import { sleepPromise } from './sleepPromise'; + +export interface RetryPolicy { + /** Maximum retry attempts (not counting the initial try) */ + maxAttempts: number; + /** Fixed delay between attempts in milliseconds */ + delayMs: number; + /** + * Decide whether a given error should be retried. + * + * @param status - HTTP status code (if known) + * @param message - Extracted error message (if known) + */ + shouldRetry(status?: number, message?: string): boolean; +} + +/** + * Retry a single async operation according to the provided policy. + * The operation is executed once initially, then up to `maxAttempts` retries. + * + * @param op - Operation to run + * @param policy - Retry policy + * @param onBackoff - Observer called before each retry (for logging/metrics) + * @returns Result of the operation if it eventually succeeds + * @throws The last error encountered if all retries fail + */ +export async function retrySamePromise( + op: () => Promise, + policy: RetryPolicy, + onBackoff: (note: string) => void, +): Promise { + let attempt = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + try { + // First pass and any subsequent retries run the same op. + return await op(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + attempt += 1; + + // Extract details defensively; do not throw from the handler. + const status: number | undefined = + err?.response?.statusCode ?? err?.response?.status; + const msg: string = + err?.response?.body || err?.message || 'Unknown error'; + + const canRetry = + attempt <= policy.maxAttempts && policy.shouldRetry(status, msg); + if (!canRetry) { + // Surface the final error to the caller, which may then split/fail. + throw err; + } + + onBackoff( + `Retrying after status=${status} attempt=${attempt}/${policy.maxAttempts} — ${msg}`, + ); + await sleepPromise(policy.delayMs); + // Loop to retry + } + } +} diff --git a/src/lib/helpers/sleepPromise.ts b/src/lib/helpers/sleepPromise.ts new file mode 100644 index 00000000..de321712 --- /dev/null +++ b/src/lib/helpers/sleepPromise.ts @@ -0,0 +1,11 @@ +/** + * Sleep in a promise + * + * @param sleepTime - The time to sleep in milliseconds. + * @returns Resolves promise + */ +export function sleepPromise(sleepTime: number): Promise { + return new Promise((resolve) => { + setTimeout(() => resolve(sleepTime), sleepTime); + }); +} diff --git a/src/lib/helpers/splitInHalf.ts b/src/lib/helpers/splitInHalf.ts new file mode 100644 index 00000000..62c8d1de --- /dev/null +++ b/src/lib/helpers/splitInHalf.ts @@ -0,0 +1,10 @@ +/** + * Split an array roughly in half. Stable for even/odd lengths. + * + * @param entries - Items to split + * @returns A tuple [left, right] halves + */ +export function splitInHalf(entries: T[]): [T[], T[]] { + const mid = Math.floor(entries.length / 2); + return [entries.slice(0, mid), entries.slice(mid)]; +} diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts index 2fe2002c..5b633411 100644 --- a/src/lib/pooling/attachWorkerHandlers.ts +++ b/src/lib/pooling/attachWorkerHandlers.ts @@ -1,6 +1,5 @@ import type { ChildProcess } from 'child_process'; -import type { WorkerState } from './renderDashboard'; -import { assignWorkToWorker } from './assignWorkToWorker'; +import { assignWorkToWorker, type WorkerState } from './assignWorkToWorker'; import { appendFileSync } from 'fs'; import { join } from 'path'; import { spawnWorkerProcess } from './spawnWorkerProcess'; diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 11bea65b..f0562f3a 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -1,17 +1,7 @@ // renderer.ts import { basename } from 'node:path'; import * as readline from 'node:readline'; - -export interface WorkerState { - /** */ - busy: boolean; - /** */ - file?: string | null; - /** */ - startedAt?: number | null; - /** last severity seen from worker stderr */ - lastLevel?: 'ok' | 'warn' | 'error'; -} +import type { WorkerState } from './assignWorkToWorker'; let lastFrame = ''; diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index c9e26f58..f891a271 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -42,25 +42,44 @@ export const PurposeRowMapping = t.type({ /** Override type */ export type PurposeRowMapping = t.TypeOf; +/** + * Mapping of column name to purpose row mapping. + * This is used to map each column in the CSV to the relevant purpose and preference definitions in + * transcend. + */ +export const ColumnPurposeMap = t.record(t.string, PurposeRowMapping); + +/** Override type */ +export type ColumnPurposeMap = t.TypeOf; + +/** + * Mapping of identifier name to the column name in the CSV file. + * This is used to map each identifier name to the column in the CSV file. + */ +export const ColumnIdentifierMap = t.record( + t.string, + t.type({ + /** The identifier name */ + name: t.string, + /** Is unique on preference store */ + isUniqueOnPreferenceStore: t.boolean, + }), +); + +/** Override type */ +export type ColumnIdentifierMap = t.TypeOf; + export const FileFormatState = t.intersection([ t.type({ /** * Definition of how to map each column in the CSV to * the relevant purpose and preference definitions in transcend */ - columnToPurposeName: t.record(t.string, PurposeRowMapping), + columnToPurposeName: ColumnPurposeMap, /** Last time the file was last parsed at */ lastFetchedAt: t.string, /** The column name that maps to the identifier */ - columnToIdentifier: t.record( - t.string, - t.type({ - /** The identifier name */ - name: t.string, - /** Is unique on preference store */ - isUniqueOnPreferenceStore: t.boolean, - }), - ), + columnToIdentifier: ColumnIdentifierMap, }), t.partial({ /** Determine which column name in file maps to the timestamp */ @@ -71,6 +90,105 @@ export const FileFormatState = t.intersection([ /** Override type */ export type FileFormatState = t.TypeOf; +/** + * This is the type of the receipts that are stored in the file + * that is used to track the state of the upload process. + * It is used to resume the upload process from where it left off. + * It is used to persist the state of the upload process across multiple runs. + */ +export const PreferenceUpdateMap = t.record( + t.string, + // This can either be true to indicate the record is pending + // or it can be an object showing the object + // We only return a fixed number of results to avoid + // making the JSON file too large + t.union([t.boolean, PreferenceUpdateItem]), +); + +/** Override type */ +export type PreferenceUpdateMap = t.TypeOf; + +/** + * This is the type of the pending updates that are safe to run without + * conflicts with existing consent preferences. + * + * Key is primaryKey of the record in the file. + * The value is the row in the file that is safe to upload. + */ +export const PendingSafePreferenceUpdates = t.record( + t.string, + // This can either be true to indicate the record is safe + // or it can be an object showing the object + // We only return a fixed number of results to avoid + // making the JSON file too large + t.union([t.boolean, t.record(t.string, t.string)]), +); + +/** Override type */ +export type PendingSafePreferenceUpdates = t.TypeOf< + typeof PendingSafePreferenceUpdates +>; + +/** + * These are the updates that failed to be uploaded to the API. + */ +export const FailingPreferenceUpdates = t.record( + t.string, + t.type({ + /** Time upload ran at */ + uploadedAt: t.string, + /** Attempts to upload that resulted in an error */ + error: t.string, + /** The update body */ + update: PreferenceUpdateItem, + }), +); + +/** Override type */ +export type FailingPreferenceUpdates = t.TypeOf< + typeof FailingPreferenceUpdates +>; + +/** + * This is the type of the pending updates that are in conflict with existing consent preferences. + * + * Key is primaryKey of the record in the file. + * The value is the row in the file that is pending upload. + */ +export const PendingWithConflictPreferenceUpdates = t.record( + t.string, + // We always return the conflicts for investigation + t.type({ + /** Record to be inserted to transcend v1/preferences API */ + record: PreferenceQueryResponseItem, + /** The row in the file that is pending upload */ + row: t.record(t.string, t.string), + }), +); + +/** Override type */ +export type PendingWithConflictPreferenceUpdates = t.TypeOf< + typeof PendingWithConflictPreferenceUpdates +>; + +/** + * The set of preference updates that are skipped + * Key is primaryKey and value is the row in the CSV + * that is skipped. + * + * This is usually because the preferences are already in the store + * or there are duplicate rows in the CSV file that are identical. + */ +export const SkippedPreferenceUpdates = t.record( + t.string, + t.record(t.string, t.string), +); + +/** Override type */ +export type SkippedPreferenceUpdates = t.TypeOf< + typeof SkippedPreferenceUpdates +>; + export const RequestUploadReceipts = t.type({ /** Last time the file was last parsed at */ lastFetchedAt: t.string, @@ -85,14 +203,7 @@ export const RequestUploadReceipts = t.type({ * So this will say the updates were safe when in fact we don't know. * We just let the default consent resolution logic handle it. */ - pendingSafeUpdates: t.record( - t.string, - // This can either be true to indicate the record is safe - // or it can be an object showing the object - // We only return a fixed number of results to avoid - // making the JSON file too large - t.union([t.boolean, t.record(t.string, t.string)]), - ), + pendingSafeUpdates: PendingSafePreferenceUpdates, /** * Mapping of primaryKey to the rows in the file that need to be uploaded * these records have conflicts with existing consent preferences. @@ -106,14 +217,7 @@ export const RequestUploadReceipts = t.type({ * * Set to `--skipExistingRecordCheck=false --dryRun=true` to get the list of conflicts. */ - pendingConflictUpdates: t.record( - t.string, - // We always return the conflicts for investigation - t.type({ - record: PreferenceQueryResponseItem, - row: t.record(t.string, t.string), - }), - ), + pendingConflictUpdates: PendingWithConflictPreferenceUpdates, /** * Mapping of primaryKey to the rows in the file that can be skipped because * their preferences are already in the store. These records may be skipped @@ -121,37 +225,20 @@ export const RequestUploadReceipts = t.type({ * * If `--skipExistingRecordCheck=false` - then no-ops will be filtered out. */ - skippedUpdates: t.record(t.string, t.record(t.string, t.string)), + skippedUpdates: SkippedPreferenceUpdates, /** * The set of failing updates * Mapping from primaryKey to the request payload, time upload happened * and error message. */ - failingUpdates: t.record( - t.string, - t.type({ - /** Time upload ran at */ - uploadedAt: t.string, - /** Attempts to upload that resulted in an error */ - error: t.string, - /** The update body */ - update: PreferenceUpdateItem, - }), - ), + failingUpdates: FailingPreferenceUpdates, /** * The set of uploads that were pending at the time that the cache file * was last written to. When using `--dryRun=true` this list will be full. * * When running `--dryRun=false` this set will shrink as updates are processed. */ - pendingUpdates: t.record( - t.string, - // This can either be true to indicate the record is pending - // or it can be an object showing the object - // We only return a fixed number of results to avoid - // making the JSON file too large - t.union([t.boolean, PreferenceUpdateItem]), - ), + pendingUpdates: PreferenceUpdateMap, /** * The updates that were successfully processed * Mapping from primaryKey to the request response. @@ -160,14 +247,7 @@ export const RequestUploadReceipts = t.type({ * If `--dryRun=false` is set, this will contain * the updates that were successfully processed. */ - successfulUpdates: t.record( - t.string, - // This can either be true to indicate the record is successful - // or it can be an object showing the object - // We only return a fixed number of results to avoid - // making the JSON file too large - t.union([t.boolean, PreferenceUpdateItem]), - ), + successfulUpdates: PreferenceUpdateMap, }); /** Override type */ diff --git a/src/lib/preference-management/index.ts b/src/lib/preference-management/index.ts index 36c72522..e690fba1 100644 --- a/src/lib/preference-management/index.ts +++ b/src/lib/preference-management/index.ts @@ -1,4 +1,3 @@ -export * from './uploadPreferenceManagementPreferencesInteractive'; export * from './codecs'; export * from './getPreferencesForIdentifiers'; export * from './parsePreferenceManagementCsv'; diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index c67fdfdd..378c0b18 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -3,7 +3,13 @@ import type { Got } from 'got'; import { keyBy } from 'lodash-es'; import * as t from 'io-ts'; import colors from 'colors'; -import { type FileFormatState, type RequestUploadReceipts } from './codecs'; +import { + type FileFormatState, + type PendingSafePreferenceUpdates, + type PendingWithConflictPreferenceUpdates, + type RequestUploadReceipts, + type SkippedPreferenceUpdates, +} from './codecs'; import { logger } from '../../logger'; import { readCsv } from '../requests'; import { getPreferencesForIdentifiers } from './getPreferencesForIdentifiers'; @@ -26,6 +32,7 @@ import type { ObjByString } from '@transcend-io/type-utils'; * * @param options - Options * @param schemaState - The schema state to use for parsing the file + * @param schema * @returns The cache with the parsed file */ export async function parsePreferenceManagementCsvWithCache( @@ -68,11 +75,11 @@ export async function parsePreferenceManagementCsvWithCache( schemaState: PersistedState, ): Promise<{ /** Pending saf updates */ - pendingSafeUpdates: Record>; + pendingSafeUpdates: PendingSafePreferenceUpdates; /** Pending conflict updates */ - pendingConflictUpdates: RequestUploadReceipts['pendingConflictUpdates']; - /** SKipped updates */ - skippedUpdates: RequestUploadReceipts['skippedUpdates']; + pendingConflictUpdates: PendingWithConflictPreferenceUpdates; + /** Skipped updates */ + skippedUpdates: SkippedPreferenceUpdates; }> { // Start the timer const t0 = new Date().getTime(); diff --git a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts b/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts deleted file mode 100644 index 14f75ca0..00000000 --- a/src/lib/preference-management/uploadPreferenceManagementPreferencesInteractive.ts +++ /dev/null @@ -1,598 +0,0 @@ -/* eslint-disable max-lines */ -import { - buildTranscendGraphQLClient, - createSombraGotInstance, - fetchAllPurposes, - fetchAllPreferenceTopics, - PreferenceTopic, - Purpose, - fetchAllIdentifiers, -} from '../graphql'; -import colors from 'colors'; -import { map } from 'bluebird'; -import { chunk } from 'lodash-es'; -import { logger } from '../../logger'; -import { parseAttributesFromString } from '../requests'; -import { PersistedState } from '@transcend-io/persisted-state'; -import { parsePreferenceManagementCsvWithCache } from './parsePreferenceManagementCsv'; -import { FileFormatState, RequestUploadReceipts } from './codecs'; -import { PreferenceUpdateItem } from '@transcend-io/privacy-types'; -import { apply, getEntries } from '@transcend-io/type-utils'; -import { NONE_PREFERENCE_MAP } from './parsePreferenceFileFormatFromCsv'; -import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; -import { getPreferenceIdentifiersFromRow } from './parsePreferenceIdentifiersFromCsv'; - -const LOG_RATE = 1000; // FIXMe set to 10k -const CONCURRENCY = 75; // FIXME 20 -const MAX_CHUNK_SIZE = 50; // FIXME - -// Treat these as "retry in place" errors (do NOT split on these). -// Note: 329 included defensively in case of custom upstream code; primarily handle 429 & 502. -const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 329] as const); - -const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); - -/** - * Extract HTTP status code from an error thrown by got - * - * @param err - */ -function getStatus(err: any): number | undefined { - return err?.response?.statusCode ?? err?.response?.status; -} - -/** - * Extract a readable error message from an error thrown by got / server - * - * @param err - */ -function extractErrorMessage(err: any): string { - let errorMsg = err?.response?.body || err?.message || 'Unknown error'; - try { - const parsed = JSON.parse(errorMsg); - if (parsed.error) { - // common GraphQL/REST patterns - const msgs = parsed.errors || - parsed.error?.errors || [parsed.error?.message || parsed.error]; - errorMsg = (Array.isArray(msgs) ? msgs : [msgs]).join(', '); - } - } catch { - // leave as-is - } - return errorMsg; -} - -/** - * Upload a set of consent preferences - * - * @param options - Options - */ -export async function uploadPreferenceManagementPreferencesInteractive({ - auth, - sombraAuth, - receiptFilepath, - schemaFilePath, - file, - partition, - isSilent = true, - dryRun = false, - skipWorkflowTriggers = false, - skipConflictUpdates = false, - skipExistingRecordCheck = false, - attributes = [], - transcendUrl, - forceTriggerWorkflows = false, - allowedIdentifierNames, - identifierColumns, - columnsToIgnore = [], -}: { - /** The Transcend API key */ - auth: string; - /** Sombra API key authentication */ - sombraAuth?: string; - /** Partition key */ - partition: string; - /** File where to store receipt and continue from where left off */ - receiptFilepath: string; - /** Path to schema file */ - schemaFilePath: string; - /** The file to process */ - file: string; - /** API URL for Transcend backend */ - transcendUrl: string; - /** Whether to do a dry run */ - dryRun?: boolean; - /** Whether to upload as isSilent */ - isSilent?: boolean; - /** Attributes string pre-parse. In format Key:Value */ - attributes?: string[]; - /** Skip workflow triggers */ - skipWorkflowTriggers?: boolean; - /** - * When true, only update preferences that do not conflict with existing - * preferences. When false, update all preferences in CSV based on timestamp. - */ - skipConflictUpdates?: boolean; - /** Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD */ - skipExistingRecordCheck?: boolean; - /** Whether to force trigger workflows */ - forceTriggerWorkflows?: boolean; - /** identifiers configured for the run */ - allowedIdentifierNames: string[]; - /** identifier columns on the CSV file */ - identifierColumns: string[]; - /** Columns to ignore in the CSV file */ - columnsToIgnore: string[]; -}): Promise { - // Parse out the extra attributes to apply to all requests uploaded - const parsedAttributes = parseAttributesFromString(attributes); - - // Create a new state file to store the requests from this run - const uploadState = new PersistedState( - receiptFilepath, - RequestUploadReceipts, - { - failingUpdates: {}, - pendingConflictUpdates: {}, - skippedUpdates: {}, - pendingSafeUpdates: {}, - successfulUpdates: {}, - pendingUpdates: {}, - lastFetchedAt: new Date().toISOString(), - }, - ); - const schemaState = new PersistedState(schemaFilePath, FileFormatState, { - columnToPurposeName: {}, - lastFetchedAt: new Date().toISOString(), - columnToIdentifier: {}, - }); - const failingRequests = uploadState.getValue('failingUpdates'); - const pendingRequests = uploadState.getValue('pendingUpdates'); - - logger.info( - colors.magenta( - 'Restored cache, there are: \n' + - `${ - Object.values(failingRequests).length - } failing requests to be retried\n` + - `${ - Object.values(pendingRequests).length - } pending requests to be processed\n` + - `The following file will be processed: ${file}\n`, - ), - ); - - // Create GraphQL client to connect to Transcend backend - const client = buildTranscendGraphQLClient(transcendUrl, auth); - - const [sombra, purposes, preferenceTopics, identifiers] = await Promise.all([ - // Create sombra instance to communicate with - createSombraGotInstance(transcendUrl, auth, sombraAuth), - // get all purposes and topics - forceTriggerWorkflows - ? Promise.resolve([] as Purpose[]) - : fetchAllPurposes(client), - forceTriggerWorkflows - ? Promise.resolve([] as PreferenceTopic[]) - : fetchAllPreferenceTopics(client), - fetchAllIdentifiers(client), - ]); - - // Process the file - const result = await parsePreferenceManagementCsvWithCache( - { - file, - purposeSlugs: purposes.map((x) => x.trackingType), - preferenceTopics, - sombra, - partitionKey: partition, - skipExistingRecordCheck, - forceTriggerWorkflows, - orgIdentifiers: identifiers, - allowedIdentifierNames, - identifierColumns, - columnsToIgnore, - }, - schemaState, - ); - - // Read in the file - uploadState.setValue( - getEntries(result.pendingSafeUpdates).reduce( - (acc, [userId, value], ind) => { - if (ind < 10) { - acc[userId] = value; - } else { - acc[userId] = true; - } - return acc; - }, - {} as Record, - ), - 'pendingSafeUpdates', - ); - uploadState.setValue(result.pendingConflictUpdates, 'pendingConflictUpdates'); - uploadState.setValue(result.skippedUpdates, 'skippedUpdates'); - - // Construct the pending updates - const pendingUpdates: Record = {}; - const timestampColumn = schemaState.getValue('timestampColumn'); - const columnToPurposeName = schemaState.getValue('columnToPurposeName'); - const columnToIdentifier = schemaState.getValue('columnToIdentifier'); - - logger.info( - colors.magenta( - `Found ${ - Object.entries(result.pendingSafeUpdates).length - } safe updates in ${file}`, - ), - ); - const conflictCount = Object.entries(result.pendingConflictUpdates).length; - if (conflictCount) { - logger.warn( - colors.magenta(`Found ${conflictCount} conflict updates in ${file}`), - ); - } - const skippedCount = Object.entries(result.skippedUpdates).length; - if (skippedCount > 0) { - logger.warn( - colors.magenta(`Found ${skippedCount} skipped updates in ${file}`), - ); - } - // Update either safe updates only or safe + conflict - Object.entries({ - ...result.pendingSafeUpdates, - ...(skipConflictUpdates - ? {} - : apply(result.pendingConflictUpdates, ({ row }) => row)), - }).forEach(([userId, update]) => { - // Determine timestamp - const timestamp = - timestampColumn === NONE_PREFERENCE_MAP - ? new Date() - : new Date(update[timestampColumn!]); - - // Determine updates - const updates = getPreferenceUpdatesFromRow({ - row: update, - columnToPurposeName, - preferenceTopics, - purposeSlugs: purposes.map((x) => x.trackingType), - }); - const identifiersForRow = getPreferenceIdentifiersFromRow({ - row: update, - columnToIdentifier, - }); - pendingUpdates[userId] = { - identifiers: identifiersForRow, - partition, - timestamp: timestamp.toISOString(), - purposes: Object.entries(updates).map(([purpose, value]) => ({ - ...value, - purpose, - workflowSettings: { - attributes: parsedAttributes, - isSilent, - skipWorkflowTrigger: skipWorkflowTriggers, - }, - })), - }; - }); - // FIXME restart better - await uploadState.setValue( - Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { - if (ind < 10) { - acc[userId] = value; - } else { - acc[userId] = true; - } - return acc; - }, {} as Record), - 'pendingUpdates', - ); - await uploadState.setValue({}, 'failingUpdates'); - // await uploadState.setValue({}, 'successfulUpdates'); dont reset - - // Exist early if dry run - if (dryRun) { - logger.info( - colors.green( - `Dry run complete, exiting. ${ - Object.values(pendingUpdates).length - } pending updates. Check file: ${receiptFilepath}`, - ), - ); - return; - } - - logger.info( - colors.magenta( - `Uploading ${ - Object.values(pendingUpdates).length - } preferences to partition: ${partition}`, - ), - ); - - // Time duration - const t0 = new Date().getTime(); - - // Build a GraphQL client - let uploadedCount = 0; - const allUpdatesPending = Object.entries(pendingUpdates); - const successfulUpdates = uploadState.getValue('successfulUpdates'); - const filteredUpdates = allUpdatesPending.filter( - ([userId]) => !successfulUpdates[userId], - ); - if (filteredUpdates.length === 0) { - logger.warn( - colors.yellow( - `No pending updates to upload to partition: ${partition}, ` + - `${allUpdatesPending.length} total already successfully uploaded.`, - ), - ); - await uploadState.setValue({}, 'pendingUpdates'); - await uploadState.setValue({}, 'pendingSafeUpdates'); - await uploadState.setValue({}, 'pendingConflictUpdates'); - return; - } - if (filteredUpdates.length < allUpdatesPending.length) { - logger.warn( - colors.yellow( - `Found ${allUpdatesPending.length} total updates, but only ` + - `${filteredUpdates.length} updates to upload to partition: ${partition}, ` + - `as ${ - allUpdatesPending.length - filteredUpdates.length - } were already successfully uploaded.`, - ), - ); - } - - // --- Helpers bound to closure state --- - - const markSuccessFor = async ( - entries: Array<[string, PreferenceUpdateItem]>, - ) => { - for (const [userId] of entries) { - successfulUpdates[userId] = true; - delete pendingUpdates[userId]; - delete result.pendingSafeUpdates[userId]; - delete result.pendingConflictUpdates[userId]; - } - uploadedCount += entries.length; - - // Periodic persistence + logging - if ( - uploadedCount % LOG_RATE === 0 || - Math.floor((uploadedCount - entries.length) / LOG_RATE) < - Math.floor(uploadedCount / LOG_RATE) - ) { - logger.info( - colors.green( - `Uploaded ${uploadedCount}/${filteredUpdates.length} user preferences to partition ${partition}`, - ), - ); - await uploadState.setValue(successfulUpdates, 'successfulUpdates'); - await uploadState.setValue( - Object.entries(pendingUpdates).reduce((acc, [userId, value], ind) => { - if (ind < 10) { - acc[userId] = value; - } else { - acc[userId] = true; - } - return acc; - }, {} as Record), - 'pendingUpdates', - ); - await uploadState.setValue( - Object.entries(result.pendingSafeUpdates).reduce( - (acc, [userId, value], ind) => { - if (ind < 10) { - acc[userId] = value; - } else { - acc[userId] = true; - } - return acc; - }, - {} as Record, - ), - 'pendingSafeUpdates', - ); - await uploadState.setValue( - result.pendingConflictUpdates, - 'pendingConflictUpdates', - ); - } - }; - - const markFailureForSingle = async ( - userId: string, - update: PreferenceUpdateItem, - err: any, - ) => { - let errorMsg = extractErrorMessage(err); - if (errorMsg.includes('Too many identifiers')) { - errorMsg += `\n ----> ${userId.split('___')[0]}`; - } - logger.error( - colors.red( - `Failed to upload user preferences for ${userId} to partition ${partition}: ${errorMsg}`, - ), - ); - const failing = uploadState.getValue('failingUpdates'); - failing[userId] = { - uploadedAt: new Date().toISOString(), - update, - error: errorMsg, - }; - delete pendingUpdates[userId]; - delete result.pendingSafeUpdates[userId]; - delete result.pendingConflictUpdates[userId]; - await uploadState.setValue(failing, 'failingUpdates'); - }; - - const markFailureForBatch = async ( - entries: Array<[string, PreferenceUpdateItem]>, - err: any, - ) => { - for (const [userId, update] of entries) { - await markFailureForSingle(userId, update, err); - } - }; - - const putBatch = async (entries: Array<[string, PreferenceUpdateItem]>) => - sombra - .put('v1/preferences', { - json: { - records: entries.map(([, update]) => update), - skipWorkflowTriggers, - forceTriggerWorkflows, - }, - }) - .json(); - - /** - * Try a batch, and if it fails: - * - On 429/502/329: retry same batch up to 3 times with 10s sleep (no splitting). - * - Otherwise: split batch in half and recurse, down to single-record requests. - * - * @param entries - */ - const uploadChunkWithSplit = async ( - entries: Array<[string, PreferenceUpdateItem]>, - ): Promise => { - // Fast path: attempt the whole batch - try { - await putBatch(entries); - await markSuccessFor(entries); - } catch (err) { - const status = getStatus(err); - if ( - (status && RETRYABLE_BATCH_STATUSES.has(status as any)) || - (status === 400 && - (extractErrorMessage(err).includes('Slow down') || - extractErrorMessage(err).includes('please try again shortly'))) - ) { - // Retry this SAME batch up to 3 times with backoff 10s - let attemptsLeft = 3; - while (attemptsLeft > 0) { - attemptsLeft -= 1; - logger.warn( - colors.yellow( - `Received ${status} for a batch of ${ - entries.length - }. Retrying in 10s... (${3 - attemptsLeft}/3)\n - -> ${extractErrorMessage(err)}`, - ), - ); - await sleep(3_000); - try { - await putBatch(entries); - await markSuccessFor(entries); - return; - } catch (retryErr) { - if ( - getStatus(retryErr) && - RETRYABLE_BATCH_STATUSES.has(getStatus(retryErr) as any) && - attemptsLeft > 0 - ) { - logger.warn( - colors.yellow( - `Retry attempt failed with ${getStatus( - retryErr, - )}, retrying again...`, - ), - ); - continue; // loop to retry again - } - // If status changed to non-retryable, fall through to split behavior - err = retryErr; - break; - } - } - - // Exhausted retries (still 429/502/329): mark the WHOLE batch as failed (do NOT split) - if ( - getStatus(err) && - RETRYABLE_BATCH_STATUSES.has(getStatus(err) as any) - ) { - logger.error( - colors.red( - `Exhausted retries for ${ - entries.length - } records due to ${getStatus( - err, - )}. Marking entire batch as failed.`, - ), - ); - await markFailureForBatch(entries, err); - return; - } - // Otherwise continue to split below (e.g., status changed to non-retryable). - } - - // Non-retryable error path -> split in half recursively (down to singles) - if (entries.length === 1) { - // Single request: try once and mark failure if it errors - try { - await putBatch(entries); - await markSuccessFor(entries); - } catch (singleErr) { - await markFailureForSingle(entries[0][0], entries[0][1], singleErr); - } - return; - } - - const mid = Math.floor(entries.length / 2); - const left = entries.slice(0, mid); - const right = entries.slice(mid); - logger.warn( - colors.yellow( - `Failed to upload batch of ${ - entries.length - } records with status: ${getStatus(err)} - ${extractErrorMessage( - err, - )}. Splitting into two batches: ${left.length} and ${right.length}.`, - ), - ); - await uploadChunkWithSplit(left); - await uploadChunkWithSplit(right); - } - }; - - // --- Kick off uploads in chunks (top-level), but each chunk may be recursively split if needed --- - const topLevelChunks = chunk(filteredUpdates, MAX_CHUNK_SIZE); - await map( - topLevelChunks, - async (currentChunk) => { - await uploadChunkWithSplit(currentChunk); - }, - { concurrency: CONCURRENCY }, - ); - - // Final persistence of state - await uploadState.setValue(successfulUpdates, 'successfulUpdates'); - await uploadState.setValue({}, 'pendingUpdates'); - await uploadState.setValue({}, 'pendingSafeUpdates'); - await uploadState.setValue({}, 'pendingConflictUpdates'); - - const t1 = new Date().getTime(); - const totalTime = t1 - t0; - logger.info( - colors.green( - `Successfully uploaded ${ - Object.keys(successfulUpdates).length - } user preferences to partition ${partition} in "${ - totalTime / 1000 - }" seconds!`, - ), - ); - if (Object.values(failingRequests).length > 0) { - logger.error( - colors.red( - `There are ${ - Object.values(failingRequests).length - } requests that failed to upload. Please check the receipt file: ${receiptFilepath}`, - ), - ); - } -} From ca37307952141fb9d9878e539f0b12ba8dcd9bc7 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 20:37:37 -0700 Subject: [PATCH 27/72] checkpoint --- README.md | 2 +- .../consent/upload-preferences/command.ts | 2 +- .../consent/upload-preferences/impl.ts | 382 ++++++++++-------- .../upload-preferences/receiptsState.ts | 78 ++-- .../upload-preferences/resolveReceiptPath.ts | 39 ++ .../consent/upload-preferences/runChild.ts | 14 +- .../consent/upload-preferences/schemaState.ts | 73 +++- .../upload/batchUploader.ts | 6 +- .../interactivePreferenceUploaderFromPlan.ts | 33 +- src/lib/helpers/RateCounter.ts | 55 +++ src/lib/helpers/index.ts | 1 + src/lib/pooling/assignWorkToWorker.ts | 112 +++-- src/lib/pooling/attachWorkerHandlers.ts | 188 +++++++-- src/lib/pooling/downloadArtifact.ts | 156 +++++++ src/lib/pooling/index.ts | 1 + src/lib/pooling/logRotation.ts | 122 +++++- src/lib/pooling/renderDashboard.ts | 321 ++++++++++----- src/lib/pooling/showCombinedLogs.ts | 96 +++++ src/lib/pooling/spawnWorkerProcess.ts | 201 ++++++--- 19 files changed, 1437 insertions(+), 445 deletions(-) create mode 100644 src/commands/consent/upload-preferences/resolveReceiptPath.ts create mode 100644 src/lib/helpers/RateCounter.ts create mode 100644 src/lib/pooling/downloadArtifact.ts create mode 100644 src/lib/pooling/showCombinedLogs.ts diff --git a/README.md b/README.md index fc183165..828ba841 100644 --- a/README.md +++ b/README.md @@ -2118,7 +2118,7 @@ FLAGS [--maxChunkSize] When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency. [default = 50] [--rateLimitRetryDelay] When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. This is only used if the request is rate limited by the Transcend API. If the request fails for any other reason, it will not be retried. [default = 3000] [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] - [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 50] + [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 10] --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index dce30106..5d611529 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -154,7 +154,7 @@ export const uploadPreferencesCommand = buildCommand({ brief: 'When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. ' + 'This is to avoid the receipt file getting too large for JSON.parse/stringify.', - default: '50', + default: '10', }, allowedIdentifierNames: { kind: 'parsed', diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 64111668..b625e10f 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -3,34 +3,39 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; import { join } from 'node:path'; -import { - mkdirSync, - existsSync, - readFileSync, - readdirSync, - statSync, -} from 'node:fs'; +import { mkdirSync, readFileSync } from 'node:fs'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; -import { - computeReceiptsFolder, - computeSchemaFile, - getFilePrefix, -} from './computeFiles'; +import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; import { collectCsvFilesOrExit } from './collectCsvFilesOrExit'; -import { availableParallelism } from 'node:os'; import type { ChildProcess } from 'node:child_process'; import { runChild } from './runChild'; import { + computePoolSize, getWorkerLogPaths, + isIpcOpen, renderDashboard, + safeSend, + showCombinedLogs, spawnWorkerProcess, } from '../../../lib/pooling'; import { installInteractiveSwitcher } from '../../../lib/pooling/installInteractiveSwitcher'; -import { resetWorkerLogs } from '../../../lib/pooling/logRotation'; +import { + classifyLogLevel, + makeLineSplitter, + resetWorkerLogs, +} from '../../../lib/pooling/logRotation'; import type { WorkerState } from '../../../lib/pooling/assignWorkToWorker'; +import { RateCounter } from '../../../lib/helpers'; +import { resolveReceiptPath } from './resolveReceiptPath'; +import { + exportCombinedLogs, + readFailingUpdatesFromReceipt, + writeFailingUpdatesCsv, + type FailingUpdateRow, +} from '../../../lib/pooling/downloadArtifact'; /** CLI flags */ export interface UploadPreferencesCommandFlags { @@ -89,85 +94,6 @@ export type TaskCommonOpts = Pick< receiptsFolder: string; }; -function getCurrentModulePath(): string { - // @ts-ignore - __filename exists in CJS/ts-node - if (typeof __filename !== 'undefined') return __filename as unknown as string; - return process.argv[1]; -} - -function computePoolSize( - concurrency: number | undefined, - filesCount: number, -): { poolSize: number; cpuCount: number } { - const cpuCount = Math.max(1, availableParallelism?.() ?? 1); - const desired = - typeof concurrency === 'number' && concurrency > 0 - ? Math.min(concurrency, filesCount) - : Math.min(cpuCount, filesCount); - return { poolSize: desired, cpuCount }; -} - -/** IPC helpers ------------------------------------------------------------ */ - -function isIpcOpen(w: ChildProcess | undefined | null): boolean { - // @ts-ignore - channel is internal but exists for forked children - const ch = w && (w as any).channel; - // @ts-ignore - return !!(w && w.connected && ch && !ch.destroyed); -} - -function safeSend(w: ChildProcess, msg: unknown): boolean { - if (!isIpcOpen(w)) return false; - try { - w.send?.(msg as any); - return true; - } catch (err: any) { - if ( - err?.code === 'ERR_IPC_CHANNEL_CLOSED' || - err?.code === 'EPIPE' || - err?.errno === -32 - ) { - return false; - } - throw err; - } -} - -/** Receipt helpers -------------------------------------------------------- */ - -/** - * Find the receipt JSON for a given input file (supports suffixes like __1). - * - * @param receiptsFolder - * @param filePath - */ -function resolveReceiptPath( - receiptsFolder: string, - filePath: string, -): string | null { - const base = `${getFilePrefix(filePath)}-receipts.json`; - const exact = join(receiptsFolder, base); - if (existsSync(exact)) return exact; - - const prefix = `${getFilePrefix(filePath)}-receipts`; - try { - const entries = readdirSync(receiptsFolder) - .filter((n) => n.startsWith(prefix) && n.endsWith('.json')) - .map((name) => { - const full = join(receiptsFolder, name); - let mtime = 0; - try { - mtime = statSync(full).mtimeMs; - } catch {} - return { full, mtime }; - }) - .sort((a, b) => b.mtime - a.mtime); - return entries[0]?.full ?? null; - } catch { - return null; - } -} - /** Totals union types for renderer */ type UploadModeTotals = { mode: 'upload'; @@ -186,7 +112,7 @@ type CheckModeTotals = { type AnyTotals = UploadModeTotals | CheckModeTotals; /** - * Summarize receipt (skipped counted in both modes). + * Summarize a receipts JSON into dashboard counters. * * @param receiptPath * @param dryRun @@ -241,43 +167,11 @@ function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { } } -/** stderr → ERROR/WARN indicator helpers ---------------------------------- */ - -// --- helpers to parse stderr lines and classify warn/error --- -function makeLineSplitter(onLine: (line: string) => void) { - let buf = ''; - return (chunk: Buffer | string) => { - buf += chunk.toString('utf8'); - let nl: number; - // eslint-disable-next-line no-cond-assign - while ((nl = buf.indexOf('\n')) !== -1) { - const line = buf.slice(0, nl); - onLine(line); - buf = buf.slice(nl + 1); - } - }; -} - -/** - * Return 'warn' | 'error' when the line is an *explicit* tagged message we care about. - * Otherwise return null so the dashboard ignores it. - * - * @param line - */ -function classifyLevel(line: string): 'warn' | 'error' | null { - // strip ANSI (very light pass; good enough for our tags) - const noAnsi = line.replace(/\x1B\[[0-9;]*m/g, ''); - // look for our child prefixes: "[wN] ERROR ..." or "[wN] WARN ..." - const m = - /^\s*\[w\d+\]\s+(ERROR|WARN|uncaughtException|unhandledRejection)\b/i.exec( - noAnsi, - ); - if (!m) return null; - const tag = m[1].toLowerCase(); - if (tag === 'warn') return 'warn'; - return 'error'; // ERROR, uncaughtException, unhandledRejection → error +function getCurrentModulePath(): string { + // @ts-ignore - __filename exists in CJS/ts-node + if (typeof __filename !== 'undefined') return __filename as unknown as string; + return process.argv[1]; } -/** Main ------------------------------------------------------------------- */ export async function uploadPreferences( this: LocalContext, @@ -326,6 +220,10 @@ export async function uploadPreferences( ); } + // Throughput metering (records/s and /m) + const meter = new RateCounter(); + let liveSuccessTotal = 0; + const receiptsFolder = computeReceiptsFolder(receiptFileDir, directory); const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); @@ -371,6 +269,8 @@ export async function uploadPreferences( number, ReturnType | undefined >(); + const failingUpdatesMem: FailingUpdateRow[] = []; + const pending = [...files]; const totals = { completed: 0, failed: 0 }; let activeWorkers = 0; @@ -388,57 +288,70 @@ export async function uploadPreferences( let dashboardPaused = false; const repaint = (final = false) => { if (dashboardPaused && !final) return; - renderDashboard( + renderDashboard({ poolSize, cpuCount, - files.length, - totals.completed, - totals.failed, + filesTotal: files.length, + filesCompleted: totals.completed, + filesFailed: totals.failed, workerState, - agg, - { final }, - ); + totals: agg, + final, + throughput: { + successSoFar: liveSuccessTotal, + r10s: meter.rate(10_000), + r60s: meter.rate(60_000), + }, + }); }; - const assignWorkToWorker = (id: number) => { + const assignWorkToSlot = (id: number) => { const w = workers.get(id); if (!isIpcOpen(w)) { + const prev = workerState.get(id); workerState.set(id, { busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: prev?.lastLevel ?? 'ok', // preserve badge (e.g., ERROR after crash) + progress: undefined, }); return; } + const filePath = pending.shift(); if (!filePath) { - const prev = workerState.get(id) || ({} as WorkerState); + const prev = workerState.get(id); workerState.set(id, { - ...prev, busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, }); return; } + workerState.set(id, { busy: true, file: filePath, startedAt: Date.now(), - lastLevel: 'ok' as any, + lastLevel: 'ok', // new task starts clean + progress: undefined, }); + if ( !safeSend(w!, { type: 'task', payload: { filePath, options: common } }) ) { // IPC closed between check and send; requeue and mark idle pending.unshift(filePath); + const prev = workerState.get(id); workerState.set(id, { busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, }); } }; @@ -448,29 +361,37 @@ export async function uploadPreferences( const st = workerState.get(id); if (!st || !st.busy) { if (pending.length === 0) break; - assignWorkToWorker(id); + assignWorkToSlot(id); } } }; // Spawn the pool for (let i = 0; i < poolSize; i += 1) { - const child = spawnWorkerProcess(i, modulePath, LOG_DIR, true, isSilent); + const child = spawnWorkerProcess({ + id: i, + modulePath, + logDir: LOG_DIR, + openLogWindows: true, + isSilent, + childFlag: CHILD_FLAG, + }); workers.set(i, child); workerState.set(i, { busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: 'ok', + progress: undefined, }); slotLogPaths.set(i, getWorkerLogPaths(child)); activeWorkers += 1; - // Surface WARN/ERROR status live from stderr + // Live WARN/ERROR status from stderr const errLine = makeLineSplitter((line) => { - const lvl = classifyLevel(line); - if (!lvl) return; // ignore untagged stderr noise - const prev = workerState.get(i) || ({} as WorkerState); + const lvl = classifyLogLevel(line); + if (!lvl) return; + const prev = workerState.get(i)!; if (prev.lastLevel !== lvl) { workerState.set(i, { ...prev, lastLevel: lvl }); repaint(); @@ -498,6 +419,26 @@ export async function uploadPreferences( return; } + if (msg.type === 'progress') { + const { successDelta, successTotal, fileTotal, filePath } = + msg.payload || {}; + liveSuccessTotal += successDelta || 0; + + // Update that worker’s live progress bar + const prev = workerState.get(i)!; + const processed = successTotal ?? prev.progress?.processed ?? 0; + const total = fileTotal ?? prev.progress?.total ?? 0; + workerState.set(i, { + ...prev, + file: prev.file ?? filePath ?? prev.file, + progress: { processed, total }, + }); + + if (successDelta) meter.add(successDelta); + repaint(); + return; + } + if (msg.type === 'result') { const { ok, filePath, receiptFilepath } = msg.payload || {}; if (ok) totals.completed += 1; @@ -508,6 +449,10 @@ export async function uploadPreferences( resolveReceiptPath(common.receiptsFolder, filePath); if (resolved) { const summary = summarizeReceipt(resolved, common.dryRun); + + failingUpdatesMem.push( + ...readFailingUpdatesFromReceipt(resolved, filePath), + ); if (summary.mode === 'upload' && agg.mode === 'upload') { agg.success += summary.success; agg.skipped += summary.skipped; @@ -524,25 +469,37 @@ export async function uploadPreferences( } } + // Mark idle; keep ERROR badge for failed task until next assignment + const prev = workerState.get(i)!; workerState.set(i, { + ...prev, busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: ok ? 'ok' : 'error', + progress: undefined, }); + refillIdleWorkers(); repaint(); } }); - child.on('exit', () => { + child.on('exit', (code, signal) => { activeWorkers -= 1; + + const prev = workerState.get(i)!; + const abnormal = (typeof code === 'number' && code !== 0) || !!signal; + workerState.set(i, { + ...prev, busy: false, file: null, startedAt: null, - lastLevel: 'ok' as any, + lastLevel: abnormal ? 'error' : prev.lastLevel ?? 'ok', + progress: undefined, }); + repaint(); }); } @@ -602,10 +559,102 @@ export async function uploadPreferences( onEnterAttachScreen: attachScreen, }); + // extra hotkeys for combined views + // inside onKeypressExtra in impl.ts + const onKeypressExtra = (buf: Buffer): void => { + const s = buf.toString('utf8'); + + // existing viewers (now with array-based sources) + if (s === 'e' || s === 'E') { + // Errors: read raw stderr and let the function filter only ERROR lines. + // (If you later add 'errorPath' to WhichLogs, you can include ['error'] too.) + dashboardPaused = true; + showCombinedLogs(slotLogPaths, ['err'], 'error'); + return; + } + if (s === 'w' || s === 'W') { + // Warnings: include the dedicated WARN file (if present) AND stderr + // (lots of libs print WARN on stderr without "ERROR" tags) + dashboardPaused = true; + showCombinedLogs(slotLogPaths, ['warn', 'err'], 'warn'); + return; + } + if (s === 'i' || s === 'I') { + // Info-only: use the classified INFO file + dashboardPaused = true; + showCombinedLogs(slotLogPaths, ['info'], 'all'); + return; + } + if (s === 'l' || s === 'L') { + // All logs: combine stdout, stderr, and your structured app log + dashboardPaused = true; + showCombinedLogs(slotLogPaths, ['out', 'err', 'structured'], 'all'); + return; + } + + // NEW: Shift+E / Shift+W / Shift+I to write combined artifact files + if (s === 'E') { + exportCombinedLogs(slotLogPaths, 'error', LOG_DIR) + .then((p) => + process.stdout.write(`\nWrote combined error logs to: ${p}\n`), + ) + .catch(() => + process.stdout.write('\nFailed to write combined error logs\n'), + ); + return; + } + if (s === 'W') { + exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR) + .then((p) => + process.stdout.write(`\nWrote combined warn logs to: ${p}\n`), + ) + .catch(() => + process.stdout.write('\nFailed to write combined warn logs\n'), + ); + return; + } + if (s === 'I') { + exportCombinedLogs(slotLogPaths, 'info', LOG_DIR) + .then((p) => + process.stdout.write(`\nWrote combined info logs to: ${p}\n`), + ) + .catch(() => + process.stdout.write('\nFailed to write combined info logs\n'), + ); + return; + } + + // NEW: Shift+F to dump failing-updates CSV (from memory) + if (s === 'F') { + const dest = join(LOG_DIR, 'failing-updates.csv'); + writeFailingUpdatesCsv(failingUpdatesMem, dest) + .then((p) => + process.stdout.write(`\nWrote failing updates CSV to: ${p}\n`), + ) + .catch(() => + process.stdout.write('\nFailed to write failing updates CSV\n'), + ); + return; + } + + // Esc / Ctrl+] back to dashboard + if (s === '\x1b' || s === '\x1d') { + dashboardPaused = false; + repaint(); + } + }; + + try { + process.stdin.setRawMode?.(true); + } catch {} + process.stdin.resume(); + process.stdin.on('data', onKeypressExtra); + // hint - const maxDigit = Math.min(poolSize - 1, 9); - const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; - const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + const { poolSize: shownPool } = computePoolSize(concurrency, files.length); + const maxDigit = Math.min(shownPool - 1, 9); + const digitRange = shownPool <= 1 ? '0' : `0-${maxDigit}`; + const extra = shownPool > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; process.stdout.write( colors.dim( `\nHotkeys: [${digitRange}] attach${extra} • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit\n\n`, @@ -614,7 +663,7 @@ export async function uploadPreferences( // wait for completion of work (not viewer) await new Promise((resolveWork) => { - const check = setInterval(() => { + const check = setInterval(async () => { if (pending.length === 0) { for (const [id, w] of workers) { const st = workerState.get(id); @@ -629,18 +678,31 @@ export async function uploadPreferences( repaint(true); + // right after repaint(true) in the "work complete" block + try { + const e = await exportCombinedLogs(slotLogPaths, 'error', LOG_DIR); + const w = await exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR); + const i = await exportCombinedLogs(slotLogPaths, 'info', LOG_DIR); + const fPath = join(LOG_DIR, 'failing-updates.csv'); + await writeFailingUpdatesCsv(failingUpdatesMem, fPath); + process.stdout.write( + `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${fPath}\n\n`, + ); + } catch {} + if ((agg as AnyTotals).mode === 'upload') { const a = agg as UploadModeTotals; process.stdout.write( colors.green( - `\nAll done. Success:${a.success} Skipped:${a.skipped} Error:${a.error}\n`, + `\nAll done. Success:${a.success.toLocaleString()} Skipped:${a.skipped.toLocaleString()} Error:${a.error.toLocaleString()}\n`, ), ); } else { const a = agg as CheckModeTotals; process.stdout.write( colors.green( - `\nAll done. Pending:${a.totalPending} PendingConflicts:${a.pendingConflicts} PendingSafe:${a.pendingSafe} Skipped:${a.skipped}\n`, + `\nAll done. Pending:${a.totalPending.toLocaleString()} PendingConflicts:${a.pendingConflicts.toLocaleString()} ` + + `PendingSafe:${a.pendingSafe.toLocaleString()} Skipped:${a.skipped.toLocaleString()}\n`, ), ); } diff --git a/src/commands/consent/upload-preferences/receiptsState.ts b/src/commands/consent/upload-preferences/receiptsState.ts index 46f56252..6998d44e 100644 --- a/src/commands/consent/upload-preferences/receiptsState.ts +++ b/src/commands/consent/upload-preferences/receiptsState.ts @@ -57,40 +57,48 @@ export type PreferenceReceiptsInterface = { export function makeReceiptsState( filepath: string, ): PreferenceReceiptsInterface { - const s = new PersistedState(filepath, RequestUploadReceipts, { - failingUpdates: {}, - pendingConflictUpdates: {}, - skippedUpdates: {}, - pendingSafeUpdates: {}, - successfulUpdates: {}, - pendingUpdates: {}, - lastFetchedAt: new Date().toISOString(), - }); + try { + const s = new PersistedState(filepath, RequestUploadReceipts, { + failingUpdates: {}, + pendingConflictUpdates: {}, + skippedUpdates: {}, + pendingSafeUpdates: {}, + successfulUpdates: {}, + pendingUpdates: {}, + lastFetchedAt: new Date().toISOString(), + }); - return { - receiptsFilepath: filepath, - getSuccessful: () => s.getValue('successfulUpdates'), - getPending: () => s.getValue('pendingUpdates'), - getFailing: () => s.getValue('failingUpdates'), - async setSuccessful(v: PreferenceUpdateMap) { - await s.setValue(v, 'successfulUpdates'); - }, - async setPending(v: PreferenceUpdateMap) { - await s.setValue(v, 'pendingUpdates'); - }, - async setPendingSafe(v: PendingSafePreferenceUpdates) { - await s.setValue(v, 'pendingSafeUpdates'); - }, - async setPendingConflict(v: PendingWithConflictPreferenceUpdates) { - await s.setValue(v, 'pendingConflictUpdates'); - }, - async setFailing(v: FailingPreferenceUpdates) { - await s.setValue(v, 'failingUpdates'); - }, - async resetPending() { - await s.setValue({}, 'pendingUpdates'); - await s.setValue({}, 'pendingSafeUpdates'); - await s.setValue({}, 'pendingConflictUpdates'); - }, - }; + return { + receiptsFilepath: filepath, + getSuccessful: () => s.getValue('successfulUpdates'), + getPending: () => s.getValue('pendingUpdates'), + getFailing: () => s.getValue('failingUpdates'), + async setSuccessful(v: PreferenceUpdateMap) { + await s.setValue(v, 'successfulUpdates'); + }, + async setPending(v: PreferenceUpdateMap) { + await s.setValue(v, 'pendingUpdates'); + }, + async setPendingSafe(v: PendingSafePreferenceUpdates) { + await s.setValue(v, 'pendingSafeUpdates'); + }, + async setPendingConflict(v: PendingWithConflictPreferenceUpdates) { + await s.setValue(v, 'pendingConflictUpdates'); + }, + async setFailing(v: FailingPreferenceUpdates) { + await s.setValue(v, 'failingUpdates'); + }, + async resetPending() { + await s.setValue({}, 'pendingUpdates'); + await s.setValue({}, 'pendingSafeUpdates'); + await s.setValue({}, 'pendingConflictUpdates'); + }, + }; + } catch (error) { + throw new Error( + `Failed to create receipts state for ${filepath}: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } } diff --git a/src/commands/consent/upload-preferences/resolveReceiptPath.ts b/src/commands/consent/upload-preferences/resolveReceiptPath.ts new file mode 100644 index 00000000..3267468f --- /dev/null +++ b/src/commands/consent/upload-preferences/resolveReceiptPath.ts @@ -0,0 +1,39 @@ +import { join } from 'node:path'; +import { getFilePrefix } from './computeFiles'; +import { existsSync, readdirSync, statSync } from 'node:fs'; + +/** + * Find the receipt JSON for a given input file (supports suffixes like __1). + * + * @param receiptsFolder - Where to look for receipts + * @param filePath - The input file path to match against + * @returns The path to the receipt file, or null if not found + */ +export function resolveReceiptPath( + receiptsFolder: string, + filePath: string, +): string | null { + const base = `${getFilePrefix(filePath)}-receipts.json`; + const exact = join(receiptsFolder, base); + if (existsSync(exact)) return exact; + + const prefix = `${getFilePrefix(filePath)}-receipts`; + try { + const entries = readdirSync(receiptsFolder) + .filter((n) => n.startsWith(prefix) && n.endsWith('.json')) + .map((name) => { + const full = join(receiptsFolder, name); + let mtime = 0; + try { + mtime = statSync(full).mtimeMs; + } catch { + // ignore if stat fails + } + return { full, mtime }; + }) + .sort((a, b) => b.mtime - a.mtime); + return entries[0]?.full ?? null; + } catch { + return null; + } +} diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 999f992d..c12e2aff 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -51,7 +51,7 @@ export async function runChild(): Promise { // Construct common options const receipts = makeReceiptsState(receiptFilepath); - const schema = makeSchemaState(options.schemaFile); + const schema = await makeSchemaState(options.schemaFile); const client = buildTranscendGraphQLClient( options.transcendUrl, options.auth, @@ -92,6 +92,18 @@ export async function runChild(): Promise { maxChunkSize: options.maxChunkSize, uploadConcurrency: options.uploadConcurrency, maxRecordsToReceipt: options.maxRecordsToReceipt, + onProgress: ({ successDelta, successTotal, fileTotal }) => { + // Emit progress messages up to the parent + process.send?.({ + type: 'progress', + payload: { + filePath, + successDelta, + successTotal, + fileTotal, + }, + }); + }, }); logger.info(`[w${workerId}] DONE ${filePath}`); diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts index 2724f63f..f178fc8d 100644 --- a/src/commands/consent/upload-preferences/schemaState.ts +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -2,7 +2,9 @@ * Module: state/schemaState * * Thin wrapper over PersistedState(FileFormatState) for schema/config - * discovered during CSV parsing. + * discovered during CSV parsing. Includes exponential backoff on transient + * JSON parse errors ("Unexpected end of JSON input") which can occur if a + * schema cache file is being written by another process. */ import { PersistedState } from '@transcend-io/persisted-state'; import { @@ -10,6 +12,10 @@ import { type ColumnIdentifierMap, type ColumnPurposeMap, } from '../../../lib/preference-management'; +import { + retrySamePromise, + type RetryPolicy, +} from '../../../lib/helpers/retrySamePromise'; export interface PreferenceSchemaInterface { /** Name of the column used as timestamp, if any */ @@ -25,22 +31,67 @@ export interface PreferenceSchemaInterface { /** * Build a schema state adapter holding CSV→purpose/identifier mappings. * + * Retries creation of the underlying PersistedState with **exponential backoff** + * when the cache file cannot be parsed due to a transient write (e.g., empty or + * partially written file) indicated by "Unexpected end of JSON input". + * * @param filepath - Path to the schema cache file * @returns Schema state port with strongly-named methods */ -export function makeSchemaState(filepath: string): PreferenceSchemaInterface { - const s = new PersistedState(filepath, FileFormatState, { +export async function makeSchemaState( + filepath: string, +): Promise { + // Initial state used if file does not exist or is empty. + const initial = { columnToPurposeName: {}, lastFetchedAt: new Date().toISOString(), columnToIdentifier: {}, - }); + } as const; - return { - state: s, - getTimestampColumn: (): string | undefined => s.getValue('timestampColumn'), - getColumnToPurposeName: (): ColumnPurposeMap => - s.getValue('columnToPurposeName'), - getColumnToIdentifier: (): ColumnIdentifierMap => - s.getValue('columnToIdentifier'), + // Retry policy: only retry on the specific JSON truncation message. + const policy: RetryPolicy = { + maxAttempts: 5, + delayMs: 50, // start small + shouldRetry: (_status, message) => + typeof message === 'string' && + /Unexpected end of JSON input/i.test(message ?? ''), }; + + // Exponential backoff with a reasonable cap. + const MAX_DELAY_MS = 2_000; + + try { + const state = await retrySamePromise( + () => + // Wrap constructor in a Promise so thrown sync errors reject properly. + Promise.resolve(new PersistedState(filepath, FileFormatState, initial)), + policy, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (note) => { + // Double the delay on each backoff (cap at MAX_DELAY_MS) + policy.delayMs = Math.min( + MAX_DELAY_MS, + Math.max(1, policy.delayMs * 2), + ); + // Optional: uncomment for local diagnostics + // process.stderr.write(`[schemaState] ${note}; next delay=${policy.delayMs}ms\n`); + }, + ); + + return { + state, + getTimestampColumn: (): string | undefined => + state.getValue('timestampColumn'), + getColumnToPurposeName: (): ColumnPurposeMap => + state.getValue('columnToPurposeName'), + getColumnToIdentifier: (): ColumnIdentifierMap => + state.getValue('columnToIdentifier'), + }; + } catch (err) { + throw new Error( + `Failed to create schema state from ${filepath}: ${ + err instanceof Error ? err.message : String(err) + }`, + ); + } } diff --git a/src/commands/consent/upload-preferences/upload/batchUploader.ts b/src/commands/consent/upload-preferences/upload/batchUploader.ts index 50c22a05..77a68442 100644 --- a/src/commands/consent/upload-preferences/upload/batchUploader.ts +++ b/src/commands/consent/upload-preferences/upload/batchUploader.ts @@ -116,8 +116,10 @@ export async function uploadChunkWithSplit( const [left, right] = splitInHalf(entries); logger.warn( - `Non-retryable error for batch of ${entries.length} (status=${status}): ${msg}. ` + - `Splitting into ${left.length} and ${right.length}.`, + colors.yellow( + `Non-retryable error for batch of ${entries.length} (status=${status}): ${msg}. ` + + `Splitting into ${left.length} and ${right.length}.`, + ), ); await uploadChunkWithSplit(left, deps, callbacks); diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 7ef7e028..e90f117c 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -42,6 +42,7 @@ export async function interactivePreferenceUploaderFromPlan( maxChunkSize = 50, uploadConcurrency = 20, maxRecordsToReceipt = 50, + onProgress, }: { /** Receipts interface */ receipts: PreferenceReceiptsInterface; @@ -65,6 +66,15 @@ export async function interactivePreferenceUploaderFromPlan( uploadConcurrency?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; + /* existing options ... */ + onProgress?: (info: { + /** how many records just succeeded */ + successDelta: number; + /** cumulative successes in this file */ + successTotal: number; + /** total records that will be uploaded in this file */ + fileTotal: number; + }) => void; }, ): Promise { // Build final payloads (pure transform; no network) @@ -116,6 +126,12 @@ export async function interactivePreferenceUploaderFromPlan( [string, PreferenceUpdateItem] >; const filtered = allEntries.filter(([userId]) => !successful[userId]); + const fileTotal = filtered.length; + onProgress?.({ + successDelta: 0, + successTotal: uploadedCount, + fileTotal, + }); if (filtered.length === 0) { logger.warn( @@ -126,6 +142,7 @@ export async function interactivePreferenceUploaderFromPlan( await receipts.resetPending(); return; } + if (filtered.length < allEntries.length) { logger.warn( colors.yellow( @@ -164,6 +181,11 @@ export async function interactivePreferenceUploaderFromPlan( delete (pendingConflictUpdates as any)[userId]; } uploadedCount += entries.length; + onProgress?.({ + successDelta: entries.length, + successTotal: uploadedCount, + fileTotal, + }); const shouldLog = uploadedCount % uploadLogInterval === 0 || @@ -201,11 +223,12 @@ export async function interactivePreferenceUploaderFromPlan( update: PreferenceUpdateItem, err: unknown, ): Promise => { - let msg = extractErrorMessage(err); - if (msg.includes('Too many identifiers')) { - // Add first identifier clue to speed up triage - msg += `\n ----> ${userId.split('___')[0]}`; - } + const msg = extractErrorMessage(err); + // FIXME + // if (msg.includes('Too many identifiers')) { + // // Add first identifier clue to speed up triage + // msg += `\n ----> ${userId.split('___')[0]}`; + // } logger.error( colors.red( `Failed to upload preferences for ${userId} (partition=${partition}): ${msg}`, diff --git a/src/lib/helpers/RateCounter.ts b/src/lib/helpers/RateCounter.ts new file mode 100644 index 00000000..7919e977 --- /dev/null +++ b/src/lib/helpers/RateCounter.ts @@ -0,0 +1,55 @@ +/** + * Tracks counts over time and calculates rates within a specified time window. + * + * This class maintains a rolling window of count "buckets" (timestamped values) + * and provides methods to add new counts and compute the rate of events over a + * configurable time window. + * + * Example usage: + * ```typescript + * const counter = new RateCounter(); + * counter.add(5); // Add 5 events + * const rate = counter.rate(60_000); // Get rate over last 60 seconds + * ``` + */ +export class RateCounter { + private buckets: Array<{ + /** Timestamp of the bucket */ + t: number; + /** Number of events in the bucket */ + n: number; + }> = []; + + /** + * Adds a new count to the counter. + * + * @param n - The number of events to add to the counter. + */ + add(n: number): void { + const now = Date.now(); + this.buckets.push({ t: now, n }); + // keep last 2 minutes of buckets + const cutoff = now - 120_000; + while (this.buckets.length && this.buckets[0].t < cutoff) { + this.buckets.shift(); + } + } + + /** + * rate over the last `windowMs` milliseconds + * + * @param windowMs - The time window in milliseconds to calculate the rate over. + * @returns The average rate of events per second over the specified time window. + */ + rate(windowMs: number): number { + const now = Date.now(); + const cutoff = now - windowMs; + let sum = 0; + for (let i = this.buckets.length - 1; i >= 0; i -= 1) { + const b = this.buckets[i]; + if (b.t < cutoff) break; + sum += b.n; + } + return sum / (windowMs / 1000); + } +} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index 71adceca..2c652a04 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -8,3 +8,4 @@ export * from './sleepPromise'; export * from './splitInHalf'; export * from './retrySamePromise'; export * from './limitRecords'; +export * from './RateCounter'; diff --git a/src/lib/pooling/assignWorkToWorker.ts b/src/lib/pooling/assignWorkToWorker.ts index 9ea20052..86d28bee 100644 --- a/src/lib/pooling/assignWorkToWorker.ts +++ b/src/lib/pooling/assignWorkToWorker.ts @@ -1,22 +1,39 @@ import type { ChildProcess } from 'node:child_process'; +import { isIpcOpen } from './spawnWorkerProcess'; export interface WorkerState { - /** Indicates if the worker is currently processing a task. */ + /** True when the worker is executing a task */ busy: boolean; - /** The file path currently being processed by the worker, or null if idle. */ - file?: string | null; - /** Timestamp when the worker started processing the current task, or null if idle. */ - startedAt?: number | null; - /** last severity seen from worker stderr */ - lastLevel?: 'ok' | 'warn' | 'error'; + /** Absolute file path currently being processed (if any) */ + file: string | null; + /** Start timestamp (ms) when the current task began */ + startedAt: number | null; + /** + * Last log level observed on stderr. + * 'ok' = normal, 'warn' = warning, 'error' = error + */ + lastLevel: 'ok' | 'warn' | 'error'; + + /** + * Live progress reported by the child process. + * `total` is the total number of records for the current file. + * `processed` is the cumulative number processed so far. + */ + progress?: { + /** Cumulative items processed so far for this file */ + processed: number; + /** Total number of items to process for this file */ + total: number; + }; } /** - * Assigns a pending work item to a specified worker process. + * Assign a pending work item to a specified worker process. * - * This function checks if the worker is available and connected, then assigns the next file path from the pending queue. - * It updates the worker's state, triggers a UI repaint, and sends the task to the worker via IPC. - * If no work is left, it marks the worker as idle. + * Checks if the worker is available and connected, then assigns the next file from the + * pending queue. Updates the worker's state (including `lastLevel` and `progress`), + * triggers a UI repaint, and sends the task to the worker via IPC. If no work is left, + * marks the worker as idle. If IPC is closed during send, requeues the work and idles. * * @param id - The worker's unique identifier. * @param payload - Common payload/options to send with each task. @@ -41,35 +58,72 @@ export function assignWorkToWorker( repaint: () => void; }, ): void { - // Grab the worker by ID const w = workers.get(id); + const prev = workerState.get(id); - // Check if worker is running - if (!w || !w.connected || !w.channel) { - // mark slot idle and skip (slot is kept for post-run log viewing) - workerState.set(id, { busy: false, file: null, startedAt: null }); + // If worker IPC isn't open, mark idle (keep lastLevel so badge persists) + if (!isIpcOpen(w)) { + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); return; } - // Grab a file that needs to be processed + // Take next file const filePath = pending.shift(); if (!filePath) { - // No work left; mark idle - workerState.set(id, { busy: false, file: null, startedAt: null }); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); repaint(); return; } - // Update dashboard state - workerState.set(id, { busy: true, file: filePath, startedAt: Date.now() }); + // Mark working (child will later send progress updates) + workerState.set(id, { + busy: true, + file: filePath, + startedAt: Date.now(), + lastLevel: 'ok', // becomes OK when a fresh task starts + progress: undefined, + }); repaint(); - // Send work item over IPC - w.send?.({ - type: 'task', - payload: { - filePath, - options: payload, - }, - }); + // Send task + try { + w!.send?.({ + type: 'task', + payload: { + filePath, + options: payload, + }, + }); + } catch (err: any) { + // If the pipe closed between the check and send: requeue + idle + if ( + err?.code === 'ERR_IPC_CHANNEL_CLOSED' || + err?.code === 'EPIPE' || + err?.errno === -32 + ) { + pending.unshift(filePath); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); + repaint(); + return; + } + throw err; + } } diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts index 5b633411..9dcca3d4 100644 --- a/src/lib/pooling/attachWorkerHandlers.ts +++ b/src/lib/pooling/attachWorkerHandlers.ts @@ -3,49 +3,74 @@ import { assignWorkToWorker, type WorkerState } from './assignWorkToWorker'; import { appendFileSync } from 'fs'; import { join } from 'path'; import { spawnWorkerProcess } from './spawnWorkerProcess'; +import { classifyLogLevel, makeLineSplitter } from './logRotation'; +/** Result message from child → parent */ export interface WorkerResultMessage { - /** Message type indicating a result */ + /** */ type: 'result'; - /** Result payload containing status and file info */ + /** */ payload: { - /** Whether the operation was successful */ - ok: boolean /** */; - /** Path to the processed file */ - filePath: string /** */; - /** Optional error message if operation failed */ + /** */ + ok: boolean; + /** */ + filePath: string; + /** */ error?: string; + /** Optional receipts path, if child provided it */ + receiptFilepath?: string; }; } +/** Ready sentinel from child → parent */ export interface WorkerReadyMessage { - /** Message type indicating worker is ready */ + /** */ type: 'ready'; } -/** - * Union type for worker messages - */ -export type WorkerMessage = WorkerResultMessage | WorkerReadyMessage; +/** Live progress from child → parent (for per-worker bars & throughput) */ +export interface WorkerProgressMessage { + /** */ + type: 'progress'; + /** */ + payload: { + /** */ + filePath: string; + /** how many just succeeded in this tick */ + successDelta: number; + /** cumulative successes for the current file */ + successTotal: number; + /** total planned records for the current file */ + fileTotal: number; + }; +} + +/** Union of all worker IPC messages */ +export type WorkerMessage = + | WorkerResultMessage + | WorkerReadyMessage + | WorkerProgressMessage; /** * Wire up a worker's lifecycle: * - on "ready": hand it work - * - on "result": update counters, queue next work + * - on "progress": update per-worker progress (and optional aggregator) + * - on "result": update counters, clear progress, queue next work * - on "exit": optionally respawn if there is still work pending * * @param id - the worker ID * @param child - the child process - * @param workers - the map of all worker processes - * @param workerState - the map of worker states - * @param state - the overall state - * @param filesPending - the list of files pending processing - * @param repaint - the function to call to repaint the dashboard - * @param common - the common task options - * @param onAllWorkersExited - the function to call when all workers have exited - * @param logDir - the directory for log files - * @param modulePath - the path to the current module (for spawning workers) - * @param spawnSilent - whether to spawn workers silently + * @param workers - map of all worker processes + * @param workerState - map of worker visual state + * @param state - global counters (completed/failed files) + * @param filesPending - FIFO of files yet to process + * @param repaint - re-render the dashboard + * @param common - common task options to send with each assignment + * @param onAllWorkersExited - callback when pool is fully drained/exited + * @param logDir - directory to write shared failure logs + * @param modulePath - module path for spawning worker processes + * @param spawnSilent - whether to spawn workers with silent stdio + * @param onProgress - optional aggregator for throughput metrics */ export function attachWorkerHandlers( id: number, @@ -53,9 +78,9 @@ export function attachWorkerHandlers( workers: Map, workerState: Map, state: { - /** Number of completed files */ + /** */ completed: number; - /** Number of failed files */ + /** */ failed: number; }, filesPending: string[], @@ -65,35 +90,107 @@ export function attachWorkerHandlers( logDir: string, modulePath: string, spawnSilent = false, + onProgress?: (info: { + /** */ + workerId: number; + /** */ + filePath: string; + /** */ + successDelta: number; + /** */ + successTotal: number; + /** */ + fileTotal: number; + }) => void, ): void { workers.set(id, child); - workerState.set(id, { busy: false, file: null, startedAt: null }); + const prev = workerState.get(id); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', // preserve previous badge (e.g., ERROR after crash) + progress: undefined, + }); - // Handle IPC messages from the child + // LIVE: watch stderr to flip WARN/ERROR badges + if (child.stderr) { + const onErrLine = makeLineSplitter((line) => { + // If there is an explicit tag, use it; otherwise since it came from stderr, treat as WARN. + const explicit = classifyLogLevel(line); // returns 'warn' | 'error' | null + const lvl = explicit ?? 'warn'; + const prev = workerState.get(id)!; + if (prev.lastLevel !== lvl) { + workerState.set(id, { ...prev, lastLevel: lvl }); + repaint(); + } + }); + child.stderr.on('data', onErrLine); + } + + // IPC messages from child child.on('message', (msg: WorkerMessage) => { if (!msg || typeof msg !== 'object') return; if (msg.type === 'ready') { - // First work assignment assignWorkToWorker(id, common, { pending: filesPending, workers, workerState, repaint, }); - } else if (msg.type === 'result') { - // Update success/failure counters + return; + } + + if (msg.type === 'progress') { + const { filePath, successDelta, successTotal, fileTotal } = msg.payload; + + // Update per-worker progress bar + const current = workerState.get(id)!; + workerState.set(id, { + ...current, + file: current.file ?? filePath, + progress: { + processed: successTotal ?? current.progress?.processed ?? 0, + total: fileTotal ?? current.progress?.total ?? 0, + }, + }); + + // Bubble to an optional global aggregator (for throughput) + if (onProgress && successDelta) { + onProgress({ + workerId: id, + filePath, + successDelta, + successTotal, + fileTotal, + }); + } + + repaint(); + return; + } + + if (msg.type === 'result') { const { ok, filePath, error } = msg.payload; - // eslint-disable-next-line no-param-reassign + + // Update file-level counters if (ok) state.completed += 1; - // eslint-disable-next-line no-param-reassign else state.failed += 1; - // Mark idle on completion - workerState.set(id, { busy: false, file: null, startedAt: null }); + // Mark idle & clear transient progress; keep ERROR badge if the task failed + const current = workerState.get(id)!; + workerState.set(id, { + ...current, + busy: false, + file: null, + startedAt: null, + lastLevel: ok ? 'ok' : 'error', + progress: undefined, + }); repaint(); - // Append failure details (if any) to a shared log + // If failure, append to a shared log for triage if (!ok && error) { appendFileSync( join(logDir, 'failures.log'), @@ -111,20 +208,29 @@ export function attachWorkerHandlers( } }); - // Handle worker termination/crash child.on('exit', (code, signal) => { workers.delete(id); + // Mark the slot visibly as ERROR (crashed) and keep it until a new task is assigned + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'error', + progress: undefined, + }); + repaint(); + // If it crashed and there's still work to do, respawn a replacement if ((code && code !== 0) || signal) { if (filesPending.length > 0) { - const replacement = spawnWorkerProcess( + const replacement = spawnWorkerProcess({ id, modulePath, logDir, - true, // attempt to open tail windows for respawns too - spawnSilent, - ); + openLogWindows: true, // attempt to open tail windows for respawns too + isSilent: spawnSilent, + }); attachWorkerHandlers( id, replacement, @@ -137,6 +243,8 @@ export function attachWorkerHandlers( onAllWorkersExited, logDir, modulePath, + spawnSilent, + onProgress, ); return; } diff --git a/src/lib/pooling/downloadArtifact.ts b/src/lib/pooling/downloadArtifact.ts new file mode 100644 index 00000000..7d0ce881 --- /dev/null +++ b/src/lib/pooling/downloadArtifact.ts @@ -0,0 +1,156 @@ +// lib/pooling/downloadArtifacts.ts +import { + createReadStream, + createWriteStream, + statSync, + readFileSync, +} from 'node:fs'; +import { join, basename } from 'node:path'; +import { once } from 'node:events'; +import type { getWorkerLogPaths } from './spawnWorkerProcess'; + +/** + * + */ +export type LogKind = 'error' | 'warn' | 'info'; + +/** + * + */ +type SlotPaths = Map | undefined>; + +/** + * Combine all worker logs of the given kind into a single file. + * Writes simple headers per worker and concatenates in worker-id order. + * + * @param slotLogPaths - Map of workerId -> WorkerLogPaths + * @param kind - 'error' | 'warn' | 'info' + * @param outDir - directory to write the combined file + * @param filename - optional override (default: combined-{kind}.log) + * @returns absolute path to the combined log file + */ +export async function exportCombinedLogs( + slotLogPaths: SlotPaths, + kind: LogKind, + outDir: string, + filename?: string, +): Promise { + const outPath = join(outDir, filename ?? `combined-${kind}.log`); + const out = createWriteStream(outPath, { flags: 'w' }); + + const pick = (p: NonNullable>) => + kind === 'error' ? p.errorPath : kind === 'warn' ? p.warnPath : p.infoPath; + + for (const [id, paths] of [...slotLogPaths.entries()].sort( + (a, b) => a[0] - b[0], + )) { + if (!paths) continue; + const srcPath = pick(paths); + try { + // header for context + out.write(`\n==== worker ${id} (${basename(srcPath)}) ====\n`); + const st = statSync(srcPath); + if (st.size === 0) { + out.write('[empty]\n'); + continue; + } + const rs = createReadStream(srcPath, { encoding: 'utf8' }); + rs.pipe(out, { end: false }); + await once(rs, 'end'); + } catch { + // skip unreadable files + out.write(`[unavailable: ${srcPath}]\n`); + } + } + + await new Promise((r) => out.end(r)); + return outPath; +} + +/** A single failing update row we will output to CSV */ +export interface FailingUpdateRow { + /** The primary key / userId from receipts map key */ + primaryKey: string; + /** When the upload attempt happened (ISO string) */ + uploadedAt: string; + /** Error message */ + error: string; + /** JSON-encoded "update" body (compact) */ + updateJson: string; + /** Optional source file the row came from (helps triage) */ + sourceFile?: string; +} + +/** + * naive CSV escape: double any quotes and wrap with quotes if needed + * + * @param v + */ +function csvCell(v: string): string { + if (v == null) return ''; + const needs = /[",\n]/.test(v); + const s = String(v).replace(/"/g, '""'); + return needs ? `"${s}"` : s; +} + +/** + * Stream an array of failing updates to a CSV file. + * This overwrites any existing file at destPath. + * + * Columns: primaryKey, uploadedAt, error, updateJson, sourceFile + * + * @param rows + * @param destPath + */ +export async function writeFailingUpdatesCsv( + rows: FailingUpdateRow[], + destPath: string, +): Promise { + const ws = createWriteStream(destPath, { flags: 'w' }); + // header + ws.write('primaryKey,uploadedAt,error,updateJson,sourceFile\n'); + for (const r of rows) { + ws.write( + `${[ + csvCell(r.primaryKey), + csvCell(r.uploadedAt), + csvCell(r.error), + csvCell(r.updateJson), + csvCell(r.sourceFile ?? ''), + ].join(',')}\n`, + ); + } + await new Promise((r) => ws.end(r)); + return destPath; +} + +/** + * Convenience helper to parse failing updates out of a receipts.json file. + * Returns rows you can merge into your in-memory buffer. + * + * @param receiptPath + * @param sourceFile + */ +export function readFailingUpdatesFromReceipt( + receiptPath: string, + sourceFile?: string, +): FailingUpdateRow[] { + try { + const raw = readFileSync(receiptPath, 'utf8'); + const json = JSON.parse(raw) as any; + const failing = json?.failingUpdates ?? {}; + const out: FailingUpdateRow[] = []; + for (const [primaryKey, val] of Object.entries(failing)) { + out.push({ + primaryKey, + uploadedAt: val?.uploadedAt ?? '', + error: val?.error ?? '', + updateJson: val?.update ? JSON.stringify(val.update) : '', + sourceFile, + }); + } + return out; + } catch { + return []; + } +} diff --git a/src/lib/pooling/index.ts b/src/lib/pooling/index.ts index ac47dedb..a736ea82 100644 --- a/src/lib/pooling/index.ts +++ b/src/lib/pooling/index.ts @@ -4,3 +4,4 @@ export * from './renderDashboard'; export * from './ensureLogFile'; export * from './spawnWorkerProcess'; export * from './attachWorkerHandlers'; +export * from './showCombinedLogs'; diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts index 04f205f6..aad9b3aa 100644 --- a/src/lib/pooling/logRotation.ts +++ b/src/lib/pooling/logRotation.ts @@ -1,8 +1,7 @@ // logRotation.ts -import { readdirSync, statSync, truncateSync, rmSync } from 'node:fs'; +import { readdirSync, writeFileSync, existsSync, unlinkSync } from 'node:fs'; import { join } from 'node:path'; - -const WORKER_LOG_RE = /^worker-\d+\.(?:log|out\.log|err\.log)$/; +import colors from 'colors'; /** * Reset worker logs in the given directory. @@ -10,30 +9,111 @@ const WORKER_LOG_RE = /^worker-\d+\.(?:log|out\.log|err\.log)$/; * - "truncate": empty files but keep them (best if tails are open) * - "delete": remove files entirely (simplest if no tails yet) * - * @param logDir - * @param mode + * @param dir - Directory to reset logs in + * @param mode - 'truncate' or 'delete' */ export function resetWorkerLogs( - logDir: string, - mode: 'truncate' | 'delete' = 'truncate', + dir: string, + mode: 'truncate' | 'delete', ): void { - let entries: string[] = []; + const patterns = [ + /worker-\d+\.log$/, + /worker-\d+\.out\.log$/, + /worker-\d+\.err\.log$/, + /worker-\d+\.warn\.log$/, + /worker-\d+\.info\.log$/, + ]; + for (const name of readdirSync(dir)) { + // eslint-disable-next-line no-continue + if (!patterns.some((rx) => rx.test(name))) continue; + const p = join(dir, name); + try { + if (mode === 'delete' && existsSync(p)) unlinkSync(p); + else writeFileSync(p, ''); + } catch { + /* ignore */ + } + } + process.stdout.write( + colors.dim( + `Logs have been ${ + mode === 'delete' ? 'deleted' : 'truncated' + } in ${dir}\n`, + ), + ); +} + +/** + * Very robust classification of a single log line into warn/error. + * Returns 'warn' | 'error' | null (null = not a level we care to badge). + * + * @param line - Single line of log output to classify + * @returns 'warn' | 'error' | null + */ +export function classifyLogLevel(line: string): 'warn' | 'error' | null { + // Strip common ANSI sequences + // eslint-disable-next-line no-control-regex + const s = line.replace(/\x1B\[[0-9;]*m/g, ''); + + // 1) Explicit worker tag: "[w12] WARN ..." or "[w2] ERROR ..." + const mTag = /\[w\d+\]\s+(ERROR|WARN)\b/i.exec(s); + if (mTag) return mTag[1].toLowerCase() as 'warn' | 'error'; + + // 2) Common plain prefixes + if (/^\s*(ERROR|ERR|FATAL)\b/i.test(s)) return 'error'; + if (/^\s*(WARN|WARNING)\b/.test(s)) return 'warn'; + + // Node runtime warnings + if (/^\s*\(node:\d+\)\s*Warning:/i.test(s)) return 'warn'; + if (/^\s*DeprecationWarning:/i.test(s)) return 'warn'; + + // 3) JSON logs (pino/bunyan/etc.) + // Try to parse as JSON and inspect `level` try { - entries = readdirSync(logDir); + const j = JSON.parse(s); + const lv = j?.level; + if (typeof lv === 'number') { + // pino levels: 40=warn, 50=error, 60=fatal + if (lv >= 50) return 'error'; + if (lv >= 40) return 'warn'; + } else if (typeof lv === 'string') { + const L = lv.toLowerCase(); + if (L === 'error' || L === 'fatal') return 'error'; + if (L === 'warn' || L === 'warning') return 'warn'; + } } catch { - return; + // not JSON, ignore } - for (const name of entries) { - if (!WORKER_LOG_RE.test(name)) continue; - const p = join(logDir, name); - try { - const st = statSync(p); - if (!st.isFile()) continue; - if (mode === 'truncate') truncateSync(p, 0); - else rmSync(p, { force: true }); - } catch { - // ignore individual failures - } + // 4) Fallthrough: look for level words inside worker-tagged lines + // e.g. "[w3] something WARNING xyz" + const mInline = /\[w\d+\].*\b(WARN|WARNING|ERROR|FATAL)\b/i.exec(s); + if (mInline) { + const L = mInline[1].toUpperCase(); + return L === 'ERROR' || L === 'FATAL' ? 'error' : 'warn'; } + + return null; +} + +/** + * Stream splitter to get whole lines from 'data' events + * + * @param onLine - Callback to call with each complete line + * @returns A function that processes a chunk of data and calls onLine for each complete line + */ +export function makeLineSplitter( + onLine: (line: string) => void, +): (chunk: Buffer | string) => void { + let buf = ''; + return (chunk: Buffer | string) => { + buf += chunk.toString('utf8'); + let nl: number; + // eslint-disable-next-line no-cond-assign + while ((nl = buf.indexOf('\n')) !== -1) { + const line = buf.slice(0, nl); + onLine(line); + buf = buf.slice(nl + 1); + } + }; } diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index f0562f3a..052fcb3e 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -1,151 +1,286 @@ -// renderer.ts import { basename } from 'node:path'; +import colors from 'colors'; import * as readline from 'node:readline'; import type { WorkerState } from './assignWorkToWorker'; -let lastFrame = ''; - -/** - * - */ -type UploadModeTotals = { - /** */ +/** Upload-mode totals (final-ish counters derived from receipts) */ +export type UploadModeTotals = { + /** Mode for uploading files --dryRun=false */ mode: 'upload'; - /** */ + /** Total number of successfully uploaded records */ success: number; - /** */ + /** Total number of skipped records */ skipped: number; - /** */ + /** Total number of error records */ error: number; /** aggregated error message -> count */ errors: Record; }; -/** - * - */ -type CheckModeTotals = { - /** */ + +/** Check-mode totals (dry-run counters) */ +export type CheckModeTotals = { + /** Mode for checking files --dryRun=true */ mode: 'check'; - /** */ + /** Total number of pending conflict records */ pendingConflicts: number; - /** */ + /** Total number of pending safe records */ pendingSafe: number; - /** */ + /** Total number of pending records to update */ totalPending: number; - /** */ + /** Total number of skipped records */ skipped: number; }; -/** - * - */ -type AnyTotals = UploadModeTotals | CheckModeTotals; -/** - * - */ -type RenderOpts = { - /** */ final?: boolean; -}; -// minimal color helpers (avoid extra deps) -const red = (s: string) => `\x1b[31m${s}\x1b[0m`; -const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`; +/** Total type for both upload and check modes. */ +export type AnyTotals = UploadModeTotals | CheckModeTotals; + +/** Render options for the dashboard */ +export interface RenderDashboardInput { + /** Number of live workers in the pool */ + poolSize: number; + /** CPU count hint shown to user */ + cpuCount: number; + + /** Total files discovered at start */ + filesTotal: number; + /** Completed files count */ + filesCompleted: number; + /** Failed files count */ + filesFailed: number; + + /** Live map of worker state (includes per-worker progress) */ + workerState: Map; + + /** Mode-specific aggregates to print under the title */ + totals?: AnyTotals; + + /** Final frame? If true, show the cursor and freeze the output */ + final?: boolean; + + /** Live throughput numbers (records/sec) */ + throughput?: { + /** total successful records observed so far (live, not just receipts) */ + successSoFar: number; + /** short-window rate (e.g., 10s) */ + r10s: number; + /** long-window rate (e.g., 60s) */ + r60s: number; + }; +} + +let lastFrame = ''; /** + * Render the multi-worker upload dashboard. + * + * Features: + * - Colored status badges per worker (WORKING=green, IDLE=dim, WARN=yellow, ERROR=red) + * - Global progress bar + per-worker mini bars (processed/total) + * - Combined receipts totals with thousands separators + * - Error breakdown in red + * - Live throughput (records/min) and a success counter + * - Estimated total jobs (records) based on receipts + in-flight + remainder + * - Hotkeys help: attach to worker, view combined warn/error/log + * + * This is a pure renderer: derive/prepare the data beforehand and pass it in. * - * @param poolSize - * @param cpuCount - * @param total - * @param completed - * @param failed - * @param workerState - * @param totals - * @param opts + * @param options - Options for rendering the dashboard */ -export function renderDashboard( - poolSize: number, - cpuCount: number, - total: number, - completed: number, - failed: number, - workerState: Map, - totals?: AnyTotals, - opts?: RenderOpts, -): void { +export function renderDashboard({ + poolSize, + cpuCount, + filesTotal, + filesCompleted, + filesFailed, + workerState, + totals, + final, + throughput, +}: RenderDashboardInput): void { + const fmt = (n: number): string => n.toLocaleString(); + const redIf = (n: number, s: string): string => (n > 0 ? colors.red(s) : s); + const inProgress = [...workerState.values()].filter((s) => s.busy).length; + + // Global file-level progress bar (files) + const completedFiles = filesCompleted + filesFailed; const pct = - total === 0 + filesTotal === 0 ? 100 - : Math.floor(((completed + failed) / Math.max(1, total)) * 100); + : Math.floor((completedFiles / Math.max(1, filesTotal)) * 100); const barWidth = 40; const filled = Math.floor((pct / 100) * barWidth); const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); - const lines = [...workerState.entries()].map(([id, s]) => { - let label = s.busy ? 'WORKING' : 'IDLE '; - if (s.lastLevel === 'error') label = red('ERROR '); - else if (s.lastLevel === 'warn') label = yellow('WARN '); + // ---- Estimate TOTAL JOBS (records) ------------------------------------- + // Jobs completed so far from receipts (only reliable in upload mode) + const jobsFromReceipts = + totals && totals.mode === 'upload' + ? (totals.success || 0) + (totals.skipped || 0) + (totals.error || 0) + : undefined; + + // Jobs total for in-flight workers that reported their planned totals + const inflightJobsKnown = [...workerState.values()].reduce((sum, s) => { + const t = s.progress?.total ?? 0; + return sum + (t > 0 && s.busy ? t : 0); + }, 0); + + // Average jobs per completed file (only when we have receipts and ≥1 file done) + const avgJobsPerCompletedFile = + jobsFromReceipts !== undefined && completedFiles > 0 + ? jobsFromReceipts / completedFiles + : undefined; + + // Remaining *files* not yet started (unknown job totals) + const remainingFiles = Math.max(filesTotal - completedFiles - inProgress, 0); + + // Estimation strategy: + // estJobs = jobsFromReceipts + inflightKnown + remainingFiles * avgJobsPerCompletedFile + // If we lack receipts (very early), try a softer fallback using in-flight averages. + let estTotalJobsText = colors.dim('Est. total jobs: —'); + if (avgJobsPerCompletedFile !== undefined) { + const est = + (jobsFromReceipts ?? 0) + + inflightJobsKnown + + remainingFiles * avgJobsPerCompletedFile; + estTotalJobsText = colors.dim(`Est. total jobs: ${fmt(Math.round(est))}`); + } else if (inProgress > 0) { + // Fallback: average planned total per in-flight file + const avgInFlight = + inflightJobsKnown > 0 ? inflightJobsKnown / inProgress : 0; + if (avgInFlight > 0) { + const est = inflightJobsKnown + remainingFiles * avgInFlight; + estTotalJobsText = colors.dim(`Est. total jobs: ${fmt(Math.round(est))}`); + } + } + // ------------------------------------------------------------------------ + + // Header + const header = [ + `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( + `(CPU avail: ${cpuCount})`, + )}`, + `${colors.dim('Files')} ${fmt(filesTotal)} ${colors.dim( + 'Completed', + )} ${fmt(filesCompleted)} ${colors.dim('Failed')} ${redIf( + filesFailed, + fmt(filesFailed), + )} ${colors.dim('In-flight')} ${fmt(inProgress)}`, + `[${bar}] ${pct}% ${estTotalJobsText}`, + ]; + + // Totals block + let totalsBlock = ''; + if (totals) { + if (totals.mode === 'upload') { + const t = totals as UploadModeTotals; + const errorsList = Object.entries(t.errors || {}).map( + ([msg, count]) => + ` ${colors.red(`Count[${fmt(count)}]`)} ${colors.red(msg)}`, + ); + totalsBlock = [ + errorsList.length + ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` + : '', + `${colors.bold('Receipts totals')} — Success: ${fmt( + t.success, + )} Skipped: ${fmt(t.skipped)} Error: ${redIf(t.error, fmt(t.error))}`, + ] + .filter(Boolean) + .join('\n\n'); + } else { + const t = totals as CheckModeTotals; + totalsBlock = + `${colors.bold('Receipts totals')} — Pending: ${fmt( + t.totalPending, + )} ` + + `PendingConflicts: ${fmt(t.pendingConflicts)} PendingSafe: ${fmt( + t.pendingSafe, + )} ` + + `Skipped: ${fmt(t.skipped)}`; + } + } + + // Throughput (display as records/hour; inputs are per-second rates) + const tp = throughput + ? (() => { + const perHour10 = Math.round(throughput.r10s * 3600); + const perHour60 = Math.round(throughput.r60s * 3600); + return ( + `Throughput: ${fmt(perHour10)}/hr (1h: ${fmt(perHour60)}/hr) ` + + `Newly uploaded this run: ${fmt(throughput.successSoFar)}` + ); + })() + : ''; + + // Per-worker lines with mini progress bars + const miniWidth = 18; + const workerLines = [...workerState.entries()].map(([id, s]) => { + const badge = + s.lastLevel === 'error' + ? colors.red('ERROR ') + : s.lastLevel === 'warn' + ? colors.yellow('WARN ') + : s.busy + ? colors.green('WORKING') + : colors.dim('IDLE '); const fname = s.file ? basename(s.file) : '-'; const elapsed = s.startedAt ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` : '-'; - return ` [w${id}] ${label} | ${fname} | ${elapsed}`; + + // mini progress + const processed = s.progress?.processed ?? 0; + const total = s.progress?.total ?? 0; + const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; + const ff = Math.floor((pctw / 100) * miniWidth); + const mini = + total > 0 + ? '█'.repeat(ff) + '░'.repeat(miniWidth - ff) + : ' '.repeat(miniWidth); + const miniTxt = + total > 0 + ? `${fmt(processed)}/${fmt(total)} (${pctw}%)` + : colors.dim('—'); + + return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; }); + // Hotkeys help const maxDigit = Math.min(poolSize - 1, 9); const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; - - let totalsLine = ''; - if (totals) { - if (totals.mode === 'upload') { - const fmt = (n: number) => n.toLocaleString(); - const errorBreakdown = Object.entries( - (totals as UploadModeTotals).errors || {}, + const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + const hotkeysLine = final + ? colors.dim( + 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', ) - .map(([k, v]) => ` Count[${fmt(v as number)}] ${k}`) - .join('\n'); - totalsLine = `${ - errorBreakdown - ? `\n\nThe individual error breakdown is:\n\n${errorBreakdown}\n\n` - : '' - }Receipts totals — Success: ${fmt(totals.success)} Skipped: ${fmt( - totals.skipped, - )} Error: ${fmt(totals.error)}`; - } else { - totalsLine = - `Receipts totals — Pending: ${totals.totalPending} PendingConflicts: ${totals.pendingConflicts} ` + - `PendingSafe: ${totals.pendingSafe} Skipped: ${totals.skipped}`; - } - } - - const isFinal = !!opts?.final; - const hotkeysLine = isFinal - ? 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit' - : `Hotkeys: [${digitRange}] attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit`; + : colors.dim( + `Hotkeys: [${digitRange}] attach${extra} • ` + + 'e=errors • w=warnings • l=logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit', + ); const frame = [ - `Parallel uploader — ${poolSize} workers (CPU avail: ${cpuCount})`, - totalsLine, - `Files: ${total} Completed: ${completed} Failed: ${failed} In-flight: ${inProgress}`, - `[${bar}] ${pct}%`, + ...header, + tp ? colors.cyan(tp) : '', + totalsBlock ? `\n${totalsBlock}` : '', '', - ...lines, + ...workerLines, '', hotkeysLine, ] .filter(Boolean) .join('\n'); - if (!isFinal && frame === lastFrame) return; + if (!final && frame === lastFrame) return; lastFrame = frame; - if (!isFinal) { + if (!final) { process.stdout.write('\x1b[?25l'); readline.cursorTo(process.stdout, 0, 0); readline.clearScreenDown(process.stdout); } else { process.stdout.write('\x1b[?25h'); } - process.stdout.write(`${frame}\n`); } diff --git a/src/lib/pooling/showCombinedLogs.ts b/src/lib/pooling/showCombinedLogs.ts new file mode 100644 index 00000000..63c8434f --- /dev/null +++ b/src/lib/pooling/showCombinedLogs.ts @@ -0,0 +1,96 @@ +/* eslint-disable no-continue, no-control-regex */ +import { readFileSync } from 'node:fs'; +import type { getWorkerLogPaths } from './spawnWorkerProcess'; + +/** + * + */ +export type WhichLogs = Array<'out' | 'err' | 'structured' | 'warn' | 'info'>; + +/** + * Show combined logs from all worker processes. + * + * @param slotLogPaths - Map of worker IDs to their log file paths. + * @param whichList - one or more sources to include (e.g., ['err','out']) + * @param filterLevel - 'error', 'warn', or 'all' to filter log levels. + */ +export function showCombinedLogs( + slotLogPaths: Map | undefined>, + whichList: WhichLogs, + filterLevel: 'error' | 'warn' | 'all', +): void { + process.stdout.write('\x1b[2J\x1b[H'); + + const isError = (t: string) => + /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); + const isWarnTag = (t: string) => /\b(WARN|WARNING)\b/i.test(t); + + const lines: string[] = []; + + for (const [, paths] of slotLogPaths) { + if (!paths) continue; + + const files: Array<{ + /** */ path: string /** */; + src: 'out' | 'err' | 'structured' | 'warn' | 'info'; + }> = []; + for (const which of whichList) { + if (which === 'out' && paths.outPath) + files.push({ path: paths.outPath, src: 'out' }); + if (which === 'err' && paths.errPath) + files.push({ path: paths.errPath, src: 'err' }); + if (which === 'structured' && (paths as any).structuredPath) + files.push({ path: (paths as any).structuredPath, src: 'structured' }); + if ((paths as any).warnPath && which === 'warn') + files.push({ path: (paths as any).warnPath, src: 'warn' }); + if ((paths as any).infoPath && which === 'info') + files.push({ path: (paths as any).infoPath, src: 'info' }); + } + + for (const { path, src } of files) { + let text = ''; + try { + text = readFileSync(path, 'utf8'); + } catch { + continue; + } + + for (const ln of text.split('\n')) { + if (!ln) continue; + + const clean = ln.replace(/\x1B\[[0-9;]*m/g, ''); + + if (filterLevel === 'all') { + lines.push(ln); + continue; + } + + if (filterLevel === 'error') { + if (isError(clean)) lines.push(ln); + continue; + } + + // filterLevel === 'warn' + // Accept: + // - explicit WARN tag anywhere + // - OR lines from stderr that are NOT explicit errors (many warn libs print to stderr) + // - OR lines containing the word "warning" (common in some libs) + if (isWarnTag(clean) || (src === 'err' && !isError(clean))) { + lines.push(ln); + continue; + } + } + } + } + + // simple time-sort; each worker often prefixes ISO timestamps + lines.sort((a, b) => { + const ta = a.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + const tb = b.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + return ta.localeCompare(tb); + }); + + process.stdout.write(`${lines.join('\n')}\n`); + process.stdout.write('\nPress Esc/Ctrl+] to return to dashboard.\n'); +} +/* eslint-enable no-continue, no-control-regex */ diff --git a/src/lib/pooling/spawnWorkerProcess.ts b/src/lib/pooling/spawnWorkerProcess.ts index af487ec2..a9a2f542 100644 --- a/src/lib/pooling/spawnWorkerProcess.ts +++ b/src/lib/pooling/spawnWorkerProcess.ts @@ -3,104 +3,213 @@ import { fork, type ChildProcess } from 'node:child_process'; import { join } from 'node:path'; import { createWriteStream } from 'node:fs'; import { openLogTailWindowMulti } from './openTerminal'; +import { ensureLogFile } from './ensureLogFile'; +import { classifyLogLevel, makeLineSplitter } from './logRotation'; export const CHILD_FLAG = '--child-upload-preferences'; +// Symbol key so we can stash/retrieve paths on the child proc safely +const LOG_PATHS_SYM: unique symbol = Symbol('workerLogPaths'); + export interface WorkerLogPaths { - /** */ + /** Structured (app-controlled) log file path written via WORKER_LOG */ structuredPath: string; - /** */ + /** Raw stdout capture */ outPath: string; - /** */ + /** Raw stderr capture */ errPath: string; + /** Lines classified as INFO (primarily stdout) */ + infoPath: string; + /** Lines classified as WARN (from stderr without error tokens) */ + warnPath: string; + /** Lines classified as ERROR (from stderr, including uncaught) */ + errorPath: string; } -// Symbol key so we can stash/retrieve paths on the child proc safely -const LOG_PATHS_SYM: unique symbol = Symbol('workerLogPaths'); +/** + * Retrieve the paths we stashed on the child. + * + * @param child - The worker ChildProcess instance. + * @returns The log paths or undefined if not set. + */ +export function getWorkerLogPaths( + child: ChildProcess, +): WorkerLogPaths | undefined { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (child as any)[LOG_PATHS_SYM] as WorkerLogPaths | undefined; +} /** + * Is IPC channel still open? (Node doesn't type `.channel`) * - * @param pathStr + * @param w - The worker ChildProcess instance. + * @returns True if the IPC channel is open, false otherwise. */ -function ensureLogFile(pathStr: string) { +export function isIpcOpen(w: ChildProcess | undefined | null): boolean { + const ch = w && (w as any).channel; + return !!(w && w.connected && ch && !(ch as any).destroyed); +} + +/** + * Safely send a message to the worker process. + * + * @param w - The worker ChildProcess instance. + * @param msg - The message to send. + * @returns True if the message was sent successfully, false otherwise. + */ +export function safeSend(w: ChildProcess, msg: unknown): boolean { + if (!isIpcOpen(w)) return false; try { - const w = createWriteStream(pathStr, { flags: 'a' }); - w.end(); - } catch {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any + w.send?.(msg as any); + return true; + } catch (err: any) { + if ( + err?.code === 'ERR_IPC_CHANNEL_CLOSED' || + err?.code === 'EPIPE' || + err?.errno === -32 + ) { + return false; + } + throw err; + } +} + +export interface SpawnWorkerOptions { + /** Worker slot/index */ + id: number; + /** Absolute path to the module to fork (should handle CHILD_FLAG) */ + modulePath: string; + /** Directory where log files will be written */ + logDir: string; + /** If true, open tail windows for the log files */ + openLogWindows: boolean; + /** If true, spawn with silent stdio (respect your existing setting) */ + isSilent: boolean; + /** Optional override for the child flag (defaults to CHILD_FLAG) */ + childFlag?: string; } /** * Spawn a worker process with piped stdio and persisted logs. - * - stdin/stdout/stderr = 'pipe' so we can attach interactively - * - stdout/stderr also go to per-worker files (.out/.err) - * - worker writes structured logs to WORKER_LOG (structuredPath) * - * @param id - * @param modulePath - * @param logDir - * @param openLogWindows - * @param isSilent + * Files produced per worker: + * - worker-{id}.log (structured WORKER_LOG written by the child) + * - worker-{id}.out.log (raw stdout) + * - worker-{id}.err.log (raw stderr) + * - worker-{id}.info.log (classified INFO lines from stdout) + * - worker-{id}.warn.log (classified WARN lines from stderr) + * - worker-{id}.error.log (classified ERROR lines from stderr) + * + * @param opts */ -export function spawnWorkerProcess( - id: number, - modulePath: string, - logDir: string, - openLogWindows: boolean, - isSilent: boolean, -): ChildProcess { +export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { + const { + id, + modulePath, + logDir, + openLogWindows, + isSilent, + childFlag = CHILD_FLAG, + } = opts; + const structuredPath = join(logDir, `worker-${id}.log`); const outPath = join(logDir, `worker-${id}.out.log`); const errPath = join(logDir, `worker-${id}.err.log`); - [structuredPath, outPath, errPath].forEach(ensureLogFile); + const infoPath = join(logDir, `worker-${id}.info.log`); + const warnPath = join(logDir, `worker-${id}.warn.log`); + const errorPath = join(logDir, `worker-${id}.error.log`); + + [structuredPath, outPath, errPath, infoPath, warnPath, errorPath].forEach( + ensureLogFile, + ); - const child = fork(modulePath, ['--child-upload-preferences'], { + const child = fork(modulePath, [childFlag], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'], env: { ...process.env, WORKER_ID: String(id), WORKER_LOG: structuredPath }, execArgv: process.execArgv, + silent: isSilent, }); - // Persist stdout/stderr to files + // Raw capture streams const outStream = createWriteStream(outPath, { flags: 'a' }); const errStream = createWriteStream(errPath, { flags: 'a' }); + + // Classified streams + const infoStream = createWriteStream(infoPath, { flags: 'a' }); + const warnStream = createWriteStream(warnPath, { flags: 'a' }); + const errorStream = createWriteStream(errorPath, { flags: 'a' }); + + // Pipe raw streams child.stdout?.pipe(outStream); child.stderr?.pipe(errStream); // Headers so tail windows show something immediately - outStream.write( - `[parent] stdout capture active for w${id} (pid ${child.pid})\n`, - ); - errStream.write( - `[parent] stderr capture active for w${id} (pid ${child.pid})\n`, - ); + const hdr = (name: string) => + `[parent] ${name} capture active for w${id} (pid ${child.pid})\n`; + outStream.write(hdr('stdout')); + errStream.write(hdr('stderr')); + infoStream.write(hdr('info')); + warnStream.write(hdr('warn')); + errorStream.write(hdr('error')); + + // Classified INFO from stdout (line-buffered) + if (child.stdout) { + const onOutLine = makeLineSplitter((line) => { + if (!line) return; + try { + // Treat all stdout lines as INFO for the classified stream + infoStream.write(`${line}\n`); + } catch { + /* ignore */ + } + }); + child.stdout.on('data', onOutLine); + } + + // Classified WARN/ERROR from stderr (line-buffered) + if (child.stderr) { + const onErrLine = makeLineSplitter((line) => { + if (!line) return; + const lvl = classifyLogLevel(line); // 'warn' | 'error' | null + try { + if (lvl === 'error') { + errorStream.write(`${line}\n`); + } else if (lvl === 'warn' || lvl == null) { + // Treat untagged stderr as WARN by default (common in libs) + warnStream.write(`${line}\n`); + } + } catch { + /* ignore */ + } + }); + child.stderr.on('data', onErrLine); + } // Stash log path metadata on the child (child as any)[LOG_PATHS_SYM] = { structuredPath, outPath, errPath, + infoPath, + warnPath, + errorPath, } as WorkerLogPaths; if (openLogWindows) { openLogTailWindowMulti( - [structuredPath, outPath, errPath], + [structuredPath, outPath, errPath, infoPath, warnPath, errorPath], `worker-${id}`, isSilent, ); } + // Best-effort error suppression on file streams outStream.on('error', () => {}); errStream.on('error', () => {}); + infoStream.on('error', () => {}); + warnStream.on('error', () => {}); + errorStream.on('error', () => {}); return child; } - -/** - * Retrieve the paths we stashed on the child. - * - * @param child - */ -export function getWorkerLogPaths( - child: ChildProcess, -): WorkerLogPaths | undefined { - return (child as any)[LOG_PATHS_SYM] as WorkerLogPaths | undefined; -} From 4e11953ec27bacd997aa89d91147b065f8f73900 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 21:19:27 -0700 Subject: [PATCH 28/72] Exports --- .../consent/upload-preferences/impl.ts | 174 ++++--- .../upload/batchUploader.ts | 2 +- src/lib/pooling/downloadArtifact.ts | 98 +++- src/lib/pooling/renderDashboard.ts | 432 +++++++++++++++--- .../parsePreferenceManagementCsv.ts | 13 +- 5 files changed, 553 insertions(+), 166 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index b625e10f..2deb9116 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -36,6 +36,10 @@ import { writeFailingUpdatesCsv, type FailingUpdateRow, } from '../../../lib/pooling/downloadArtifact'; +import type { + ExportStatusMap, + ExportArtifactStatus, +} from '../../../lib/pooling/renderDashboard'; /** CLI flags */ export interface UploadPreferencesCommandFlags { @@ -220,7 +224,7 @@ export async function uploadPreferences( ); } - // Throughput metering (records/s and /m) + // Throughput metering (records/s) const meter = new RateCounter(); let liveSuccessTotal = 0; @@ -285,6 +289,15 @@ export async function uploadPreferences( skipped: 0, }; + // Export status shown permanently in the dashboard + const exportStatus: ExportStatusMap = { + error: { path: join(LOG_DIR, 'combined-errors.log') }, + warn: { path: join(LOG_DIR, 'combined-warns.log') }, + info: { path: join(LOG_DIR, 'combined-info.log') }, + all: { path: join(LOG_DIR, 'combined-all.log') }, + failuresCsv: { path: join(LOG_DIR, 'failing-updates.csv') }, + }; + let dashboardPaused = false; const repaint = (final = false) => { if (dashboardPaused && !final) return; @@ -302,6 +315,8 @@ export async function uploadPreferences( r10s: meter.rate(10_000), r60s: meter.rate(60_000), }, + exportsDir: LOG_DIR, + exportStatus, }); }; @@ -313,7 +328,7 @@ export async function uploadPreferences( busy: false, file: null, startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', // preserve badge (e.g., ERROR after crash) + lastLevel: prev?.lastLevel ?? 'ok', progress: undefined, }); return; @@ -336,7 +351,7 @@ export async function uploadPreferences( busy: true, file: filePath, startedAt: Date.now(), - lastLevel: 'ok', // new task starts clean + lastLevel: 'ok', progress: undefined, }); @@ -374,7 +389,7 @@ export async function uploadPreferences( logDir: LOG_DIR, openLogWindows: true, isSilent, - childFlag: CHILD_FLAG, + // childFlag omitted: spawn uses its own default CHILD_FLAG }); workers.set(i, child); workerState.set(i, { @@ -553,91 +568,110 @@ export async function uploadPreferences( onAttach: attachScreen, onDetach: detachScreen, onCtrlC: onSigint, - getLogPaths: (id) => safeGetLogPathsForSlot(id), + getLogPaths: (id: number) => safeGetLogPathsForSlot(id), replayBytes: 200 * 1024, replayWhich: ['out', 'err'], onEnterAttachScreen: attachScreen, }); - // extra hotkeys for combined views - // inside onKeypressExtra in impl.ts + // Lowercase keys open viewers; Uppercase keys write export files. + // e -> errors viewer (stderr filtered to ERROR) + // w -> warnings viewer (warn file + stderr non-error) + // i -> info viewer (info file) + // l -> all logs viewer (out + err + structured) + // E -> export combined errors + // W -> export combined warns + // I -> export combined info + // A -> export combined ALL logs + // F -> export failing-updates CSV + // Esc / Ctrl+] -> return to dashboard const onKeypressExtra = (buf: Buffer): void => { const s = buf.toString('utf8'); - // existing viewers (now with array-based sources) - if (s === 'e' || s === 'E') { - // Errors: read raw stderr and let the function filter only ERROR lines. - // (If you later add 'errorPath' to WhichLogs, you can include ['error'] too.) + const view = ( + sources: Array<'out' | 'err' | 'structured' | 'warn' | 'info'>, + level: 'error' | 'warn' | 'all', + ) => { dashboardPaused = true; - showCombinedLogs(slotLogPaths, ['err'], 'error'); - return; - } - if (s === 'w' || s === 'W') { - // Warnings: include the dedicated WARN file (if present) AND stderr - // (lots of libs print WARN on stderr without "ERROR" tags) - dashboardPaused = true; - showCombinedLogs(slotLogPaths, ['warn', 'err'], 'warn'); - return; - } - if (s === 'i' || s === 'I') { - // Info-only: use the classified INFO file - dashboardPaused = true; - showCombinedLogs(slotLogPaths, ['info'], 'all'); - return; - } - if (s === 'l' || s === 'L') { - // All logs: combine stdout, stderr, and your structured app log - dashboardPaused = true; - showCombinedLogs(slotLogPaths, ['out', 'err', 'structured'], 'all'); - return; - } + showCombinedLogs(slotLogPaths, sources, level); + }; + + const noteExport = (slot: keyof ExportStatusMap, p: string) => { + const now = Date.now(); + const current: ExportArtifactStatus = exportStatus[slot] || { path: p }; + exportStatus[slot] = { + path: p || current.path, + savedAt: now, + exported: true, + }; + repaint(); + }; - // NEW: Shift+E / Shift+W / Shift+I to write combined artifact files + // --- viewers (lowercase) --- + if (s === 'e') return view(['err'], 'error'); + if (s === 'w') return view(['warn', 'err'], 'warn'); + if (s === 'i') return view(['info'], 'all'); + if (s === 'l') return view(['out', 'err', 'structured'], 'all'); + + // --- exports (uppercase) --- if (s === 'E') { - exportCombinedLogs(slotLogPaths, 'error', LOG_DIR) - .then((p) => - process.stdout.write(`\nWrote combined error logs to: ${p}\n`), - ) + void exportCombinedLogs(slotLogPaths, 'error', LOG_DIR) + .then((p) => { + process.stdout.write(`\nWrote combined error logs to: ${p}\n`); + noteExport('error', p); + }) .catch(() => process.stdout.write('\nFailed to write combined error logs\n'), ); return; } if (s === 'W') { - exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR) - .then((p) => - process.stdout.write(`\nWrote combined warn logs to: ${p}\n`), - ) + void exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR) + .then((p) => { + process.stdout.write(`\nWrote combined warn logs to: ${p}\n`); + noteExport('warn', p); + }) .catch(() => process.stdout.write('\nFailed to write combined warn logs\n'), ); return; } if (s === 'I') { - exportCombinedLogs(slotLogPaths, 'info', LOG_DIR) - .then((p) => - process.stdout.write(`\nWrote combined info logs to: ${p}\n`), - ) + void exportCombinedLogs(slotLogPaths, 'info', LOG_DIR) + .then((p) => { + process.stdout.write(`\nWrote combined info logs to: ${p}\n`); + noteExport('info', p); + }) .catch(() => process.stdout.write('\nFailed to write combined info logs\n'), ); return; } - - // NEW: Shift+F to dump failing-updates CSV (from memory) + if (s === 'A') { + void exportCombinedLogs(slotLogPaths, 'all', LOG_DIR) + .then((p) => { + process.stdout.write(`\nWrote combined ALL logs to: ${p}\n`); + noteExport('all', p); + }) + .catch(() => + process.stdout.write('\nFailed to write combined ALL logs\n'), + ); + return; + } if (s === 'F') { const dest = join(LOG_DIR, 'failing-updates.csv'); - writeFailingUpdatesCsv(failingUpdatesMem, dest) - .then((p) => - process.stdout.write(`\nWrote failing updates CSV to: ${p}\n`), - ) + void writeFailingUpdatesCsv(failingUpdatesMem, dest) + .then((p) => { + process.stdout.write(`\nWrote failing updates CSV to: ${p}\n`); + noteExport('failuresCsv', p); + }) .catch(() => process.stdout.write('\nFailed to write failing updates CSV\n'), ); return; } - // Esc / Ctrl+] back to dashboard + // --- back to dashboard --- if (s === '\x1b' || s === '\x1d') { dashboardPaused = false; repaint(); @@ -650,17 +684,6 @@ export async function uploadPreferences( process.stdin.resume(); process.stdin.on('data', onKeypressExtra); - // hint - const { poolSize: shownPool } = computePoolSize(concurrency, files.length); - const maxDigit = Math.min(shownPool - 1, 9); - const digitRange = shownPool <= 1 ? '0' : `0-${maxDigit}`; - const extra = shownPool > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; - process.stdout.write( - colors.dim( - `\nHotkeys: [${digitRange}] attach${extra} • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit\n\n`, - ), - ); - // wait for completion of work (not viewer) await new Promise((resolveWork) => { const check = setInterval(async () => { @@ -676,19 +699,32 @@ export async function uploadPreferences( clearInterval(check); clearInterval(renderInterval); - repaint(true); - - // right after repaint(true) in the "work complete" block + // Final “auto-export” of artifacts try { const e = await exportCombinedLogs(slotLogPaths, 'error', LOG_DIR); + exportStatus.error = { path: e, savedAt: Date.now(), exported: true }; const w = await exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR); + exportStatus.warn = { path: w, savedAt: Date.now(), exported: true }; const i = await exportCombinedLogs(slotLogPaths, 'info', LOG_DIR); + exportStatus.info = { path: i, savedAt: Date.now(), exported: true }; + const a = await exportCombinedLogs(slotLogPaths, 'all', LOG_DIR); + exportStatus.all = { path: a, savedAt: Date.now(), exported: true }; const fPath = join(LOG_DIR, 'failing-updates.csv'); await writeFailingUpdatesCsv(failingUpdatesMem, fPath); + exportStatus.failuresCsv = { + path: fPath, + savedAt: Date.now(), + exported: true, + }; process.stdout.write( - `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${fPath}\n\n`, + `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${a}\n ${fPath}\n\n`, ); - } catch {} + } catch { + // ignore + } + + // Final repaint with exportStatus visible & green + repaint(true); if ((agg as AnyTotals).mode === 'upload') { const a = agg as UploadModeTotals; diff --git a/src/commands/consent/upload-preferences/upload/batchUploader.ts b/src/commands/consent/upload-preferences/upload/batchUploader.ts index 77a68442..fc8985e2 100644 --- a/src/commands/consent/upload-preferences/upload/batchUploader.ts +++ b/src/commands/consent/upload-preferences/upload/batchUploader.ts @@ -117,7 +117,7 @@ export async function uploadChunkWithSplit( const [left, right] = splitInHalf(entries); logger.warn( colors.yellow( - `Non-retryable error for batch of ${entries.length} (status=${status}): ${msg}. ` + + `Non-retryable failure for batch of ${entries.length} (status=${status}): ${msg}. ` + `Splitting into ${left.length} and ${right.length}.`, ), ); diff --git a/src/lib/pooling/downloadArtifact.ts b/src/lib/pooling/downloadArtifact.ts index 7d0ce881..43eface9 100644 --- a/src/lib/pooling/downloadArtifact.ts +++ b/src/lib/pooling/downloadArtifact.ts @@ -4,27 +4,47 @@ import { createWriteStream, statSync, readFileSync, + mkdirSync, } from 'node:fs'; -import { join, basename } from 'node:path'; +import { join, basename, dirname } from 'node:path'; import { once } from 'node:events'; import type { getWorkerLogPaths } from './spawnWorkerProcess'; +/** Which combined log to export */ +export type LogKind = 'error' | 'warn' | 'info' | 'all'; + +/** Convenience alias for the optional return from getWorkerLogPaths */ +type SlotPaths = Map | undefined>; /** * */ -export type LogKind = 'error' | 'warn' | 'info'; +type WorkerPaths = NonNullable>; /** + * Choose the best source file for a given kind, with safe fallbacks: + * - error: prefer errorPath → fallback to errPath + * - warn: prefer warnPath → fallback to errPath + * - info: prefer infoPath → fallback to outPath * + * @param kind + * @param p */ -type SlotPaths = Map | undefined>; +function pickSourcePath( + kind: Exclude, + p: WorkerPaths, +): string | undefined { + if (kind === 'error') return (p as any).errorPath || p.errPath; + if (kind === 'warn') return (p as any).warnPath || p.errPath; + // kind === 'info' + return (p as any).infoPath || p.outPath; +} /** * Combine all worker logs of the given kind into a single file. * Writes simple headers per worker and concatenates in worker-id order. * * @param slotLogPaths - Map of workerId -> WorkerLogPaths - * @param kind - 'error' | 'warn' | 'info' + * @param kind - 'error' | 'warn' | 'info' | 'all' * @param outDir - directory to write the combined file * @param filename - optional override (default: combined-{kind}.log) * @returns absolute path to the combined log file @@ -35,20 +55,66 @@ export async function exportCombinedLogs( outDir: string, filename?: string, ): Promise { + mkdirSync(outDir, { recursive: true }); + const outPath = join(outDir, filename ?? `combined-${kind}.log`); const out = createWriteStream(outPath, { flags: 'w' }); - const pick = (p: NonNullable>) => - kind === 'error' ? p.errorPath : kind === 'warn' ? p.warnPath : p.infoPath; - - for (const [id, paths] of [...slotLogPaths.entries()].sort( + for (const [id, pathsMaybe] of [...slotLogPaths.entries()].sort( (a, b) => a[0] - b[0], )) { - if (!paths) continue; - const srcPath = pick(paths); + if (!pathsMaybe) continue; + const p = pathsMaybe as WorkerPaths; + + // Worker header + out.write(`\n==== worker ${id} ====\n`); + + if (kind === 'all') { + // Concatenate stdout, stderr, and structured (if present) + const sources: Array<{ + /** */ label: string /** */; + /** */ + path?: string; + }> = [ + { label: 'stdout', path: p.outPath }, + { label: 'stderr', path: p.errPath }, + { label: 'structured', path: (p as any).structuredPath }, + ]; + + for (const { label, path } of sources) { + out.write( + `\n---- ${label}${path ? ` (${basename(path)})` : ''} ----\n`, + ); + if (!path) { + out.write('[unavailable]\n'); + continue; + } + try { + const st = statSync(path); + if (st.size === 0) { + out.write('[empty]\n'); + continue; + } + const rs = createReadStream(path, { encoding: 'utf8' }); + rs.pipe(out, { end: false }); + await once(rs, 'end'); + } catch { + out.write(`[unavailable: ${path}]\n`); + } + } + continue; + } + + // error / warn / info (single best source with fallback) + const srcPath = pickSourcePath(kind, p); + out.write(`(${srcPath ? basename(srcPath) : 'unavailable'})\n`); + + if (!srcPath) { + out.write('[unavailable]\n'); + continue; + } + try { - // header for context - out.write(`\n==== worker ${id} (${basename(srcPath)}) ====\n`); const st = statSync(srcPath); if (st.size === 0) { out.write('[empty]\n'); @@ -95,9 +161,9 @@ function csvCell(v: string): string { /** * Stream an array of failing updates to a CSV file. - * This overwrites any existing file at destPath. + * Overwrites any existing file at destPath. * - * Columns: primaryKey, uploadedAt, error, updateJson, sourceFile + * Columns: primaryKey,uploadedAt,error,updateJson,sourceFile * * @param rows * @param destPath @@ -106,8 +172,8 @@ export async function writeFailingUpdatesCsv( rows: FailingUpdateRow[], destPath: string, ): Promise { + mkdirSync(dirname(destPath), { recursive: true }); const ws = createWriteStream(destPath, { flags: 'w' }); - // header ws.write('primaryKey,uploadedAt,error,updateJson,sourceFile\n'); for (const r of rows) { ws.write( @@ -125,7 +191,7 @@ export async function writeFailingUpdatesCsv( } /** - * Convenience helper to parse failing updates out of a receipts.json file. + * Parse failing updates out of a receipts.json file. * Returns rows you can merge into your in-memory buffer. * * @param receiptPath diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 052fcb3e..65d64d04 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -1,39 +1,69 @@ -import { basename } from 'node:path'; +// renderDashboard.ts +import { basename, join } from 'node:path'; import colors from 'colors'; import * as readline from 'node:readline'; +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import type { WorkerState } from './assignWorkToWorker'; +import type { getWorkerLogPaths } from './spawnWorkerProcess'; /** Upload-mode totals (final-ish counters derived from receipts) */ export type UploadModeTotals = { - /** Mode for uploading files --dryRun=false */ + /** */ mode: 'upload'; - /** Total number of successfully uploaded records */ + /** */ success: number; - /** Total number of skipped records */ + /** */ skipped: number; - /** Total number of error records */ + /** */ error: number; - /** aggregated error message -> count */ + /** */ errors: Record; }; /** Check-mode totals (dry-run counters) */ export type CheckModeTotals = { - /** Mode for checking files --dryRun=true */ + /** */ mode: 'check'; - /** Total number of pending conflict records */ + /** */ pendingConflicts: number; - /** Total number of pending safe records */ + /** */ pendingSafe: number; - /** Total number of pending records to update */ + /** */ totalPending: number; - /** Total number of skipped records */ + /** */ skipped: number; }; /** Total type for both upload and check modes. */ export type AnyTotals = UploadModeTotals | CheckModeTotals; +/** Export kinds we support in the UI */ +export type LogExportKind = 'error' | 'warn' | 'info' | 'all'; + +/** Status for a single export artifact */ +export interface ExportArtifactStatus { + /** Absolute path of the last written artifact file */ + path: string; + /** Unix ms when that file was last written */ + savedAt?: number; + /** True once we’ve exported at least once during this parent run */ + exported?: boolean; +} + +/** Map of all export artifacts we show */ +export interface ExportStatusMap { + /** */ + error?: ExportArtifactStatus; + /** */ + warn?: ExportArtifactStatus; + /** */ + info?: ExportArtifactStatus; + /** */ + all?: ExportArtifactStatus; + /** */ + failuresCsv?: ExportArtifactStatus; // for Shift+F CSV of failing updates +} + /** Render options for the dashboard */ export interface RenderDashboardInput { /** Number of live workers in the pool */ @@ -59,32 +89,119 @@ export interface RenderDashboardInput { /** Live throughput numbers (records/sec) */ throughput?: { - /** total successful records observed so far (live, not just receipts) */ + /** */ successSoFar: number; - /** short-window rate (e.g., 10s) */ + /** */ r10s: number; - /** long-window rate (e.g., 60s) */ + /** */ r60s: number; }; + + /** + * Directory where export artifacts (combined logs / CSV) are written. + * Always shown so users know where to find files. + */ + exportsDir?: string; + + /** + * Current (latest) artifact status per kind. When a kind has never been + * exported this session, the line renders in dim text and shows a sensible + * default path in exportsDir so users know where it will appear. + */ + exportStatus?: ExportStatusMap; } let lastFrame = ''; +/* ------------------------------------------------------------ + * Small helpers + * ------------------------------------------------------------ */ + +const stripAnsi = (s: string) => s.replace(/\x1B\[[0-9;]*m/g, ''); +const isError = (t: string) => + /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); +const isWarn = (t: string) => /\b(WARN|WARNING)\b/i.test(t); + /** - * Render the multi-worker upload dashboard. + * Consider a line a “header” if it looks like the start of a new log record. + * Helps us group multi-line WARN/ERROR blocks. * - * Features: - * - Colored status badges per worker (WORKING=green, IDLE=dim, WARN=yellow, ERROR=red) - * - Global progress bar + per-worker mini bars (processed/total) - * - Combined receipts totals with thousands separators - * - Error breakdown in red - * - Live throughput (records/min) and a success counter - * - Estimated total jobs (records) based on receipts + in-flight + remainder - * - Hotkeys help: attach to worker, view combined warn/error/log + * @param t + */ +const isNewHeader = (t: string) => + isError(t) || + isWarn(t) || + /^\s*\[w\d+\]/.test(t) || + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(t); + +/** + * Given raw text of a log file, return only the multi-line "blocks" + * whose first line satisfies `starts` (e.g., isWarn/isError). Blocks + * continue until the next header-like line or a blank line. * - * This is a pure renderer: derive/prepare the data beforehand and pass it in. + * @param text + * @param starts + */ +function extractBlocks( + text: string, + starts: (cleanLine: string) => boolean, +): string[] { + if (!text) return []; + const out: string[] = []; + const lines = text.split('\n'); + + let buf: string[] = []; + let inBlock = false; + + const flush = () => { + if (buf.length) out.push(buf.join('\n')); + buf = []; + inBlock = false; + }; + + for (const raw of lines) { + const clean = stripAnsi(raw || ''); + const headery = isNewHeader(clean); + + if (!inBlock) { + if (starts(clean)) { + inBlock = true; + buf.push(raw); + } + continue; + } + + if (!raw || headery) { + flush(); + if (starts(clean)) { + inBlock = true; + buf.push(raw); + } + } else { + buf.push(raw); + } + } + flush(); + return out.filter(Boolean); +} + +const fmtNum = (n: number): string => n.toLocaleString(); +const fmtTime = (ts?: number): string => + ts ? new Date(ts).toLocaleTimeString() : '—'; + +/* ------------------------------------------------------------ + * Main renderer + * ------------------------------------------------------------ */ + +/** + * Render the multi-worker upload dashboard. + * + * Always shows: + * - Exports directory + * - One line per export hotkey with the target file path, + * last saved time, and green coloring after first success * - * @param options - Options for rendering the dashboard + * @param root0 */ export function renderDashboard({ poolSize, @@ -96,13 +213,14 @@ export function renderDashboard({ totals, final, throughput, + exportsDir, + exportStatus = {}, }: RenderDashboardInput): void { - const fmt = (n: number): string => n.toLocaleString(); const redIf = (n: number, s: string): string => (n > 0 ? colors.red(s) : s); const inProgress = [...workerState.values()].filter((s) => s.busy).length; - // Global file-level progress bar (files) + // Global file-level progress (files) const completedFiles = filesCompleted + filesFailed; const pct = filesTotal === 0 @@ -112,63 +230,62 @@ export function renderDashboard({ const filled = Math.floor((pct / 100) * barWidth); const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); - // ---- Estimate TOTAL JOBS (records) ------------------------------------- - // Jobs completed so far from receipts (only reliable in upload mode) + // Estimate TOTAL JOBS (records) const jobsFromReceipts = totals && totals.mode === 'upload' ? (totals.success || 0) + (totals.skipped || 0) + (totals.error || 0) : undefined; - // Jobs total for in-flight workers that reported their planned totals const inflightJobsKnown = [...workerState.values()].reduce((sum, s) => { const t = s.progress?.total ?? 0; return sum + (t > 0 && s.busy ? t : 0); }, 0); - // Average jobs per completed file (only when we have receipts and ≥1 file done) const avgJobsPerCompletedFile = jobsFromReceipts !== undefined && completedFiles > 0 ? jobsFromReceipts / completedFiles : undefined; - // Remaining *files* not yet started (unknown job totals) const remainingFiles = Math.max(filesTotal - completedFiles - inProgress, 0); - // Estimation strategy: - // estJobs = jobsFromReceipts + inflightKnown + remainingFiles * avgJobsPerCompletedFile - // If we lack receipts (very early), try a softer fallback using in-flight averages. let estTotalJobsText = colors.dim('Est. total jobs: —'); if (avgJobsPerCompletedFile !== undefined) { const est = (jobsFromReceipts ?? 0) + inflightJobsKnown + remainingFiles * avgJobsPerCompletedFile; - estTotalJobsText = colors.dim(`Est. total jobs: ${fmt(Math.round(est))}`); + estTotalJobsText = colors.dim( + `Est. total jobs: ${fmtNum(Math.round(est))}`, + ); } else if (inProgress > 0) { - // Fallback: average planned total per in-flight file const avgInFlight = inflightJobsKnown > 0 ? inflightJobsKnown / inProgress : 0; if (avgInFlight > 0) { const est = inflightJobsKnown + remainingFiles * avgInFlight; - estTotalJobsText = colors.dim(`Est. total jobs: ${fmt(Math.round(est))}`); + estTotalJobsText = colors.dim( + `Est. total jobs: ${fmtNum(Math.round(est))}`, + ); } } - // ------------------------------------------------------------------------ // Header const header = [ `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( `(CPU avail: ${cpuCount})`, )}`, - `${colors.dim('Files')} ${fmt(filesTotal)} ${colors.dim( + `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim( 'Completed', - )} ${fmt(filesCompleted)} ${colors.dim('Failed')} ${redIf( + )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${redIf( filesFailed, - fmt(filesFailed), - )} ${colors.dim('In-flight')} ${fmt(inProgress)}`, + fmtNum(filesFailed), + )} ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, `[${bar}] ${pct}% ${estTotalJobsText}`, ]; + if (exportsDir) { + header.push(colors.dim(`Exports dir: ${exportsDir}`)); + } + // Totals block let totalsBlock = ''; if (totals) { @@ -176,44 +293,46 @@ export function renderDashboard({ const t = totals as UploadModeTotals; const errorsList = Object.entries(t.errors || {}).map( ([msg, count]) => - ` ${colors.red(`Count[${fmt(count)}]`)} ${colors.red(msg)}`, + ` ${colors.red(`Count[${fmtNum(count)}]`)} ${colors.red(msg)}`, ); totalsBlock = [ errorsList.length ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` : '', - `${colors.bold('Receipts totals')} — Success: ${fmt( + `${colors.bold('Receipts totals')} — Success: ${fmtNum( t.success, - )} Skipped: ${fmt(t.skipped)} Error: ${redIf(t.error, fmt(t.error))}`, + )} Skipped: ${fmtNum(t.skipped)} Error: ${redIf( + t.error, + fmtNum(t.error), + )}`, ] .filter(Boolean) .join('\n\n'); } else { const t = totals as CheckModeTotals; totalsBlock = - `${colors.bold('Receipts totals')} — Pending: ${fmt( + `${colors.bold('Receipts totals')} — Pending: ${fmtNum( t.totalPending, )} ` + - `PendingConflicts: ${fmt(t.pendingConflicts)} PendingSafe: ${fmt( + `PendingConflicts: ${fmtNum(t.pendingConflicts)} PendingSafe: ${fmtNum( t.pendingSafe, )} ` + - `Skipped: ${fmt(t.skipped)}`; + `Skipped: ${fmtNum(t.skipped)}`; } } - // Throughput (display as records/hour; inputs are per-second rates) + // Throughput (records/hour) const tp = throughput ? (() => { const perHour10 = Math.round(throughput.r10s * 3600); const perHour60 = Math.round(throughput.r60s * 3600); - return ( - `Throughput: ${fmt(perHour10)}/hr (1h: ${fmt(perHour60)}/hr) ` + - `Newly uploaded this run: ${fmt(throughput.successSoFar)}` - ); + return `Throughput: ${fmtNum(perHour10)}/hr (1h: ${fmtNum( + perHour60, + )}/hr) Newly uploaded this run: ${fmtNum(throughput.successSoFar)}`; })() : ''; - // Per-worker lines with mini progress bars + // Per-worker lines const miniWidth = 18; const workerLines = [...workerState.entries()].map(([id, s]) => { const badge = @@ -230,7 +349,6 @@ export function renderDashboard({ ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` : '-'; - // mini progress const processed = s.progress?.processed ?? 0; const total = s.progress?.total ?? 0; const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; @@ -241,24 +359,52 @@ export function renderDashboard({ : ' '.repeat(miniWidth); const miniTxt = total > 0 - ? `${fmt(processed)}/${fmt(total)} (${pctw}%)` + ? `${fmtNum(processed)}/${fmtNum(total)} (${pctw}%)` : colors.dim('—'); return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; }); - // Hotkeys help - const maxDigit = Math.min(poolSize - 1, 9); - const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; - const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; - const hotkeysLine = final - ? colors.dim( - 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', - ) - : colors.dim( - `Hotkeys: [${digitRange}] attach${extra} • ` + - 'e=errors • w=warnings • l=logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit', - ); + // Multi-line hotkeys + always-visible export targets + const makeExportLine = ( + key: 'E' | 'W' | 'I' | 'A' | 'F', + label: string, + status?: ExportArtifactStatus, + fallbackName?: string, // used if status.path is missing + ): string => { + const exported = !!status?.exported; + const path = + status?.path || + (exportsDir + ? join( + exportsDir, + fallbackName ?? `${label.toLowerCase().replace(/\s+/g, '-')}.log`, + ) + : '(set exportsDir)'); + const time = fmtTime(status?.savedAt); + const prefix = exported ? colors.green('●') : colors.dim('○'); + const text = `${prefix}: ${key}=export-${label}: ${path} ${colors.dim( + `(last saved: ${time})`, + )}`; + return exported ? colors.green(text) : colors.dim(text); + }; + + const hotkeysHeader = colors.dim( + 'Hotkeys: digits attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit', + ); + + const hotkeyExportLines = [ + makeExportLine('E', 'errors', exportStatus.error, 'combined-errors.log'), + makeExportLine('W', 'warns', exportStatus.warn, 'combined-warns.log'), + makeExportLine('I', 'info', exportStatus.info, 'combined-info.log'), + makeExportLine('A', 'all', exportStatus.all, 'combined-all.log'), + makeExportLine( + 'F', + 'failures-csv', + exportStatus.failuresCsv, + 'failing-updates.csv', + ), + ]; const frame = [ ...header, @@ -267,7 +413,8 @@ export function renderDashboard({ '', ...workerLines, '', - hotkeysLine, + hotkeysHeader, + ...hotkeyExportLines, ] .filter(Boolean) .join('\n'); @@ -284,3 +431,148 @@ export function renderDashboard({ } process.stdout.write(`${frame}\n`); } + +/* ------------------------------------------------------------ + * Export helpers (used by key handlers in impl.ts) + * ------------------------------------------------------------ */ + +/** + * + * @param slotLogPaths + * @param kind + * @param outDir + */ +export async function exportCombinedLogs( + slotLogPaths: Map | undefined>, + kind: LogExportKind, + outDir: string, +): Promise { + mkdirSync(outDir, { recursive: true }); + + const ts = new Date().toISOString().replace(/[:.]/g, '-'); + const outPath = + kind === 'error' + ? join(outDir, `combined-errors-${ts}.log`) + : kind === 'warn' + ? join(outDir, `combined-warns-${ts}.log`) + : kind === 'info' + ? join(outDir, `combined-info-${ts}.log`) + : join(outDir, `combined-all-${ts}.log`); + + const lines: string[] = []; + + for (const [, paths] of slotLogPaths) { + if (!paths) continue; + + const readSafe = (p?: string) => { + try { + return p ? readFileSync(p, 'utf8') : ''; + } catch { + return ''; + } + }; + + if (kind === 'all') { + const blobs = [ + paths.outPath, + paths.errPath, + (paths as any).structuredPath, + ] + .filter(Boolean) + .map((p) => readSafe(p)); + blobs.forEach((text) => { + if (!text) return; + lines.push(...text.split('\n').filter(Boolean)); + }); + continue; + } + + if (kind === 'info') { + const infoPath = (paths as any).infoPath as string | undefined; + const text = readSafe(infoPath) || readSafe(paths.outPath); + if (!text) continue; + lines.push(...text.split('\n').filter(Boolean)); + continue; + } + + if (kind === 'warn') { + const warnPath = (paths as any).warnPath as string | undefined; + let text = readSafe(warnPath); + if (!text) { + const stderr = readSafe(paths.errPath); + if (stderr) { + const blocks = extractBlocks( + stderr, + (cl) => isWarn(cl) && !isError(cl), + ); + if (blocks.length) text = blocks.join('\n\n'); + } + } + if (!text) continue; + lines.push(...text.split('\n').filter(Boolean)); + continue; + } + + // kind === 'error' + const errorPath = (paths as any).errorPath as string | undefined; + let text = readSafe(errorPath); + if (!text) { + const stderr = readSafe(paths.errPath); + if (stderr) { + const blocks = extractBlocks(stderr, (cl) => isError(cl)); + if (blocks.length) text = blocks.join('\n\n'); + } + } + if (!text) continue; + lines.push(...text.split('\n').filter(Boolean)); + } + + // naive time-sort + lines.sort((a, b) => { + const ta = a.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + const tb = b.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + return ta.localeCompare(tb); + }); + + writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); + return outPath; +} + +/** + * + * @param rows + * @param outPath + */ +export async function writeFailingUpdatesCsv( + rows: Array>, + outPath: string, +): Promise { + mkdirSync(join(outPath, '..'), { recursive: true }); + + const headers = Array.from( + rows.reduce>((acc, row) => { + Object.keys(row || {}).forEach((k) => acc.add(k)); + return acc; + }, new Set()), + ); + + const esc = (v: unknown): string => { + if (v == null) return ''; + const s = + typeof v === 'string' + ? v + : typeof v === 'object' + ? JSON.stringify(v) + : String(v); + if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`; + return s; + }; + + const lines = [ + headers.join(','), + ...rows.map((r) => headers.map((h) => esc((r as any)[h])).join(',')), + ]; + + writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); + return outPath; +} diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 378c0b18..0376a1b3 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -187,18 +187,11 @@ export async function parsePreferenceManagementCsvWithCache( const previous = seenAlready[primaryKey]; const diffs = Object.entries(pref) .filter(([key, value]) => previous[key] !== value) - .map( - ([key, value]) => - ` "${key}": previous="${previous[key]}", current="${value}"`, - ) - .join('\n'); - const sameValues = Object.entries(pref) - .filter(([key, value]) => previous[key] === value) - .map(([key, value]) => ` "${key}": value="${value}"`) - .join('\n'); + .map(([key]) => key) + .join(', '); logger.warn( colors.yellow( - `Duplicate primary key found, merging: "${primaryKey}"\nDiff:\n${diffs}\nSame Values:\n${sameValues}`, + `Duplicate primary key "${primaryKey}" at index ${ind}. Diff: ${diffs}`, ), ); primaryKey = `${primaryKey}___${ind}`; From 451b1cc9951827da3db5ba74dd25095d195d97d7 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sat, 16 Aug 2025 22:12:20 -0700 Subject: [PATCH 29/72] Working --- src/lib/pooling/renderDashboard.ts | 431 +++++++++++++++--- src/lib/pooling/showCombinedLogs.ts | 16 +- .../parsePreferenceIdentifiersFromCsv.ts | 8 +- 3 files changed, 367 insertions(+), 88 deletions(-) diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts index 65d64d04..8d32e91e 100644 --- a/src/lib/pooling/renderDashboard.ts +++ b/src/lib/pooling/renderDashboard.ts @@ -1,10 +1,17 @@ -// renderDashboard.ts -import { basename, join } from 'node:path'; +// renderDashboard.ts — rewritten to make export artifacts easy to OPEN / REVEAL / COPY +// - Keeps the existing dashboard renderer and export helpers +// - Adds cross‑platform helpers: openExport, revealExport, copyExportPath +// - Prints OSC‑8 hyperlinks *and* plain absolute paths *and* file:// URLs +// - Writes a sidecar index file with the latest artifact paths for quick copy + +import { basename, dirname, join, resolve } from 'node:path'; import colors from 'colors'; import * as readline from 'node:readline'; -import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; import type { WorkerState } from './assignWorkToWorker'; import type { getWorkerLogPaths } from './spawnWorkerProcess'; +import { pathToFileURL } from 'node:url'; +import { spawn } from 'node:child_process'; /** Upload-mode totals (final-ish counters derived from receipts) */ export type UploadModeTotals = { @@ -39,6 +46,10 @@ export type AnyTotals = UploadModeTotals | CheckModeTotals; /** Export kinds we support in the UI */ export type LogExportKind = 'error' | 'warn' | 'info' | 'all'; +/** + * + */ +export type ExportKindWithCsv = LogExportKind | 'failures-csv'; /** Status for a single export artifact */ export interface ExportArtifactStatus { @@ -66,28 +77,26 @@ export interface ExportStatusMap { /** Render options for the dashboard */ export interface RenderDashboardInput { - /** Number of live workers in the pool */ - poolSize: number; - /** CPU count hint shown to user */ - cpuCount: number; - - /** Total files discovered at start */ - filesTotal: number; - /** Completed files count */ - filesCompleted: number; - /** Failed files count */ - filesFailed: number; - - /** Live map of worker state (includes per-worker progress) */ - workerState: Map; + /** */ + poolSize: number; // Number of live workers in the pool + /** */ + cpuCount: number; // CPU count hint shown to user - /** Mode-specific aggregates to print under the title */ - totals?: AnyTotals; + /** */ + filesTotal: number; // Total files discovered at start + /** */ + filesCompleted: number; // Completed files count + /** */ + filesFailed: number; // Failed files count - /** Final frame? If true, show the cursor and freeze the output */ - final?: boolean; + /** */ + workerState: Map; // Live map of worker state + /** */ + totals?: AnyTotals; // Mode-specific aggregates + /** */ + final?: boolean; // Final frame? If true, show the cursor and freeze the output - /** Live throughput numbers (records/sec) */ + /** */ throughput?: { /** */ successSoFar: number; @@ -97,21 +106,15 @@ export interface RenderDashboardInput { r60s: number; }; - /** - * Directory where export artifacts (combined logs / CSV) are written. - * Always shown so users know where to find files. - */ + /** Directory where export artifacts (combined logs / CSV) are written. */ exportsDir?: string; - /** - * Current (latest) artifact status per kind. When a kind has never been - * exported this session, the line renders in dim text and shows a sensible - * default path in exportsDir so users know where it will appear. - */ + /** Current (latest) artifact status per kind. */ exportStatus?: ExportStatusMap; } let lastFrame = ''; +let lastIndexFileContents = ''; /* ------------------------------------------------------------ * Small helpers @@ -122,22 +125,42 @@ const isError = (t: string) => /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); const isWarn = (t: string) => /\b(WARN|WARNING)\b/i.test(t); +const fmtNum = (n: number): string => n.toLocaleString(); +const fmtTime = (ts?: number): string => + ts ? new Date(ts).toLocaleTimeString() : '—'; + /** - * Consider a line a “header” if it looks like the start of a new log record. - * Helps us group multi-line WARN/ERROR blocks. * * @param t */ -const isNewHeader = (t: string) => - isError(t) || - isWarn(t) || - /^\s*\[w\d+\]/.test(t) || - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(t); +function isNewHeader(t: string) { + return ( + isError(t) || + isWarn(t) || + /^\s*\[w\d+\]/.test(t) || + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(t) + ); +} + +/** + * + * @param absPath + * @param label + */ +function osc8Link(absPath: string, label?: string): string { + if (!absPath || absPath.startsWith('(')) return label ?? absPath; // Skip placeholders + try { + const { href } = pathToFileURL(absPath); // file:///… URL + const OSC = '\u001B]8;;'; + const BEL = '\u0007'; + const text = label ?? absPath; // may contain SGR color codes + return `${OSC}${href}${BEL}${text}${OSC}${BEL}`; + } catch { + return label ?? absPath; + } +} /** - * Given raw text of a log file, return only the multi-line "blocks" - * whose first line satisfies `starts` (e.g., isWarn/isError). Blocks - * continue until the next header-like line or a blank line. * * @param text * @param starts @@ -185,21 +208,218 @@ function extractBlocks( return out.filter(Boolean); } -const fmtNum = (n: number): string => n.toLocaleString(); -const fmtTime = (ts?: number): string => - ts ? new Date(ts).toLocaleTimeString() : '—'; +/** + * Compute an absolute path for a would‑be artifact (even before it exists). + * + * @param kind + * @param exportsDir + * @param status + */ +function artifactAbsPath( + kind: ExportKindWithCsv, + exportsDir?: string, + status?: ExportArtifactStatus, +): string { + const fallbackName = + kind === 'error' + ? 'combined-errors.log' + : kind === 'warn' + ? 'combined-warns.log' + : kind === 'info' + ? 'combined-info.log' + : kind === 'all' + ? 'combined-all.log' + : 'failing-updates.csv'; + + const rawPath = + status?.path || + (exportsDir ? join(exportsDir, fallbackName) : '(set exportsDir)'); + return rawPath.startsWith('(') ? rawPath : resolve(rawPath); +} + +/** + * Create (if needed) an exports index file summarizing artifact paths. + * + * @param exportsDir + * @param exportStatus + */ +function writeExportsIndex( + exportsDir?: string, + exportStatus?: ExportStatusMap, +): string | undefined { + if (!exportsDir) return undefined; + const lines: string[] = ['# Export artifacts — latest paths', '']; + + const kinds: Array< + [ExportKindWithCsv, ExportArtifactStatus | undefined, string] + > = [ + ['error', exportStatus?.error, 'Errors log'], + ['warn', exportStatus?.warn, 'Warnings log'], + ['info', exportStatus?.info, 'Info log'], + ['all', exportStatus?.all, 'All logs'], + ['failures-csv', exportStatus?.failuresCsv, 'Failing updates (CSV)'], + ]; + + for (const [k, st, label] of kinds) { + const abs = artifactAbsPath(k, exportsDir, st); + const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; + lines.push(`${label}:`); + lines.push(` path: ${abs}`); + lines.push(` url: ${url}`); + lines.push(''); + } + + const content = lines.join('\n'); + if (content === lastIndexFileContents) return join(exportsDir, 'exports.index.txt'); + + mkdirSync(exportsDir, { recursive: true }); + const out = join(exportsDir, 'exports.index.txt'); + writeFileSync(out, `${content}\n`, 'utf8'); + lastIndexFileContents = content; + return out; +} /* ------------------------------------------------------------ - * Main renderer + * Cross‑platform OPEN / REVEAL / COPY helpers * ------------------------------------------------------------ */ /** - * Render the multi-worker upload dashboard. * - * Always shows: - * - Exports directory - * - One line per export hotkey with the target file path, - * last saved time, and green coloring after first success + * @param cmd + * @param args + */ +function spawnDetached(cmd: string, args: string[]) { + try { + const child = spawn(cmd, args, { stdio: 'ignore', detached: true }); + child.unref(); + return true; + } catch { + return false; + } +} + +/** + * + * @param p + */ +async function openPath(p: string): Promise { + if (!p || p.startsWith('(')) return false; + if (process.platform === 'darwin') return spawnDetached('open', [p]); + if (process.platform === 'win32') return spawnDetached('cmd', ['/c', 'start', '', p]); + return spawnDetached('xdg-open', [p]); +} + +/** + * + * @param p + */ +async function revealInFileManager(p: string): Promise { + if (!p || p.startsWith('(')) return false; + if (process.platform === 'darwin') return spawnDetached('open', ['-R', p]); + if (process.platform === 'win32') return spawnDetached('explorer.exe', ['/select,', p]); + // Linux: best effort — open folder + return spawnDetached('xdg-open', [dirname(p)]); +} + +/** + * + * @param text + */ +async function copyToClipboard(text: string): Promise { + if (!text || text.startsWith('(')) return false; + try { + if (process.platform === 'darwin') { + const p = spawn('pbcopy'); + p.stdin?.write(text); + p.stdin?.end(); + return true; + } + if (process.platform === 'win32') { + const p = spawn('clip'); + p.stdin?.write(text.replace(/\n/g, '\r\n')); + p.stdin?.end(); + return true; + } + // Linux: try xclip, then xsel + try { + const p = spawn('xclip', ['-selection', 'clipboard']); + p.stdin?.write(text); + p.stdin?.end(); + return true; + } catch {} + try { + const p2 = spawn('xsel', ['--clipboard', '--input']); + p2.stdin?.write(text); + p2.stdin?.end(); + return true; + } catch {} + } catch {} + return false; +} + +/** + * Action helpers you can call from your key handlers in impl.ts + * + * @param kind + * @param exportsDir + * @param exportStatus + */ +export async function openExport( + kind: ExportKindWithCsv, + exportsDir?: string, + exportStatus?: ExportStatusMap, +): Promise<{ + /** */ ok: boolean /** */; + /** */ + path: string; +}> { + const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); + return { ok: await openPath(path), path }; +} + +/** + * + * @param kind + * @param exportsDir + * @param exportStatus + */ +export async function revealExport( + kind: ExportKindWithCsv, + exportsDir?: string, + exportStatus?: ExportStatusMap, +): Promise<{ + /** */ ok: boolean /** */; + /** */ + path: string; +}> { + const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); + return { ok: await revealInFileManager(path), path }; +} + +/** + * + * @param kind + * @param exportsDir + * @param exportStatus + */ +export async function copyExportPath( + kind: ExportKindWithCsv, + exportsDir?: string, + exportStatus?: ExportStatusMap, +): Promise<{ + /** */ ok: boolean /** */; + /** */ + path: string; +}> { + const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); + return { ok: await copyToClipboard(path), path }; +} + +/* ------------------------------------------------------------ + * Main renderer + * ------------------------------------------------------------ */ + +/** * * @param root0 */ @@ -268,6 +488,33 @@ export function renderDashboard({ } } + // Estimate expected completion time (ETA) + let etaText = ''; + if (throughput && estTotalJobsText && avgJobsPerCompletedFile !== undefined) { + const est = + (jobsFromReceipts ?? 0) + + inflightJobsKnown + + remainingFiles * avgJobsPerCompletedFile; + const uploaded = throughput.successSoFar; + const remainingJobs = Math.max(est - uploaded, 0); + const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; + if (ratePerSec > 0 && remainingJobs > 0) { + const secondsLeft = Math.round(remainingJobs / ratePerSec); + const eta = new Date(Date.now() + secondsLeft * 1000); + const hours = Math.floor(secondsLeft / 3600); + const minutes = Math.floor((secondsLeft % 3600) / 60); + const timeLeft = + hours > 0 + ? `${hours}h ${minutes}m` + : minutes > 0 + ? `${minutes}m` + : `${secondsLeft}s`; + etaText = colors.magenta( + `Expected completion: ${eta.toLocaleTimeString()} (${timeLeft} left)`, + ); + } + } + // Header const header = [ `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( @@ -279,12 +526,10 @@ export function renderDashboard({ filesFailed, fmtNum(filesFailed), )} ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, - `[${bar}] ${pct}% ${estTotalJobsText}`, + `[${bar}] ${pct}% ${estTotalJobsText} ${etaText}`, ]; - if (exportsDir) { - header.push(colors.dim(`Exports dir: ${exportsDir}`)); - } + if (exportsDir) header.push(colors.dim(`Exports dir: ${exportsDir}`)); // Totals block let totalsBlock = ''; @@ -365,15 +610,28 @@ export function renderDashboard({ return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; }); - // Multi-line hotkeys + always-visible export targets + // Base hotkeys (viewer keys rendered like before; export keys moved below) + const maxDigit = Math.min(poolSize - 1, 9); + const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + const hotkeysLine = final + ? colors.dim( + 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', + ) + : colors.dim( + `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit`, + ); + + // Pretty export block — one line per export key with path + indicator const makeExportLine = ( key: 'E' | 'W' | 'I' | 'A' | 'F', label: string, status?: ExportArtifactStatus, - fallbackName?: string, // used if status.path is missing + fallbackName?: string, ): string => { const exported = !!status?.exported; - const path = + + const rawPath = status?.path || (exportsDir ? join( @@ -381,30 +639,51 @@ export function renderDashboard({ fallbackName ?? `${label.toLowerCase().replace(/\s+/g, '-')}.log`, ) : '(set exportsDir)'); + + const abs = rawPath.startsWith('(') ? rawPath : resolve(rawPath); const time = fmtTime(status?.savedAt); - const prefix = exported ? colors.green('●') : colors.dim('○'); - const text = `${prefix}: ${key}=export-${label}: ${path} ${colors.dim( - `(last saved: ${time})`, - )}`; - return exported ? colors.green(text) : colors.dim(text); + const dot = exported ? colors.green('●') : colors.dim('○'); + const hotkey = colors.bold(`${key}=export-${label}`); + + const openText = exported ? colors.green('open') : colors.dim('open'); + const openLink = osc8Link(abs, openText); + const plainPath = exported ? colors.green(abs) : colors.dim(abs); + const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; + + return ( + `${dot} ${hotkey}: ${openLink} ${plainPath} ${colors.dim( + `(last saved: ${time})`, + )}\n` + ` ${colors.dim('url:')} ${url}` + ); }; - const hotkeysHeader = colors.dim( - 'Hotkeys: digits attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+C exit', - ); - - const hotkeyExportLines = [ - makeExportLine('E', 'errors', exportStatus.error, 'combined-errors.log'), - makeExportLine('W', 'warns', exportStatus.warn, 'combined-warns.log'), - makeExportLine('I', 'info', exportStatus.info, 'combined-info.log'), - makeExportLine('A', 'all', exportStatus.all, 'combined-all.log'), - makeExportLine( + const exportBlock = [ + colors.dim('Exports (Cmd/Ctrl‑click “open” or copy the plain path):'), + ` ${makeExportLine( + 'E', + 'errors', + exportStatus.error, + 'combined-errors.log', + )}`, + ` ${makeExportLine( + 'W', + 'warns', + exportStatus.warn, + 'combined-warns.log', + )}`, + ` ${makeExportLine('I', 'info', exportStatus.info, 'combined-info.log')}`, + ` ${makeExportLine('A', 'all', exportStatus.all, 'combined-all.log')}`, + ` ${makeExportLine( 'F', 'failures-csv', exportStatus.failuresCsv, 'failing-updates.csv', - ), - ]; + )}`, + colors.dim(' (Also written to exports.index.txt for easy copying.)'), + ].join('\n'); + + // Optionally write/update the sidecar index file each frame (cheap I/O, guarded by memo) + writeExportsIndex(exportsDir, exportStatus); const frame = [ ...header, @@ -413,8 +692,8 @@ export function renderDashboard({ '', ...workerLines, '', - hotkeysHeader, - ...hotkeyExportLines, + hotkeysLine, + exportBlock ? `\n${exportBlock}` : '', ] .filter(Boolean) .join('\n'); diff --git a/src/lib/pooling/showCombinedLogs.ts b/src/lib/pooling/showCombinedLogs.ts index 63c8434f..1f54a36f 100644 --- a/src/lib/pooling/showCombinedLogs.ts +++ b/src/lib/pooling/showCombinedLogs.ts @@ -32,19 +32,15 @@ export function showCombinedLogs( const files: Array<{ /** */ path: string /** */; + /** */ src: 'out' | 'err' | 'structured' | 'warn' | 'info'; }> = []; for (const which of whichList) { - if (which === 'out' && paths.outPath) - files.push({ path: paths.outPath, src: 'out' }); - if (which === 'err' && paths.errPath) - files.push({ path: paths.errPath, src: 'err' }); - if (which === 'structured' && (paths as any).structuredPath) - files.push({ path: (paths as any).structuredPath, src: 'structured' }); - if ((paths as any).warnPath && which === 'warn') - files.push({ path: (paths as any).warnPath, src: 'warn' }); - if ((paths as any).infoPath && which === 'info') - files.push({ path: (paths as any).infoPath, src: 'info' }); + if (which === 'out' && paths.outPath) files.push({ path: paths.outPath, src: 'out' }); + if (which === 'err' && paths.errPath) files.push({ path: paths.errPath, src: 'err' }); + if (which === 'structured' && (paths as any).structuredPath) files.push({ path: (paths as any).structuredPath, src: 'structured' }); + if ((paths as any).warnPath && which === 'warn') files.push({ path: (paths as any).warnPath, src: 'warn' }); + if ((paths as any).infoPath && which === 'info') files.push({ path: (paths as any).infoPath, src: 'info' }); } for (const { path, src } of files) { diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 87db1c67..588c7482 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -259,6 +259,9 @@ export async function addTranscendIdToPreferences( // return preferences; // } // Add a transcendent ID to each preference if it doesn't already exist + const emailList = (process.env.EMAIL_LIST || '') + .split(',') + .map((email) => email.trim()); const disallowedEmails = [ 'noemail@costco.com', 'NOEMAILYET@GMAIL.COM', @@ -359,11 +362,12 @@ export async function addTranscendIdToPreferences( 'none@yahoo.com', '123@LIVE.COM', // FIXME - ...(process.env.EMAIL_LIST || '').split(',').map((email) => email.trim()), + ...emailList, ].map((email) => email.toLowerCase()); + console.log(emailList[0], emailList[50]); return preferences.map((pref) => { - const email = (pref.email_address || '').toLowerCase(); + const email = (pref.email_address || '').toLowerCase().trim(); return { ...pref, person_id: pref.person_id !== '-2' ? pref.person_id : '', From 5910ce93383a658d930f125a65056a163aa87daf Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 00:47:35 -0700 Subject: [PATCH 30/72] refactor of pooling --- .vscode/settings.json | 3 + README.md | 3325 ----------------- .../artifacts/ExportManager.ts | 173 + .../artifacts/artifactAbsPath.ts | 46 + .../upload-preferences/artifacts/index.ts | 4 + .../artifacts/writeExportsIndex.ts | 57 + .../artifacts/writeFailingUpdatesCsv.ts | 41 + .../upload-preferences/buildTaskOptions.ts | 92 + .../consent/upload-preferences/impl.ts | 527 +-- .../receipts/applyReceiptSummary.ts | 60 + .../upload-preferences/receipts/index.ts | 5 + .../receipts/readFailingUpdatesFromReceipt.ts | 49 + .../{ => receipts}/receiptsState.ts | 2 +- .../{ => receipts}/resolveReceiptPath.ts | 2 +- .../receipts/summarizeReceipt.ts | 64 + .../consent/upload-preferences/runChild.ts | 55 +- .../upload-preferences/ui/buildFrameModel.ts | 164 + .../upload-preferences/ui/headerLines.ts | 228 ++ .../consent/upload-preferences/ui/index.ts | 3 + .../upload-preferences/ui/renderDashboard.ts | 47 + .../upload/buildInteractiveUploadPlan.ts | 2 +- .../interactivePreferenceUploaderFromPlan.ts | 5 +- src/lib/helpers/index.ts | 1 + src/lib/helpers/readSafe.ts | 16 + src/lib/pooling/assignWorkToWorker.ts | 1 + src/lib/pooling/attachWorkerHandlers.ts | 244 +- src/lib/pooling/diagnostics.ts | 59 + src/lib/pooling/downloadArtifact.ts | 222 -- src/lib/pooling/exportCombinedLogs.ts | 131 + src/lib/pooling/index.ts | 17 +- src/lib/pooling/installInteractiveSwitcher.ts | 227 +- src/lib/pooling/ipc.ts | 106 + src/lib/pooling/keymap.ts | 75 + src/lib/pooling/keypressExtra.ts | 120 + src/lib/pooling/logRotation.ts | 160 +- src/lib/pooling/os.ts | 90 + src/lib/pooling/osc8Link.ts | 21 + src/lib/pooling/renderDashboard.ts | 857 ----- src/lib/pooling/replayTail.ts | 27 + src/lib/pooling/safeGetLogPathsForSlot.ts | 32 + src/lib/pooling/showCombinedLogs.ts | 45 +- src/lib/pooling/spawnWorkerProcess.ts | 33 +- src/lib/pooling/workerAssignment.ts | 104 + src/lib/pooling/workerIds.ts | 35 + .../parsePreferenceIdentifiersFromCsv.ts | 4 +- 45 files changed, 2457 insertions(+), 5124 deletions(-) create mode 100644 src/commands/consent/upload-preferences/artifacts/ExportManager.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/artifactAbsPath.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/index.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/writeExportsIndex.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts create mode 100644 src/commands/consent/upload-preferences/buildTaskOptions.ts create mode 100644 src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts create mode 100644 src/commands/consent/upload-preferences/receipts/index.ts create mode 100644 src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts rename src/commands/consent/upload-preferences/{ => receipts}/receiptsState.ts (98%) rename src/commands/consent/upload-preferences/{ => receipts}/resolveReceiptPath.ts (95%) create mode 100644 src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts create mode 100644 src/commands/consent/upload-preferences/ui/buildFrameModel.ts create mode 100644 src/commands/consent/upload-preferences/ui/headerLines.ts create mode 100644 src/commands/consent/upload-preferences/ui/index.ts create mode 100644 src/commands/consent/upload-preferences/ui/renderDashboard.ts create mode 100644 src/lib/helpers/readSafe.ts create mode 100644 src/lib/pooling/diagnostics.ts delete mode 100644 src/lib/pooling/downloadArtifact.ts create mode 100644 src/lib/pooling/exportCombinedLogs.ts create mode 100644 src/lib/pooling/ipc.ts create mode 100644 src/lib/pooling/keymap.ts create mode 100644 src/lib/pooling/keypressExtra.ts create mode 100644 src/lib/pooling/os.ts create mode 100644 src/lib/pooling/osc8Link.ts delete mode 100644 src/lib/pooling/renderDashboard.ts create mode 100644 src/lib/pooling/replayTail.ts create mode 100644 src/lib/pooling/safeGetLogPathsForSlot.ts create mode 100644 src/lib/pooling/workerAssignment.ts create mode 100644 src/lib/pooling/workerIds.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index bf64b790..fbf0a61b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -61,6 +61,9 @@ "preact", "pubspec", "Requirize", + "respawned", + "respawning", + "respawns", "retryable", "sombra", "subdatapoint", diff --git a/README.md b/README.md index 828ba841..e69de29b 100644 --- a/README.md +++ b/README.md @@ -1,3325 +0,0 @@ -# Transcend CLI - - - - -## Table of Contents - -- [Changelog](#changelog) -- [Overview](#overview) -- [Installation](#installation) -- [transcend.yml](#transcendyml) -- [Usage](#usage) - - [`transcend request approve`](#transcend-request-approve) - - [Examples](#examples) - - [`transcend request upload`](#transcend-request-upload) - - [Examples](#examples-1) - - [`transcend request download-files`](#transcend-request-download-files) - - [Examples](#examples-2) - - [`transcend request cancel`](#transcend-request-cancel) - - [Examples](#examples-3) - - [`transcend request restart`](#transcend-request-restart) - - [Examples](#examples-4) - - [`transcend request notify-additional-time`](#transcend-request-notify-additional-time) - - [Examples](#examples-5) - - [`transcend request mark-silent`](#transcend-request-mark-silent) - - [Examples](#examples-6) - - [`transcend request enricher-restart`](#transcend-request-enricher-restart) - - [Examples](#examples-7) - - [`transcend request reject-unverified-identifiers`](#transcend-request-reject-unverified-identifiers) - - [Examples](#examples-8) - - [`transcend request export`](#transcend-request-export) - - [Examples](#examples-9) - - [`transcend request skip-preflight-jobs`](#transcend-request-skip-preflight-jobs) - - [Examples](#examples-10) - - [`transcend request system mark-request-data-silos-completed`](#transcend-request-system-mark-request-data-silos-completed) - - [Examples](#examples-11) - - [`transcend request system retry-request-data-silos`](#transcend-request-system-retry-request-data-silos) - - [Examples](#examples-12) - - [`transcend request system skip-request-data-silos`](#transcend-request-system-skip-request-data-silos) - - [Examples](#examples-13) - - [`transcend request preflight pull-identifiers`](#transcend-request-preflight-pull-identifiers) - - [Examples](#examples-14) - - [`transcend request preflight push-identifiers`](#transcend-request-preflight-push-identifiers) - - [Examples](#examples-15) - - [`transcend request cron pull-identifiers`](#transcend-request-cron-pull-identifiers) - - [Examples](#examples-16) - - [`transcend request cron mark-identifiers-completed`](#transcend-request-cron-mark-identifiers-completed) - - [Examples](#examples-17) - - [`transcend consent build-xdi-sync-endpoint`](#transcend-consent-build-xdi-sync-endpoint) - - [Examples](#examples-18) - - [`transcend consent pull-consent-metrics`](#transcend-consent-pull-consent-metrics) - - [Examples](#examples-19) - - [`transcend consent pull-consent-preferences`](#transcend-consent-pull-consent-preferences) - - [Examples](#examples-20) - - [`transcend consent update-consent-manager`](#transcend-consent-update-consent-manager) - - [Examples](#examples-21) - - [`transcend consent upload-consent-preferences`](#transcend-consent-upload-consent-preferences) - - [Examples](#examples-22) - - [`transcend consent upload-cookies-from-csv`](#transcend-consent-upload-cookies-from-csv) - - [Examples](#examples-23) - - [`transcend consent upload-data-flows-from-csv`](#transcend-consent-upload-data-flows-from-csv) - - [Examples](#examples-24) - - [`transcend consent upload-preferences`](#transcend-consent-upload-preferences) - - [Examples](#examples-25) - - [`transcend inventory pull`](#transcend-inventory-pull) - - [Scopes](#scopes) - - [Examples](#examples-26) - - [`transcend inventory push`](#transcend-inventory-push) - - [Scopes](#scopes-1) - - [Examples](#examples-27) - - [CI Integration](#ci-integration) - - [Dynamic Variables](#dynamic-variables) - - [`transcend inventory scan-packages`](#transcend-inventory-scan-packages) - - [Examples](#examples-28) - - [`transcend inventory discover-silos`](#transcend-inventory-discover-silos) - - [Examples](#examples-29) - - [`transcend inventory pull-datapoints`](#transcend-inventory-pull-datapoints) - - [Examples](#examples-30) - - [`transcend inventory pull-unstructured-discovery-files`](#transcend-inventory-pull-unstructured-discovery-files) - - [Examples](#examples-31) - - [`transcend inventory derive-data-silos-from-data-flows`](#transcend-inventory-derive-data-silos-from-data-flows) - - [Examples](#examples-32) - - [`transcend inventory derive-data-silos-from-data-flows-cross-instance`](#transcend-inventory-derive-data-silos-from-data-flows-cross-instance) - - [Examples](#examples-33) - - [`transcend inventory consent-manager-service-json-to-yml`](#transcend-inventory-consent-manager-service-json-to-yml) - - [Examples](#examples-34) - - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) - - [Examples](#examples-35) - - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) - - [Examples](#examples-36) - - [`transcend migration sync-ot`](#transcend-migration-sync-ot) - - [Authentication](#authentication) - - [Examples](#examples-37) -- [Prompt Manager](#prompt-manager) -- [Proxy usage](#proxy-usage) - - - -## Changelog - -To stay up to date on breaking changes to the CLI between major version updates, please refer to [CHANGELOG.md](CHANGELOG.md). - -## Overview - -A command line interface that allows you to programatically interact with the Transcend. - -## Installation - -This package is distributed through npm, and assumes an installation of [npm and Node](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). - -```sh -npm install --global @transcend-io/cli -transcend --help -``` - -You can also run the CLI using npx: - -```sh -npx -p @transcend-io/cli -- transcend --help -``` - -Note - -_The CLI commands which interact with Transcend's API will default to using Transcend's EU backend. To use these commands with the US backend, you will need to add the flag --transcendUrl=https://api.us.transcend.io. You can also set the environment variable `TRANSCEND_API_URL=https://api.us.transcend.io`_ - -## transcend.yml - -Within your git repositories, you can define a file `transcend.yml`. This file allows you define part of your Data Map in code. Using the CLI, you can sync that configuration back to the Transcend Admin Dashboard (https://app.transcend.io/privacy-requests/connected-services). - -You can find various examples for your `transcend.yml` file in the [examples/](./examples/) folder. If you are looking for a starting point to copy and paste, [simple.yml](./examples/simple.yml) is a good place to start. This file is annotated with links and documentations that new members of your team can use if they come across the file. - -The API for this YAML file can be found in [./src/codecs.ts](./src/codecs.ts) under the variable named "TranscendInput". The shape of the YAML file will be type-checked every time a command is run. - -By default, your editor or IDE should recognize `transcend.yml` and validate it against our latest published [JSON schema](./transcend-yml-schema-latest.json). This is dependent on whether your editor uses [yaml-language-server](https://github.com/redhat-developer/yaml-language-server), such as through the [VS Code YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml). - -Your editor will use the latest version's schema. To pin the `transcend.yml` schema to a previous major version, include this at the top of your file (and change `v4` to your target major version): - -```yml -# yaml-language-server: $schema=https://raw.githubusercontent.com/transcend-io/cli/main/transcend-yml-schema-v4.json -``` - -The structure of `transcend.yml` looks something like the following: - -```yaml -# Manage at: https://app.transcend.io/infrastructure/api-keys -# See https://docs.transcend.io/docs/authentication -# Define API keys that may be shared across data silos -# in the data map. When creating new data silos through the YAML -# CLI, it is possible to specify which API key should be associated -# with the newly created data silo. -api-keys: - - title: Webhook Key - - title: Analytics Key - -# Manage at: https://app.transcend.io/privacy-requests/identifiers -# See https://docs.transcend.io/docs/identity-enrichment -# Define enricher or pre-flight check webhooks that will be executed -# prior to privacy request workflows. Some examples may include: -# - identity enrichment: look up additional identifiers for that user. -# i.e. map an email address to a user ID -# - fraud check: auto-cancel requests if the user is flagged for fraudulent behavior -# - customer check: auto-cancel request for some custom business criteria -enrichers: - - title: Basic Identity Enrichment - description: Enrich an email address to the userId and phone number - url: https://example.acme.com/transcend-enrichment-webhook - input-identifier: email - output-identifiers: - - userId - - phone - - myUniqueIdentifier - - title: Fraud Check - description: Ensure the email address is not marked as fraudulent - url: https://example.acme.com/transcend-fraud-check - input-identifier: email - output-identifiers: - - email - privacy-actions: - - ERASURE - -# Manage at: https://app.transcend.io/privacy-requests/connected-services -# See https://docs.transcend.io/docs/the-data-map#data-silos -# Define the data silos in your data map. A data silo can be a database, -# or a web service that may use a collection of different data stores under the hood. -data-silos: - # Note: title is the only required top-level field for a data silo - - title: Redshift Data Warehouse - description: The mega-warehouse that contains a copy over all SQL backed databases - integrationName: server - url: https://example.acme.com/transcend-webhook - api-key-title: Webhook Key - data-subjects: - - customer - - employee - - newsletter-subscriber - - b2b-contact - identity-keys: - - email - - userId - deletion-dependencies: - - Identity Service - owners: - - alice@transcend.io - datapoints: - - title: Webhook Notification - key: _global - privacy-actions: - - ACCESS - - ERASURE - - SALE_OPT_OUT - - title: User Model - description: The centralized user model user - key: users - privacy-actions: - - ACCESS - fields: - - key: firstName - title: First Name - description: The first name of the user, inputted during onboarding - - key: email - title: Email - description: The email address of the user -``` - -## Usage - - - -### `transcend request approve` - -```txt -USAGE - transcend request approve (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--origins PRIVACY_CENTER|ADMIN_DASHBOARD|API|SHOPIFY] [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--transcendUrl value] [--concurrency value] - transcend request approve --help - -Bulk approve a set of privacy requests from the DSR Automation -> Incoming Requests tab. - -FLAGS - --auth The Transcend API key. Requires scopes: "Request Approval and Communication", "View Incoming Requests", "Manage Request Compilation" - --actions The request actions to approve [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--origins] The request origins to approve [PRIVACY_CENTER|ADMIN_DASHBOARD|API|SHOPIFY, separator = ,] - [--silentModeBefore] Any requests made before this date should be marked as silent mode - [--createdAtBefore] Approve requests that were submitted before this time - [--createdAtAfter] Approve requests that were submitted after this time - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk approve all SALE_OPT_OUT and ERASURE requests** - -```sh -transcend request approve --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --transcendUrl=https://api.us.transcend.io -``` - -**Approve all Erasure requests that came through the API** - -```sh -transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --origins=API -``` - -**Approve all requests, but mark any request made before 05/03/2023 as silent mode to prevent emailing those requests** - -```sh -transcend request approve \ - --auth="$TRANSCEND_API_KEY" \ - --actions=SALE_OPT_OUT \ - --silentModeBefore=2024-05-03T00:00:00.000Z -``` - -**Increase the concurrency (defaults to 50)** - -```sh -transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=100 -``` - -**Approve ERASURE requests created within a specific time frame** - -```sh -transcend request approve \ - --auth="$TRANSCEND_API_KEY" \ - --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z -``` - -### `transcend request upload` - -```txt -USAGE - transcend request upload (--auth value) [--file value] [--transcendUrl value] [--cacheFilepath value] [--requestReceiptFolder value] [--sombraAuth value] [--concurrency value] [--attributes value] [--isTest] [--isSilent] [--skipSendingReceipt] [--emailIsVerified] [--skipFilterStep] [--dryRun] [--debug] [--defaultPhoneCountryCode value] - transcend request upload --help - -Upload a set of requests from a CSV. - -This 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. - -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. - -FLAGS - --auth The Transcend API key. Requires scopes: "Submit New Data Subject Request", "View Identity Verification Settings", "View Global Attributes" - [--file] Path to the CSV file of requests to upload [default = ./requests.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--cacheFilepath] The path to the JSON file encoding the metadata used to map the CSV shape to Transcend API [default = ./transcend-privacy-requests-cache.json] - [--requestReceiptFolder] The path to the folder where receipts of each upload are stored [default = ./privacy-request-upload-receipts] - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] - [--attributes] Tag all of the requests with the following attributes. Format: key1:value1;value2,key2:value3;value4 [default = Tags:transcend-cli] - [--isTest] Flag whether the requests being uploaded are test requests or regular requests [default = false] - [--isSilent/--noIsSilent] Flag whether the requests being uploaded should be submitted in silent mode [default = true] - [--skipSendingReceipt] Flag whether to skip sending of the receipt email [default = false] - [--emailIsVerified/--noEmailIsVerified] Indicate whether the email address being uploaded is pre-verified. Set to false to send a verification email [default = true] - [--skipFilterStep] When true, skip the interactive step to filter down the CSV [default = false] - [--dryRun] When true, perform a dry run of the upload instead of calling the API to submit the requests [default = false] - [--debug] Debug logging [default = false] - [--defaultPhoneCountryCode] When uploading phone numbers, if the phone number is missing a country code, assume this country code [default = 1] - -h --help Print help information and exit -``` - -See a demo of the interactive mapping processbelow (_note: the command is slightly different from the one shown in the video, but the arguments are the same._) - -https://user-images.githubusercontent.com/10264973/205477183-d4762087-668c-43f1-a84c-0fce0ec3e132.mov - -#### Examples - -**Upload requests from a CSV file** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv -``` - -**For self-hosted sombras that use an internal key** - -```sh -transcend request upload \ - --auth="$TRANSCEND_API_KEY" \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" \ - --file=/Users/transcend/Desktop/test.csv -``` - -**Run without being prompted to filter requests** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --skipFilterStep -``` - -**Perform a dry run to see what will be uploaded, without calling the Transcend API** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --dryRun -``` - -**Mark the uploaded requests as test requests** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --isTest -``` - -**Send email communications to the users throughout the request lifecycle** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --isSilent=false -``` - -**Upload requests without sending initial email receipt, but still send later emails** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --skipSendingReceipt -``` - -**Increase the concurrency (defaults to 50)** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --concurrency=100 -``` - -**Specify default country code for phone numbers** - -```sh -transcend request upload \ - --auth="$TRANSCEND_API_KEY" \ - --file=/Users/transcend/Desktop/test.csv \ - --defaultPhoneCountryCode=44 -``` - -**Include debug logs - warning, this logs out personal data** - -```sh -transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --debug -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request upload \ - --auth="$TRANSCEND_API_KEY" \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" \ - --file=/Users/transcend/Desktop/test.csv \ - --transcendUrl=https://api.us.transcend.io -``` - -**Send email verification to user before request continues** - -```sh -transcend request upload \ - --auth="$TRANSCEND_API_KEY" \ - --file=/Users/transcend/Desktop/test.csv \ - --isSilent=false \ - --emailIsVerified=false -``` - -**Tag all uploaded requests with custom fields (formerly known as "attributes")** - -```sh -transcend request upload \ - --auth="$TRANSCEND_API_KEY" \ - --file=/Users/transcend/Desktop/test.csv \ - --attributes=Tags:transcend-cli;my-customer-tag,Customer:acme-corp -``` - -### `transcend request download-files` - -```txt -USAGE - transcend request download-files (--auth value) [--sombraAuth value] [--concurrency value] [--requestIds value]... [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--folderPath value] [--createdAtBefore value] [--createdAtAfter value] [--approveAfterDownload] [--transcendUrl value] - transcend request download-files --help - -Download the files associated with a Data Subject Access Request (DSAR) from DSR Automation -> Incoming Requests tab. - -FLAGS - --auth The Transcend API key. Requires scopes: "View the Request Compilation", "View Incoming Requests", "Request Approval and Communication" - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--concurrency] The concurrency to use when downloading requests in parallel [default = 10] - [--requestIds]... Specify the specific request IDs to download [separator = ,] - [--statuses] The request statuses to download. Comma-separated list. Defaults to APPROVING,DOWNLOADABLE. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--folderPath] The folder to download files to [default = ./dsr-files] - [--createdAtBefore] Download requests that were submitted before this time - [--createdAtAfter] Download requests that were submitted after this time - [--approveAfterDownload] If the request is in status=APPROVING, approve the request after its downloaded [default = false] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -Download the files associated with a Data Subject Access Request (DSAR) from [DSR Automation -> Incoming Requests](https://app.transcend.io/privacy-requests/incoming-requests) tab. - -Screenshot 2025-06-03 at 3 32 00 PM - -#### Examples - -**Download all requests in status=APPROVING or status=DOWNLOADABLE** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io -``` - -**Write files to a specific folder on disk** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" --folderPath=./my-folder -``` - -**Auto approve after download** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" --approveAfterDownload -``` - -**Download requests in APPROVING state only** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" --statuses=APPROVING -``` - -**Increase the concurrency (defaults to 10)** - -```sh -transcend request download-files --auth="$TRANSCEND_API_KEY" --concurrency=100 -``` - -**Download requests in a timeframe** - -```sh -transcend request download-files \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z -``` - -**Download specific requests** - -```sh -transcend request download-files \ - --auth="$TRANSCEND_API_KEY" \ - --requestIds=b8c2ce13-9e40-4104-af79-23c68f2a87ba,d5eedc52-0f85-4034-bc01-14951acad5aa -``` - -### `transcend request cancel` - -```txt -USAGE - transcend request cancel (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--requestIds value]... [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--cancellationTitle value] [--transcendUrl value] [--concurrency value] - transcend request cancel --help - -Bulk cancel a set of privacy requests from the DSR Automation -> Incoming Requests tab. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Incoming Requests", "Request Approval and Communication" - --actions The request actions to cancel [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--statuses] The request statuses to cancel. Comma-separated list. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--requestIds]... Specify the specific request IDs to cancel [separator = ,] - [--silentModeBefore] Any requests made before this date should be marked as silent mode for canceling to skip email sending - [--createdAtBefore] Cancel requests that were submitted before this time - [--createdAtAfter] Cancel requests that were submitted after this time - [--cancellationTitle] The title of the email template that should be sent to the requests upon cancelation [default = Request Canceled] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk cancel all open SALE_OPT_OUT and ERASURE requests** - -```sh -transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --transcendUrl=https://api.us.transcend.io -``` - -**Bulk cancel all Erasure (request.type=ERASURE) requests that are in an enriching state (request.status=ENRICHING)** - -```sh -transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --statuses=ENRICHING -``` - -**Send a specific email template to the request that are being canceled** - -```sh -transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --cancellationTitle="Custom Email Template" -``` - -**Cancel all open SALE_OPT_OUT, but mark any request made before 05/03/2023 as silent mode to prevent emailing those requests** - -```sh -transcend request cancel \ - --auth="$TRANSCEND_API_KEY" \ - --actions=SALE_OPT_OUT \ - --silentModeBefore=2024-05-03T00:00:00.000Z -``` - -**Cancel all open SALE_OPT_OUT, within a specific time frame** - -```sh -transcend request cancel \ - --auth="$TRANSCEND_API_KEY" \ - --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z -``` - -**Increase the concurrency (defaults to 50)** - -```sh -transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=500 -``` - -**Bulk cancel requests by ID** - -```sh -transcend request cancel \ - --auth="$TRANSCEND_API_KEY" \ - --actions=ACCESS,ERASURE,SALE_OPT_OUT,CONTACT_OPT_OUT \ - --statuses=ENRICHING,COMPILING,APPROVING,WAITING,REQUEST_MADE,ON_HOLD,DELAYED,SECONDARY \ - --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 -``` - -### `transcend request restart` - -```txt -USAGE - transcend request restart (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) (--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED) [--transcendUrl value] [--requestReceiptFolder value] [--sombraAuth value] [--concurrency value] [--requestIds value]... [--emailIsVerified] [--createdAt value] [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--sendEmailReceipt] [--copyIdentifiers] [--skipWaitingPeriod] - transcend request restart --help - -Bulk update a set of privacy requests based on a set of request filters. - -FLAGS - --auth The Transcend API key. Requires scopes: "Submit New Data Subject Request", "View the Request Compilation" - --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - --statuses The request statuses to restart [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--requestReceiptFolder] The path to the folder where receipts of each upload are stored [default = ./privacy-request-upload-receipts] - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--concurrency] The concurrency to use when uploading requests in parallel [default = 15] - [--requestIds]... Specify the specific request IDs to restart [separator = ,] - [--emailIsVerified/--noEmailIsVerified] Indicate whether the primary email address is verified. Set to false to send a verification email [default = true] - [--createdAt] Restart requests that were submitted before a specific date - [--silentModeBefore] Requests older than this date should be marked as silent mode - [--createdAtBefore] Restart requests that were submitted before this time - [--createdAtAfter] Restart requests that were submitted after this time - [--sendEmailReceipt] Send email receipts to the restarted requests [default = false] - [--copyIdentifiers] Copy over all enriched identifiers from the initial request [default = false] - [--skipWaitingPeriod] Skip queued state of request and go straight to compiling [default = false] - -h --help Print help information and exit -``` - -#### Examples - -**Restart requests with specific statuses and actions** - -```sh -transcend request restart --auth="$TRANSCEND_API_KEY" --statuses=COMPILING,ENRICHING --actions=ACCESS,ERASURE -``` - -**For self-hosted sombras that use an internal key** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --transcendUrl=https://api.us.transcend.io -``` - -**Increase the concurrency (defaults to 15)** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --concurrency=100 -``` - -**Re-verify emails** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --emailIsVerified=false -``` - -**Restart specific requests by ID** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 -``` - -**Restart requests that were submitted before a specific date** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --createdAt=2024-05-11T00:00:00.000Z -``` - -**Restart requests and place everything in silent mode submitted before a certain date** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --silentModeBefore=2024-12-05T00:00:00.000Z -``` - -**Restart requests within a specific timeframe** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z -``` - -**Send email receipts to the restarted requests** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --sendEmailReceipt -``` - -**Copy over all enriched identifiers from the initial request** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --copyIdentifiers -``` - -**Skip queued state of request and go straight to compiling** - -```sh -transcend request restart \ - --auth="$TRANSCEND_API_KEY" \ - --statuses=COMPILING,ENRICHING \ - --actions=ACCESS,ERASURE \ - --skipWaitingPeriod -``` - -### `transcend request notify-additional-time` - -```txt -USAGE - transcend request notify-additional-time (--auth value) (--createdAtBefore value) [--createdAtAfter value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--daysLeft value] [--days value] [--requestIds value]... [--emailTemplate value] [--transcendUrl value] [--concurrency value] - transcend request notify-additional-time --help - -Bulk notify a set of privacy requests from the DSR Automation -> Incoming Requests tab that more time is needed to complete the request. Note any request in silent mode will not be emailed. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Incoming Requests", "Request Approval and Communication" - --createdAtBefore Notify requests that are open but submitted before this time - [--createdAtAfter] Notify requests that are open but submitted after this time - [--actions] The request actions to notify [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--daysLeft] Only notify requests that have less than this number of days until they are considered expired [default = 10] - [--days] The number of days to adjust the expiration of the request to [default = 45] - [--requestIds]... Specify the specific request IDs to notify [separator = ,] - [--emailTemplate] The title of the email template that should be sent to the requests [default = Additional Time Needed] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] - -h --help Print help information and exit -``` - -#### Examples - -**Notify all request types that were made before 01/01/2024** - -```sh -transcend request notify-additional-time --auth="$TRANSCEND_API_KEY" --createdAtBefore=2024-01-01T00:00:00.000Z -``` - -**Notify all request types that were made during a date range** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --createdAtAfter=2024-12-15T00:00:00.000Z -``` - -**Notify certain request types** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --actions=SALE_OPT_OUT,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --transcendUrl=https://api.us.transcend.io -``` - -**Bulk notify requests by ID** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 -``` - -**Only notify requests that are expiring in the next 3 days or less** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --daysLeft=3 -``` - -**Change number of days to extend request by** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --days=30 -``` - -**Send a specific email template to the request that instead of the default** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --emailTemplate="Custom Email Template" -``` - -**Increase the concurrency (defaults to 50)** - -```sh -transcend request notify-additional-time \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --concurrency=500 -``` - -### `transcend request mark-silent` - -```txt -USAGE - transcend request mark-silent (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--requestIds value]... [--createdAtBefore value] [--createdAtAfter value] [--transcendUrl value] [--concurrency value] - transcend request mark-silent --help - -Bulk update a set of privacy requests from the DSR Automation -> Incoming Requests tab to be in silent mode. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --actions The request actions to mark silent [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--statuses] The request statuses to mark silent. Comma-separated list. Defaults to REQUEST_MADE,WAITING,ENRICHING,COMPILING,DELAYED,APPROVING,SECONDARY,SECONDARY_APPROVING. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--requestIds]... Specify the specific request IDs to mark silent [separator = ,] - [--createdAtBefore] Mark silent requests that were submitted before this time - [--createdAtAfter] Mark silent requests that were submitted after this time - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk mark silent all open SALE_OPT_OUT and ERASURE requests** - -```sh -transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request mark-silent \ - --auth="$TRANSCEND_API_KEY" \ - --actions=ERASURE \ - --transcendUrl=https://api.us.transcend.io -``` - -**Bulk mark as silent all Erasure (request.type=ERASURE) requests that are in an enriching state (request.status=ENRICHING)** - -```sh -transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=ERASURE --statuses=ENRICHING -``` - -**Bulk mark as silent requests by ID** - -```sh -transcend request mark-silent \ - --auth="$TRANSCEND_API_KEY" \ - --actions=ACCESS,ERASURE,SALE_OPT_OUT,CONTACT_OPT_OUT \ - --statuses=ENRICHING,COMPILING,APPROVING,WAITING,REQUEST_MADE,ON_HOLD,DELAYED,SECONDARY \ - --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 -``` - -**Mark sale opt out requests as silent within a certain date range** - -```sh -transcend request mark-silent \ - --auth="$TRANSCEND_API_KEY" \ - --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z -``` - -**Increase the concurrency (defaults to 50)** - -```sh -transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=500 -``` - -### `transcend request enricher-restart` - -```txt -USAGE - transcend request enricher-restart (--auth value) (--enricherId value) [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--requestEnricherStatuses QUEUED|WAITING|SKIPPED|ERROR|RESOLVED|ACTION_REQUIRED|REMOTE_PROCESSING|WAITING_ON_DEPENDENCIES|POLLING] [--transcendUrl value] [--concurrency value] [--requestIds value]... [--createdAtBefore value] [--createdAtAfter value] - transcend request enricher-restart --help - -Bulk restart a particular enricher across a series of DSRs. - -The API key needs the following scopes: -- Manage Request Compilation - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --enricherId The ID of the enricher to restart - [--actions] The request action to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--requestEnricherStatuses] The request enricher statuses to restart [QUEUED|WAITING|SKIPPED|ERROR|RESOLVED|ACTION_REQUIRED|REMOTE_PROCESSING|WAITING_ON_DEPENDENCIES|POLLING, separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 15] - [--requestIds]... Specify the specific request IDs to restart [separator = ,] - [--createdAtBefore] Restart requests that were submitted before this time - [--createdAtAfter] Restart requests that were submitted after this time - -h --help Print help information and exit -``` - -#### Examples - -**Restart a particular enricher across a series of DSRs** - -```sh -transcend request enricher-restart --auth="$TRANSCEND_API_KEY" --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 -``` - -**Restart specific request types** - -```sh -transcend request enricher-restart \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --actions=ACCESS,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request enricher-restart \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --transcendUrl=https://api.us.transcend.io -``` - -**Increase the concurrency (defaults to 15)** - -```sh -transcend request enricher-restart \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --concurrency=100 -``` - -**Restart requests within a specific timeframe** - -```sh -transcend request enricher-restart \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z -``` - -**Restart requests that are in an error state** - -```sh -transcend request enricher-restart \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --requestEnricherStatuses=ERROR -``` - -### `transcend request reject-unverified-identifiers` - -```txt -USAGE - transcend request reject-unverified-identifiers (--auth value) (--identifierNames value)... [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--transcendUrl value] - transcend request reject-unverified-identifiers --help - -Bulk clear out any request identifiers that are unverified. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --identifierNames... The names of identifiers to clear out [separator = ,] - [--actions] The request action to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk clear out any request identifiers that are unverified** - -```sh -transcend request reject-unverified-identifiers --auth="$TRANSCEND_API_KEY" --identifierNames=phone -``` - -**Restart specific request types** - -```sh -transcend request reject-unverified-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --identifierNames=phone \ - --actions=ACCESS,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request reject-unverified-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --identifierNames=phone \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend request export` - -```txt -USAGE - transcend request export (--auth value) [--sombraAuth value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--transcendUrl value] [--file value] [--concurrency value] [--createdAtBefore value] [--createdAtAfter value] [--showTests] [--pageLimit value] - transcend request export --help - -Export privacy requests and request identifiers to a CSV file. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Incoming Requests", "View the Request Compilation" - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--actions] The request actions to export [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--statuses] The request statuses to export [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the CSV file where identifiers will be written to [default = ./transcend-request-export.csv] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] - [--createdAtBefore] Pull requests that were submitted before this time - [--createdAtAfter] Pull requests that were submitted after this time - [--showTests/--noShowTests] Filter for test requests or production requests - when not provided, pulls both - [--pageLimit] The page limit to use when pulling in pages of requests [default = 100] - -h --help Print help information and exit -``` - -#### Examples - -**Pull all requests** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" -``` - -**Filter for specific actions and statuses** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --statuses=COMPILING,ENRICHING --actions=ACCESS,ERASURE -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io -``` - -**With Sombra authentication** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --sombraAuth="$SOMBRA_INTERNAL_KEY" -``` - -**Increase the concurrency (defaults to 100)** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --concurrency=500 -``` - -**Filter for production requests only** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --showTests=false -``` - -**Filter for requests within a date range** - -```sh -transcend request export \ - --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z -``` - -**Write to a specific file location** - -```sh -transcend request export --auth="$TRANSCEND_API_KEY" --file=./path/to/file.csv -``` - -### `transcend request skip-preflight-jobs` - -```txt -USAGE - transcend request skip-preflight-jobs (--auth value) (--enricherIds value)... [--transcendUrl value] - transcend request skip-preflight-jobs --help - -This command allows for bulk skipping preflight checks. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --enricherIds... The ID of the enrichers to skip privacy request jobs for [separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk skipping preflight checks** - -```sh -transcend request skip-preflight-jobs --auth="$TRANSCEND_API_KEY" --enricherIds=70810f2e-cf90-43f6-9776-901a5950599f -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request skip-preflight-jobs \ - --auth="$TRANSCEND_API_KEY" \ - --enricherIds=70810f2e-cf90-43f6-9776-901a5950599f,db1e64ba-cea6-43ff-ad27-5dc8122e5224 \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend request system mark-request-data-silos-completed` - -```txt -USAGE - transcend request system mark-request-data-silos-completed (--auth value) (--dataSiloId value) [--file value] [--transcendUrl value] - transcend request system mark-request-data-silos-completed --help - -This command takes in a CSV of Request IDs as well as a Data Silo ID and marks all associated privacy request jobs as completed. -This command is useful with the "Bulk Response" UI. The CSV is expected to have 1 column named "Request Id". - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --dataSiloId The ID of the data silo to pull in - [--file] Path to the CSV file where identifiers will be written to. The CSV is expected to have 1 column named "Request Id". [default = ./request-identifiers.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Mark all associated privacy request jobs as completed** - -```sh -transcend request system mark-request-data-silos-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f -``` - -**Pull to a specific file location** - -```sh -transcend request system mark-request-data-silos-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --file=/Users/transcend/Desktop/test.csv -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request system mark-request-data-silos-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend request system retry-request-data-silos` - -```txt -USAGE - transcend request system retry-request-data-silos (--auth value) (--dataSiloId value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--transcendUrl value] - transcend request system retry-request-data-silos --help - -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. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --dataSiloId The ID of the data silo to pull in - --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk restarting a set of data silos jobs for open privacy requests** - -```sh -transcend request system retry-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ACCESS -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request system retry-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ACCESS \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend request system skip-request-data-silos` - -```txt -USAGE - transcend request system skip-request-data-silos (--auth value) (--dataSiloId value) [--transcendUrl value] (--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED) [--status SKIPPED|RESOLVED] - transcend request system skip-request-data-silos --help - -This command allows for bulk skipping all open privacy request jobs for a particular data silo. This command is useful if you want to disable a data silo and then clear out any active privacy requests that are still queued up for that data silo. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Compilation" - --dataSiloId The ID of the data silo to skip privacy request jobs for - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - --statuses The request statuses to skip [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] - [--status] The status to set the request data silo job to [SKIPPED|RESOLVED, default = SKIPPED] - -h --help Print help information and exit -``` - -#### Examples - -**Bulk skipping all open privacy request jobs for a particular data silo** - -```sh -transcend request system skip-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request system skip-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --transcendUrl=https://api.us.transcend.io -``` - -**Only mark as completed requests in "removing data" phase** - -```sh -transcend request system skip-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --statuses=SECONDARY -``` - -**Set to status "RESOLVED" instead of status "SKIPPED"** - -```sh -transcend request system skip-request-data-silos \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --status=RESOLVED -``` - -### `transcend request preflight pull-identifiers` - -```txt -USAGE - transcend request preflight pull-identifiers (--auth value) [--sombraAuth value] [--transcendUrl value] [--file value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--concurrency value] - transcend request preflight pull-identifiers --help - -This command pulls down the set of privacy requests that are currently pending manual enrichment. - -This is useful for the following workflow: - -1. Pull identifiers to CSV: - - transcend request preflight pull-identifiers --file=./enrichment-requests.csv - -2. Fill out the CSV with additional identifiers - -3. Push updated back to Transcend: - - transcend request preflight push-identifiers --file=./enrichment-requests.csv - -FLAGS - --auth The Transcend API key. Requires scopes: "View Incoming Requests", "View the Request Compilation" - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the CSV file where requests will be written to [default = ./manual-enrichment-identifiers.csv] - [--actions] The request actions to pull for [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] - -h --help Print help information and exit -``` - -#### Examples - -**Pull down the set of privacy requests that are currently pending manual enrichment** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" -``` - -**Pull to a specific file location** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv -``` - -**For specific types of requests** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --actions=ACCESS,ERASURE -``` - -**For US hosted infrastructure** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io -``` - -**With Sombra authentication** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --sombraAuth="$SOMBRA_INTERNAL_KEY" -``` - -**With specific concurrency** - -```sh -transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --concurrency=200 -``` - -### `transcend request preflight push-identifiers` - -```txt -USAGE - transcend request preflight push-identifiers (--auth value) (--enricherId value) [--sombraAuth value] [--transcendUrl value] [--file value] [--markSilent] [--concurrency value] - transcend request preflight push-identifiers --help - -This command push up a set of identifiers for a set of requests pending manual enrichment. - -This is useful for the following workflow: - -1. Pull identifiers to CSV: - - transcend request preflight pull-identifiers --file=./enrichment-requests.csv - -2. Fill out the CSV with additional identifiers - -3. Push updated back to Transcend: - - transcend request preflight push-identifiers --file=./enrichment-requests.csv - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Request Identity Verification", "Manage Request Compilation" - --enricherId The ID of the Request Enricher to upload to - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the CSV file where requests will be written to [default = ./manual-enrichment-identifiers.csv] - [--markSilent] When true, set requests into silent mode before enriching [default = false] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] - -h --help Print help information and exit -``` - -#### Examples - -**Push up a set of identifiers for a set of requests pending manual enrichment** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf -``` - -**Pull to a specific file location** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ - --file=/Users/transcend/Desktop/test.csv -``` - -**For US hosted infrastructure** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ - --transcendUrl=https://api.us.transcend.io -``` - -**With Sombra authentication** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" -``` - -**With specific concurrency** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ - --concurrency=200 -``` - -**When enriching requests, mark all requests as silent mode before processing** - -```sh -transcend request preflight push-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ - --markSilent -``` - -### `transcend request cron pull-identifiers` - -```txt -USAGE - transcend request cron pull-identifiers (--auth value) (--dataSiloId value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--file value] [--transcendUrl value] [--sombraAuth value] [--pageLimit value] [--skipRequestCount] [--chunkSize value] - transcend request cron pull-identifiers --help - -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. - -For 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. - -Read more at https://docs.transcend.io/docs/integrations/cron-job-integration. - -FLAGS - --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. No scopes are required for this command. - --dataSiloId The ID of the data silo to pull in - --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] - [--file] Path to the CSV file where identifiers will be written to [default = ./cron-identifiers.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--pageLimit] The page limit to use when pulling in pages of identifiers [default = 100] - [--skipRequestCount] 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 [default = false] - [--chunkSize] 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 [default = 10000] - -h --help Print help information and exit -``` - -#### Examples - -**Pull outstanding identifiers for a data silo** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE -``` - -**Pull to a specific file location** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --file=/Users/transcend/Desktop/test.csv -``` - -**For self-hosted sombras that use an internal key** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --transcendUrl=https://api.us.transcend.io -``` - -**Specifying the page limit, defaults to 100** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --pageLimit=300 \ - --chunkSize=6000 -``` - -**Specifying the chunk size for large datasets to avoid file size limits (defaults to 100,000 rows per file)** - -```sh -transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --chunkSize=50000 -``` - -### `transcend request cron mark-identifiers-completed` - -```txt -USAGE - transcend request cron mark-identifiers-completed (--auth value) (--dataSiloId value) [--file value] [--transcendUrl value] [--sombraAuth value] - transcend request cron mark-identifiers-completed --help - -This command takes the output of "transcend request cron pull-identifiers" and notifies Transcend that all of the requests in the CSV have been processed. -This is used in the workflow like: - -1. Pull identifiers to CSV: - - transcend request cron pull-identifiers \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --actions=ERASURE \ - --file=./outstanding-requests.csv - -2. Run your process to operate on that CSV of requests. - -3. Notify Transcend of completion - - transcend request cron mark-identifiers-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --file=./outstanding-requests.csv - -Read more at https://docs.transcend.io/docs/integrations/cron-job-integration. - -FLAGS - --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. No scopes are required for this command. - --dataSiloId The ID of the data silo to pull in - [--file] Path to the CSV file where identifiers will be written to [default = ./cron-identifiers.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - -h --help Print help information and exit -``` - -#### Examples - -**Mark identifiers as completed** - -```sh -transcend request cron mark-identifiers-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f -``` - -**Pull to a specific file location** - -```sh -transcend request cron mark-identifiers-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --file=/Users/transcend/Desktop/test.csv -``` - -**For self-hosted sombras that use an internal key** - -```sh -transcend request cron mark-identifiers-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend request cron mark-identifiers-completed \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend consent build-xdi-sync-endpoint` - -```txt -USAGE - transcend consent build-xdi-sync-endpoint (--auth value) (--xdiLocation value) [--file value] [--removeIpAddresses] [--domainBlockList value] [--xdiAllowedCommands value] [--transcendUrl value] - transcend consent build-xdi-sync-endpoint --help - -This command allows for building of the XDI Sync Endpoint across a set of Transcend accounts. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Consent Manager" - --xdiLocation The location of the XDI that will be loaded by the generated sync endpoint - [--file] The HTML file path where the sync endpoint should be written [default = ./sync-endpoint.html] - [--removeIpAddresses/--noRemoveIpAddresses] When true, remove IP addresses from the domain list [default = true] - [--domainBlockList] The set of domains that should be excluded from the sync endpoint. Comma-separated list. [default = localhost] - [--xdiAllowedCommands] The allowed set of XDI commands [default = ConsentManager:Sync] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Build XDI sync endpoint** - -```sh -transcend consent build-xdi-sync-endpoint --auth="$TRANSCEND_API_KEY" --xdiLocation=https://cdn.your-site.com/xdi.js -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent build-xdi-sync-endpoint \ - --auth="$TRANSCEND_API_KEY" \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --transcendUrl=https://api.us.transcend.io -``` - -**Pull to specific file location** - -```sh -transcend consent build-xdi-sync-endpoint \ - --auth="$TRANSCEND_API_KEY" \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --file=./my-folder/sync-endpoint.html -``` - -**Don't filter out regular expressions** - -```sh -transcend consent build-xdi-sync-endpoint \ - --auth="$TRANSCEND_API_KEY" \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --removeIpAddresses=false -``` - -**Filter out certain domains that should not be included in the sync endpoint definition** - -```sh -transcend consent build-xdi-sync-endpoint \ - --auth="$TRANSCEND_API_KEY" \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --domainBlockList=ignored.com,localhost -``` - -**Override XDI allowed commands** - -```sh -transcend consent build-xdi-sync-endpoint \ - --auth="$TRANSCEND_API_KEY" \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --xdiAllowedCommands=ExtractIdentifiers:Simple -``` - -**Configuring across multiple Transcend Instances** - -```sh -# Pull down API keys across all Transcend instances -transcend admin generate-api-keys \ - --email="$TRANSCEND_EMAIL" \ - --password="$TRANSCEND_PASSWORD" \ - --transcendUrl=https://api.us.transcend.io \ - --scopes="View Consent Manager" \ - --apiKeyTitle="[cli][$TRANSCEND_EMAIL] XDI Endpoint Construction" \ - --file=./api-keys.json \ - --parentOrganizationId=1821d872-6114-406e-90c3-73b4d5e246cf - -# Path list of API keys as authentication -transcend consent build-xdi-sync-endpoint \ - --auth=./api-keys.json \ - --xdiLocation=https://cdn.your-site.com/xdi.js \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend consent pull-consent-metrics` - -```txt -USAGE - transcend consent pull-consent-metrics (--auth value) (--start value) [--end value] [--folder value] [--bin value] [--transcendUrl value] - transcend consent pull-consent-metrics --help - -This command allows for pulling consent manager metrics for a Transcend account, or a set of Transcend accounts. - -By 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: - -- `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 });` -- `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 });` -- `CONSENT_SESSIONS_BY_REGIME_Default.csv` -> this contains the number of sessions detected for the bin period -- `PRIVACY_SIGNAL_TIMESERIES_DNT.csv` -> the number of DNT signals detected. -- `PRIVACY_SIGNAL_TIMESERIES_GPC.csv` -> the number of GPC signals detected. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Consent Manager" - --start The start date to pull metrics from - [--end] The end date to pull metrics until - [--folder] The folder to save metrics to [default = ./consent-metrics/] - [--bin] The bin metric when pulling data (1h or 1d) [default = 1d] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Pull consent manager metrics for a Transcend account** - -```sh -transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent pull-consent-metrics \ - --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ - --transcendUrl=https://api.us.transcend.io -``` - -**Pull start and end date explicitly** - -```sh -transcend consent pull-consent-metrics \ - --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ - --end=2024-03-01T00:00:00.000Z -``` - -**Save to an explicit folder** - -```sh -transcend consent pull-consent-metrics \ - --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ - --end=2024-03-01T00:00:00.000Z \ - --folder=./my-folder/ -``` - -**Bin data hourly vs daily** - -```sh -transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z --bin=1h -``` - -### `transcend consent pull-consent-preferences` - -```txt -USAGE - transcend consent pull-consent-preferences (--auth value) (--partition value) [--sombraAuth value] [--file value] [--transcendUrl value] [--timestampBefore value] [--timestampAfter value] [--identifiers value]... [--concurrency value] - transcend consent pull-consent-preferences --help - -This command allows for pull of consent preferences from the Managed Consent Database. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Managed Consent Database Admin API" - --partition The partition key to download consent preferences to - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--file] Path to the CSV file to save preferences to [default = ./preferences.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--timestampBefore] Filter for consents updated this time - [--timestampAfter] Filter for consents updated after this time - [--identifiers]... Filter for specific identifiers [separator = ,] - [--concurrency] The concurrency to use when downloading consents in parallel [default = 100] - -h --help Print help information and exit -``` - -#### Examples - -**Fetch all consent preferences from partition key** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 -``` - -**Fetch all consent preferences from partition key and save to ./consent.csv** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./consent.csv -``` - -**Filter on consent updates before a date** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --timestampBefore=2024-04-03T00:00:00.000Z -``` - -**Filter on consent updates after a date** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --timestampAfter=2024-04-03T00:00:00.000Z -``` - -**For self-hosted sombras that use an internal key** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --sombraAuth="$SOMBRA_INTERNAL_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent pull-consent-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend consent update-consent-manager` - -```txt -USAGE - transcend consent update-consent-manager (--auth value) (--bundleTypes PRODUCTION|TEST) [--deploy] [--transcendUrl value] - transcend consent update-consent-manager --help - -This command allows for updating Consent Manager to latest version. The Consent Manager bundle can also be deployed using this command. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Consent Manager Developer Settings" - --bundleTypes The bundle types to deploy. Defaults to PRODUCTION,TEST. [PRODUCTION|TEST, separator = ,] - [--deploy] When true, deploy the Consent Manager after updating the version [default = false] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Update Consent Manager to latest version** - -```sh -transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io -``` - -**Update version and deploy bundles** - -```sh -transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --deploy -``` - -**Update just the TEST bundle** - -```sh -transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --bundleTypes=TEST -``` - -**Update just the PRODUCTION bundle** - -```sh -transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --bundleTypes=PRODUCTION -``` - -**Update multiple organizations at once** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="Manage Consent Manager" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend consent update-consent-manager --auth=./transcend-api-keys.json --deploy -``` - -### `transcend consent upload-consent-preferences` - -```txt -USAGE - transcend consent upload-consent-preferences (--base64EncryptionKey value) (--base64SigningKey value) (--partition value) [--file value] [--consentUrl value] [--concurrency value] - transcend consent upload-consent-preferences --help - -This command allows for updating of consent preferences to the Managed Consent Database. - -FLAGS - --base64EncryptionKey The encryption key used to encrypt the userId - --base64SigningKey The signing key used to prove authentication of consent request - --partition The partition key to download consent preferences to - [--file] The file to pull consent preferences from [default = ./preferences.csv] - [--consentUrl] URL of the Transcend consent backend. Use https://consent.us.transcend.io for US hosting [default = https://consent.transcend.io] - [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] - -h --help Print help information and exit -``` - -#### Examples - -**Upload consent preferences to partition key** - -```sh -transcend consent upload-consent-preferences \ - --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ - --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 -``` - -**Upload consent preferences to partition key from file** - -```sh -transcend consent upload-consent-preferences \ - --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ - --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./consent.csv -``` - -**Upload consent preferences to partition key and set concurrency** - -```sh -transcend consent upload-consent-preferences \ - --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ - --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --concurrency=200 -``` - -### `transcend consent upload-cookies-from-csv` - -```txt -USAGE - transcend consent upload-cookies-from-csv (--auth value) (--trackerStatus LIVE|NEEDS_REVIEW) [--file value] [--transcendUrl value] - transcend consent upload-cookies-from-csv --help - -Upload cookies from CSV. This command allows for uploading of cookies from CSV. - -Step 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. - -Step 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. - -Step 3) Upload the modified CSV file back into the dashboard with this command. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Data Flows" - --trackerStatus The status of the cookies you will upload. [LIVE|NEEDS_REVIEW] - [--file] Path to the CSV file to upload [default = ./cookies.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Upload the file of cookies in ./cookies.csv into the "Approved" tab** - -```sh -transcend consent upload-cookies-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE -``` - -**Upload the file of cookies in ./cookies.csv into the "Triage" tab** - -```sh -transcend consent upload-cookies-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=NEEDS_REVIEW -``` - -**Specifying the CSV file to read from** - -```sh -transcend consent upload-cookies-from-csv \ - --auth="$TRANSCEND_API_KEY" \ - --trackerStatus=LIVE \ - --file=./custom/my-cookies.csv -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent upload-cookies-from-csv \ - --auth="$TRANSCEND_API_KEY" \ - --trackerStatus=LIVE \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend consent upload-data-flows-from-csv` - -```txt -USAGE - transcend consent upload-data-flows-from-csv (--auth value) (--trackerStatus LIVE|NEEDS_REVIEW) [--file value] [--classifyService] [--transcendUrl value] - transcend consent upload-data-flows-from-csv --help - -Upload data flows from CSV. This command allows for uploading of data flows from CSV. - -Step 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. - -Step 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. - -Step 3) Upload the modified CSV file back into the dashboard with this command. - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Data Flows" - --trackerStatus The status of the data flows you will upload. [LIVE|NEEDS_REVIEW] - [--file] Path to the CSV file to upload [default = ./data-flows.csv] - [--classifyService] When true, automatically assign the service for a data flow based on the domain that is specified [default = false] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -To get a CSV of data flows, you can download the data flows 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. - -export-data-flows - -#### Examples - -**Upload the file of data flows in ./data-flows.csv into the "Approved" tab** - -```sh -transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE -``` - -**Upload the file of data flows in ./data-flows.csv into the "Triage" tab** - -```sh -transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=NEEDS_REVIEW -``` - -**Specifying the CSV file to read from** - -```sh -transcend consent upload-data-flows-from-csv \ - --auth="$TRANSCEND_API_KEY" \ - --trackerStatus=LIVE \ - --file=./custom/my-data-flows.csv -``` - -**Have Transcend automatically fill in the service names by looking up the data flow host in Transcend's database** - -```sh -transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE --classifyService -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent upload-data-flows-from-csv \ - --auth="$TRANSCEND_API_KEY" \ - --trackerStatus=LIVE \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend consent upload-preferences` - -```txt -USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] [--file value] [--directory value] [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] - transcend consent upload-preferences --help - -Upload preference management data to your Preference Store. - -This 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. - -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. - -Parallel preference uploader (Node 22+ ESM/TS) ------------------------------------------------------------------------------ -- Spawns a pool of child *processes* (not threads) to run uploads in parallel. -- Shows a live dashboard in the parent terminal with progress per worker. -- Creates per-worker log files and (optionally) opens OS terminals to tail them. -- Uses the same module as both parent and child; the child mode is toggled - by the presence of a CLI flag ('--child-upload-preferences'). - -FLAGS - --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" - --partition The partition key to download consent preferences to - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the CSV file to load preferences from - [--directory] Path to the directory of CSV files to load preferences from - [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] - [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] - [--receiptFileDir] Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided. - [--schemaFilePath] The path to where the schema for the file should be saved. If file is provided, it will default to ./-preference-upload-schema.json If directory is provided, it will default to /../preference-upload-schema.json - [--skipWorkflowTriggers] Whether to skip workflow triggers when uploading to preference store [default = false] - [--forceTriggerWorkflows] Whether to force trigger workflows for existing consent records [default = false] - [--skipConflictUpdates] Whether to skip uploading of any records where the preference store and file have a hard conflict [default = false] - [--isSilent/--noIsSilent] Whether to skip sending emails in workflows [default = true] - [--attributes] Attributes to add to any DSR request if created. Comma-separated list of key:value pairs. [default = Tags:transcend-cli,Source:transcend-cli] - [--receiptFilepath] Store resulting, continuing where left off [default = ./preference-management-upload-receipts.json] - [--concurrency] The number of concurrent processes to use to upload the files. When this is not set, it defaults to the number of CPU cores available on the machine. e.g. if there are 5 concurrent processes for 15 files, each parallel job would get 3 files to process. - [--uploadConcurrency] When uploading preferences to v1/preferences - this is the number of concurrent requests made at any given time by a single process.This is NOT the batch size—it's how many batch *tasks* run in parallel. The number of total concurrent requests is maxed out at concurrency * uploadConcurrency. [default = 75] - [--maxChunkSize] When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency. [default = 50] - [--rateLimitRetryDelay] When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. This is only used if the request is rate limited by the Transcend API. If the request fails for any other reason, it will not be retried. [default = 3000] - [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] - [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 10] - --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. - --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. - [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. - -h --help Print help information and exit -``` - -A sample CSV can be found [here](./examples/cli-upload-preferences-example.csv). In this example, `Sales` and `Marketing` are two custom Purposes, and `SalesCommunications` and `MarketingCommunications` are Preference Topics. During the interactive CLI prompt, you can map these columns to the slugs stored in Transcend! - -#### Examples - -**Upload consent preferences to partition key `4d1c5daa-90b7-4d18-aa40-f86a43d2c726`** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --file=./preferences.csv \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 -``` - -**Upload consent preferences with additional options** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./preferences.csv \ - --dryRun \ - --skipWorkflowTriggers \ - --skipConflictUpdates \ - --isSilent=false \ - --attributes=Tags:transcend-cli,Source:transcend-cli \ - --receiptFilepath=./preference-management-upload-receipts.json -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./preferences.csv \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend inventory pull` - -```txt -USAGE - transcend inventory pull (--auth value) [--resources all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes] [--file value] [--transcendUrl value] [--dataSiloIds value]... [--integrationNames value]... [--trackerStatuses LIVE|NEEDS_REVIEW] [--pageSize value] [--skipDatapoints] [--skipSubDatapoints] [--includeGuessedCategories] [--debug] - transcend inventory pull --help - -Generates a transcend.yml by pulling the configuration from your Transcend instance. - -The API key needs various scopes depending on the resources being pulled (see the CLI's README for more details). - -This command can be helpful if you are looking to: - -- Copy your data into another instance -- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code. - -FLAGS - --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. - [--resources] The different resource types to pull in. Defaults to dataSilos,enrichers,templates,apiKeys. [all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes, separator = ,] - [--file] Path to the YAML file to pull into [default = ./transcend.yml] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... The UUIDs of the data silos that should be pulled into the YAML file [separator = ,] - [--integrationNames]... The types of integrations to pull down [separator = ,] - [--trackerStatuses] The statuses of consent manager trackers to pull down. Defaults to all statuses. [LIVE|NEEDS_REVIEW, separator = ,] - [--pageSize] The page size to use when paginating over the API [default = 50] - [--skipDatapoints] When true, skip pulling in datapoints alongside data silo resource [default = false] - [--skipSubDatapoints] When true, skip pulling in subDatapoints alongside data silo resource [default = false] - [--includeGuessedCategories] When true, included guessed data categories that came from the content classifier [default = false] - [--debug] Set to true to include debug logs while pulling the configuration [default = false] - -h --help Print help information and exit -``` - -#### Scopes - -The API key permissions for this command vary based on the `resources` argument: - -| Resource | Description | Scopes | Link | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| apiKeys | API Key definitions assigned to Data Silos. API keys cannot be created through the CLI, but you can map API key usage to Data Silos. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) | -| customFields | Custom field definitions that define extra metadata for each table in the Admin Dashboard. | View Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) | -| templates | Email templates. Only template titles can be created and mapped to other resources. | View Email Templates | [DSR Automation -> Email Templates](https://app.transcend.io/privacy-requests/email-templates) | -| dataSilos | The Data Silo/Integration definitions. | View Data Map, View Data Subject Request Settings | [Data Inventory -> Data Silos](https://app.transcend.io/data-map/data-inventory/) and [Infrastucture -> Integrations](https://app.transcend.io/infrastructure/integrationsdata-silos) | -| enrichers | The Privacy Request enricher configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | -| dataFlows | Consent Manager Data Flow definitions. | View Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) | -| businessEntities | The business entities in the data inventory. | View Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) | -| processingActivities | The processing activities in the data inventory. | View Data Inventory | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) | -| actions | The Privacy Request action settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | -| dataSubjects | The Privacy Request data subject settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | -| identifiers | The Privacy Request identifier configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | -| cookies | Consent Manager Cookie definitions. | View Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) | -| consentManager | Consent Manager general settings, including domain list. | View Consent Manager | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) | -| partitions | The partitions in the account (often representative of separate data controllers). | View Consent Manager | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) | -| prompts | The Transcend AI prompts | View Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) | -| promptPartials | The Transcend AI prompt partials | View Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partialss) | -| promptGroups | The Transcend AI prompt groups | View Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) | -| agents | The agents in the prompt manager. | View Prompts | [Prompt Manager -> Agents](https://app.transcend.io/prompts/agents) | -| agentFunctions | The agent functions in the prompt manager. | View Prompts | [Prompt Manager -> Agent Functions](https://app.transcend.io/prompts/agent-functions) | -| agentFiles | The agent files in the prompt manager. | View Prompts | [Prompt Manager -> Agent Files](https://app.transcend.io/prompts/agent-files) | -| vendors | The vendors in the data inventory. | View Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) | -| dataCategories | The data categories in the data inventory. | View Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) | -| processingPurposes | The processing purposes in the data inventory. | View Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) | -| actionItems | Onboarding related action items | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | -| actionItemCollections | Onboarding related action item group names | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | -| teams | Team definitions of users and scope groupings | View Scopes | [Administration -> Teams](https://app.transcend.io/admin/teams) | -| privacyCenters | The privacy center configurations. | View Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) | -| policies | The privacy center policies. | View Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) | -| messages | Message definitions used across consent, privacy center, email templates and more. | View Internationalization Messages | [Privacy Center -> Messages](https://app.transcend.io/privacy-center/messages-internationalization), [Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) | -| assessments | Assessment responses. | View Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) | -| assessmentTemplates | Assessment template configurations. | View Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) | -| purposes | Consent purposes and related preference management topics. | View Consent Manager, View Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) | - -#### Examples - -**Write out file to ./transcend.yml** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" -``` - -**Write out file to custom location** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml -``` - -**Pull specific data silo by ID** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --dataSiloIds=710fec3c-7bcc-4c9e-baff-bf39f9bec43e -``` - -**Pull specific types of data silos** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --integrationNames=salesforce,snowflake -``` - -**Pull specific resource types** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=apiKeys,templates,dataSilos,enrichers -``` - -**Pull data flows and cookies with specific tracker statuses (see [this example](./examples/data-flows-cookies.yml))** - -```sh -transcend inventory pull \ - --auth="$TRANSCEND_API_KEY" \ - --resources=dataFlows,cookies \ - --trackerStatuses=NEEDS_REVIEW,LIVE -``` - -**Pull data silos without datapoint information** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipDatapoints -``` - -**Pull data silos without subdatapoint information** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipSubDatapoints -``` - -**Pull data silos with guessed categories** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --includeGuessedCategories -``` - -**Pull custom field definitions only (see [this example](./examples/attributes.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields -``` - -**Pull business entities only (see [this example](./examples/business-entities.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=businessEntities -``` - -**Pull processing activities only (see [this example](./examples/processing-activities.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=processingActivities -``` - -**Pull enrichers and identifiers (see [this example](./examples/enrichers.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=enrichers,identifiers -``` - -**Pull onboarding action items (see [this example](./examples/action-items.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actionItems,actionItemCollections -``` - -**Pull consent manager domain list (see [this example](./examples/consent-manager-domains.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=consentManager -``` - -**Pull identifier configurations (see [this example](./examples/identifiers.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=identifiers -``` - -**Pull request actions configurations (see [this example](./examples/actions.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actions -``` - -**Pull consent manager purposes and preference management topics (see [this example](./examples/purposes.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=purposes -``` - -**Pull data subject configurations (see [this example](./examples/data-subjects.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSubjects -``` - -**Pull assessments and assessment templates** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=assessments,assessmentTemplates -``` - -**Pull everything** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=all -``` - -**Pull configuration files across multiple instances** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Consent Manager" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth=./transcend-api-keys.json --resources=consentManager --file=./transcend/ -``` - -Note: This command will overwrite the existing transcend.yml file that you have locally. - -### `transcend inventory push` - -```txt -USAGE - transcend inventory push (--auth value) [--file value] [--transcendUrl value] [--pageSize value] [--variables value] [--publishToPrivacyCenter] [--classifyService] [--deleteExtraAttributeValues] - transcend inventory push --help - -Given a transcend.yml file, sync the contents up to your Transcend instance. - -FLAGS - --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. - [--file] Path to the YAML file to push from [default = ./transcend.yml] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--pageSize] The page size to use when paginating over the API [default = 50] - [--variables] The variables to template into the YAML file when pushing configuration. Comma-separated list of key:value pairs. [default = ""] - [--publishToPrivacyCenter] When true, publish the configuration to the Privacy Center [default = false] - [--classifyService] When true, automatically assign the service for a data flow based on the domain that is specified [default = false] - [--deleteExtraAttributeValues] When true and syncing attributes, delete any extra attributes instead of just upserting [default = false] - -h --help Print help information and exit -``` - -#### Scopes - -The scopes for `transcend inventory push` are the same as the scopes for [`transcend inventory pull`](#transcend-inventory-pull). - -#### Examples - -**Looks for file at ./transcend.yml** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" -``` - -**Looks for file at custom location ./custom/location.yml** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml -``` - -**Apply service classifier to all data flows** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService -``` - -**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues -``` - -**Use dynamic variables to fill out parameters in YAML files (see [./examples/multi-instance.yml](./examples/multi-instance.yml))** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --variables=domain:acme.com,stage:staging -``` - -**Push a single .yml file configuration into multiple Transcend instances** - -This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth="$TRANSCEND_API_KEY" -transcend inventory push --auth=./transcend-api-keys.json -``` - -**Push multiple .yml file configurations into multiple Transcend instances** - -This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth=./transcend-api-keys.json --file=./transcend/ -# -transcend inventory push --auth=./transcend-api-keys.json --file=./transcend/ -``` - -**Apply service classifier to all data flows** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataFlows -transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService -``` - -**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields -transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues -``` - -Some things to note about this sync process: - -1. Any field that is defined in your .yml file will be synced up to app.transcend.io. If any change was made on the Admin Dashboard, it will be overwritten. -2. If you omit a field from the .yml file, this field will not be synced. This gives you the ability to define as much or as little configuration in your transcend.yml file as you would like, and let the remainder of fields be labeled through the Admin Dashboard -3. If you define new data subjects, identifiers, data silos or datapoints that were not previously defined on the Admin Dashboard, the CLI will create these new resources automatically. -4. Currently, this CLI does not handle deleting or renaming of resources. If you need to delete or rename a data silo, identifier, enricher or API key, you should make the change on the Admin Dashboard. -5. The only resources that this CLI will not auto-generate are: - -- a) Data silo owners: If you assign an email address to a data silo, you must first make sure that user is invited into your Transcend instance (https://app.transcend.io/admin/users). -- b) API keys: This CLI will not create new API keys. You will need to first create the new API keys on the Admin Dashboard (https://app.transcend.io/infrastructure/api-keys). You can then list out the titles of the API keys that you generated in your transcend.yml file, after which the CLI is capable of updating that API key to be able to respond to different data silos in your Data Map - -#### CI Integration - -Once you have a workflow for creating your transcend.yml file, you will want to integrate your `transcend inventory push` command on your CI. - -Below is an example of how to set this up using a Github action: - -```yaml -name: Transcend Data Map Syncing -# See https://app.transcend.io/privacy-requests/connected-services - -on: - push: - branches: - - 'main' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: '16' - - - name: Install Transcend CLI - run: npm install --global @transcend-io/cli - - # If you have a script that generates your transcend.yml file from - # an ORM or infrastructure configuration, add that step here - # Leave this step commented out if you want to manage your transcend.yml manually - # - name: Generate transcend.yml - # run: ./scripts/generate_transcend_yml.py - - - name: Push Transcend config - run: transcend inventory push --auth=${{ secrets.TRANSCEND_API_KEY }} -``` - -#### Dynamic Variables - -If you are using this CLI to sync your Data Map between multiple Transcend instances, you may find the need to make minor modifications to your configurations between environments. The most notable difference would be the domain where your webhook URLs are hosted on. - -The `transcend inventory push` command takes in a parameter `variables`. This is a CSV of `key:value` pairs. - -This command could fill out multiple parameters in a YAML file like [./examples/multi-instance.yml](./examples/multi-instance.yml), copied below: - -```yml -api-keys: - - title: Webhook Key -enrichers: - - title: Basic Identity Enrichment - description: Enrich an email address to the userId and phone number - # The data silo webhook URL is the same in each environment, - # except for the base domain in the webhook URL. - url: https://example.<>/transcend-enrichment-webhook - input-identifier: email - output-identifiers: - - userId - - phone - - myUniqueIdentifier - - title: Fraud Check - description: Ensure the email address is not marked as fraudulent - url: https://example.<>/transcend-fraud-check - input-identifier: email - output-identifiers: - - email - privacy-actions: - - ERASURE -data-silos: - - title: Redshift Data Warehouse - integrationName: server - description: The mega-warehouse that contains a copy over all SQL backed databases - <> - url: https://example.<>/transcend-webhook - api-key-title: Webhook Key -``` - -### `transcend inventory scan-packages` - -```txt -USAGE - transcend inventory scan-packages (--auth value) [--scanPath value] [--ignoreDirs value]... [--repositoryName value] [--transcendUrl value] - transcend inventory scan-packages --help - -Transcend scans packages and dependencies for the following frameworks: - -- package.json -- requirements.txt & setup.py -- Podfile -- Package.resolved -- build.gradle -- pubspec.yaml -- Gemfile & .gemspec -- composer.json - -This 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: - -- repository name -- package names -- dependency names and versions -- package descriptions - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Code Scanning" - [--scanPath] File path in the project to scan [default = ./] - [--ignoreDirs]... List of directories to ignore in scan [separator = ,] - [--repositoryName] Name of the git repository that the package should be tied to - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Scan the current directory** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" -``` - -**Scan a specific directory** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --scanPath=./examples/ -``` - -**Ignore certain folders** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --ignoreDirs=./test,./build -``` - -**Specify the name of the repository** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --repositoryName=transcend-io/test -``` - -### `transcend inventory discover-silos` - -```txt -USAGE - transcend inventory discover-silos (--scanPath value) (--dataSiloId value) (--auth value) [--fileGlobs value] [--ignoreDirs value] [--transcendUrl value] - transcend inventory discover-silos --help - -We support scanning for new data silos in JavaScript, Python, Gradle, and CocoaPods projects. - -To 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. - -FLAGS - --scanPath File path in the project to scan - --dataSiloId The UUID of the corresponding data silo - --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. Requires scopes: "Manage Assigned Data Inventory" - [--fileGlobs] You can pass a glob syntax pattern(s) to specify additional file paths to scan. Comma-separated list of globs. [default = ""] - [--ignoreDirs] Comma-separated list of directories to ignore. [default = ""] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Scan a JavaScript package.json** - -```sh -transcend inventory discover-silos \ - --scanPath=./myJavascriptProject \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=445ee241-5f2a-477b-9948-2a3682a43d0e -``` - -**Scan multiple file types (Podfile, Gradle, etc.) in examples directory** - -```sh -transcend inventory discover-silos \ - --scanPath=./examples/ \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=b6776589-0b7d-466f-8aad-4378ffd3a321 -``` - -This call will look for all the package.json files in the scan path `./myJavascriptProject`, parse each of the dependencies into their individual package names, and send it to our Transcend backend for classification. These classifications can then be viewed [here](https://app.transcend.io/data-map/data-inventory/silo-discovery/triage). The process is the same for scanning requirements.txt, podfiles and build.gradle files. - -Here are some examples of a [Podfile](./examples/code-scanning/test-cocoa-pods/Podfile) and [Gradle file](./examples/code-scanning/test-gradle/build.gradle). - -### `transcend inventory pull-datapoints` - -```txt -USAGE - transcend inventory pull-datapoints (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--includeAttributes] [--includeGuessedCategories] [--parentCategories FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER] [--subCategories value]... - transcend inventory pull-datapoints --help - -Export the datapoints from your Data Inventory into a CSV. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Data Inventory" - [--file] The file to save datapoints to [default = ./datapoints.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] - [--includeAttributes] Whether to include attributes in the output [default = false] - [--includeGuessedCategories] Whether to include guessed categories in the output [default = false] - [--parentCategories] List of parent categories to filter by [FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER, separator = ,] - [--subCategories]... List of subcategories to filter by [separator = ,] - -h --help Print help information and exit -``` - -#### Examples - -**All arguments** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --includeGuessedCategories \ - --parentCategories=CONTACT,ID,LOCATION \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Pull datapoints for specific data silos** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Include attributes in the output** - -```sh -transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeAttributes -``` - -**Include guessed categories in the output** - -```sh -transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeGuessedCategories -``` - -**Filter by parent categories** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --parentCategories=ID,LOCATION -``` - -**Filter by subcategories** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 -``` - -**Specify the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend inventory pull-unstructured-discovery-files` - -```txt -USAGE - transcend inventory pull-unstructured-discovery-files (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--subCategories value]... [--status MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED] [--includeEncryptedSnippets] - transcend inventory pull-unstructured-discovery-files --help - -This command allows for pulling Unstructured Discovery into a CSV. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Data Inventory" - [--file] The file to save datapoints to [default = ./unstructured-discovery-files.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] - [--subCategories]... List of data categories to filter by [separator = ,] - [--status] List of classification statuses to filter by [MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED, separator = ,] - [--includeEncryptedSnippets] Whether to include encrypted snippets of the entries classified [default = false] - -h --help Print help information and exit -``` - -#### Examples - -**All arguments** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --file=./unstructured-discovery-files.csv \ - --transcendUrl=https://api.us.transcend.io \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ - --status=VALIDATED,MANUALLY_ADDED,CORRECTED \ - --includeEncryptedSnippets -``` - -**Specify the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --transcendUrl=https://api.us.transcend.io -``` - -**Pull entries for specific data silos** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Filter by data categories** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 -``` - -**Filter by classification status (exclude unconfirmed recommendations)** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --status=VALIDATED,MANUALLY_ADDED,CORRECTED -``` - -**Filter by classification status (include rejected recommendations)** - -```sh -transcend inventory pull-unstructured-discovery-files --auth="$TRANSCEND_API_KEY" --status=REJECTED -``` - -### `transcend inventory derive-data-silos-from-data-flows` - -```txt -USAGE - transcend inventory derive-data-silos-from-data-flows (--auth value) (--dataFlowsYmlFolder value) (--dataSilosYmlFolder value) [--ignoreYmls value]... [--transcendUrl value] - transcend inventory derive-data-silos-from-data-flows --help - -Given a folder of data flow transcend.yml configurations, convert those configurations to set of data silo transcend.yml configurations. - -FLAGS - --auth The Transcend API key. No scopes are required for this command. - --dataFlowsYmlFolder The folder that contains data flow yml files - --dataSilosYmlFolder The folder that contains data silo yml files - [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to data silo configurations in folder** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ -``` - -**Use with US backend** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ \ - --transcendUrl=https://api.us.transcend.io -``` - -**Skip a set of yml files** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ \ - --ignoreYmls=Skip.yml,Other.yml -``` - -### `transcend inventory derive-data-silos-from-data-flows-cross-instance` - -```txt -USAGE - transcend inventory derive-data-silos-from-data-flows-cross-instance (--auth value) (--dataFlowsYmlFolder value) [--output value] [--ignoreYmls value]... [--transcendUrl value] - transcend inventory derive-data-silos-from-data-flows-cross-instance --help - -Given a folder of data flow transcend.yml configurations, convert those configurations to a single transcend.yml configurations of all related data silos. - -FLAGS - --auth The Transcend API key. No scopes are required for this command. - --dataFlowsYmlFolder The folder that contains data flow yml files - [--output] The output transcend.yml file containing the data silo configurations [default = ./transcend.yml] - [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to data silo configurations in file** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ -``` - -**Use with US backend** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --transcendUrl=https://api.us.transcend.io -``` - -**Skip a set of yml files** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --ignoreYmls=Skip.yml,Other.yml -``` - -**Convert data flow configurations in folder to data silo configurations in file** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --output=./output.yml -``` - -### `transcend inventory consent-manager-service-json-to-yml` - -```txt -USAGE - transcend inventory consent-manager-service-json-to-yml [--file value] [--output value] - transcend inventory consent-manager-service-json-to-yml --help - -Import the services from an airgap.js file into a Transcend instance. - -1. Run `await airgap.getMetadata()` on a site with airgap -2. Right click on the printed object, and click `Copy object` -3. Place output of file in a file named `services.json` -4. Run: - - transcend inventory consent-manager-service-json-to-yml --file=./services.json --output=./transcend.yml - -5. Run: - - transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./transcend.yml --classifyService - -FLAGS - [--file] Path to the services.json file, output of await airgap.getMetadata() [default = ./services.json] - [--output] Path to the output transcend.yml to write to [default = ./transcend.yml] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to yml in ./transcend.yml** - -```sh -transcend inventory consent-manager-service-json-to-yml -``` - -**With file locations** - -```sh -transcend inventory consent-manager-service-json-to-yml --file=./folder/services.json --output=./folder/transcend.yml -``` - -### `transcend inventory consent-managers-to-business-entities` - -```txt -USAGE - transcend inventory consent-managers-to-business-entities (--consentManagerYmlFolder value) [--output value] - transcend inventory consent-managers-to-business-entities --help - -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. - -FLAGS - --consentManagerYmlFolder Path to the folder of Consent Manager transcend.yml files to combine - [--output] Path to the output transcend.yml with business entity configuration [default = ./combined-business-entities.yml] - -h --help Print help information and exit -``` - -#### Examples - -**Combine files in folder to file ./combined-business-entities.yml** - -```sh -transcend inventory consent-managers-to-business-entities --consentManagerYmlFolder=./working/consent-managers/ -``` - -**Specify custom output file** - -```sh -transcend inventory consent-managers-to-business-entities \ - --consentManagerYmlFolder=./working/consent-managers/ \ - --output=./custom.yml -``` - -### `transcend admin generate-api-keys` - -```txt -USAGE - transcend admin generate-api-keys (--email value) (--password value) (--apiKeyTitle value) (--file value) (--scopes View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories) [--deleteExistingApiKey] [--createNewApiKey] [--parentOrganizationId value] [--transcendUrl value] - transcend admin generate-api-keys --help - -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. - -Unlike 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. - -Authentication 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". - -FLAGS - --email The email address that you use to log into Transcend - --password The password for your account login - --apiKeyTitle The title of the API key being generated or destroyed - --file The file where API keys should be written to - --scopes The list of scopes that should be given to the API key [View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories, separator = ,] - [--deleteExistingApiKey/--noDeleteExistingApiKey] When true, if an API key exists with the specified apiKeyTitle, the existing API key is deleted [default = true] - [--createNewApiKey/--noCreateNewApiKey] When true, new API keys will be created. Set to false if you simply want to delete all API keys with a title [default = true] - [--parentOrganizationId] Filter for only a specific organization by ID, returning all child accounts associated with that organization - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Generate API keys for cross-instance usage** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --transcendUrl=https://api.us.transcend.io -``` - -**Filter for only a specific organization by ID, returning all child accounts associated with that organization** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --parentOrganizationId=7098bb38-070d-4f26-8fa4-1b61b9cdef77 -``` - -**Delete all API keys with a certain title** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --createNewApiKey=false -``` - -**Throw error if an API key already exists with that title, default behavior is to delete the existing API key and create a new one with that same title** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --deleteExistingApiKey=false -``` - -**Find your organization ID** - -You can use the following GQL query on the [EU GraphQL Playground](https://api.us.transcend.io/graphql) or [US GraphQL Playground](https://api.us.transcend.io/graphql) to get your organization IDs and their parent/child relationships. - -```gql -query { - user { - organization { - id - parentOrganizationId - } - } -} -``` - -### `transcend migration sync-ot` - -```txt -USAGE - transcend migration sync-ot [--hostname value] [--oneTrustAuth value] [--source oneTrust|file] [--transcendAuth value] [--transcendUrl value] [--file value] [--resource assessments] [--dryRun] [--debug] - transcend migration sync-ot --help - -Pulls resources from a OneTrust and syncs them to a Transcend instance. For now, it only supports retrieving OneTrust Assessments. - -This command can be helpful if you are looking to: -- Pull resources from your OneTrust account. -- Migrate your resources from your OneTrust account to Transcend. - -OneTrust authentication requires an OAuth Token with scope for accessing the assessment endpoints. -If syncing the resources to Transcend, you will also need to generate an API key on the Transcend Admin Dashboard. - -FLAGS - [--hostname] The domain of the OneTrust environment from which to pull the resource - [--oneTrustAuth] The OAuth access token with the scopes necessary to access the OneTrust Public APIs - [--source] Whether to read the assessments from OneTrust or from a file [oneTrust|file, default = oneTrust] - [--transcendAuth] The Transcend API key. Requires scopes: "Manage Assessments" - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the file to pull the resource into. Must be a json file! - [--resource] The resource to pull from OneTrust. For now, only assessments is supported [assessments, default = assessments] - [--dryRun] Whether to export the resource to a file rather than sync to Transcend [default = false] - [--debug] Whether to print detailed logs in case of error [default = false] - -h --help Print help information and exit -``` - -#### Authentication - -In order to use this command, you will need to generate a OneTrust OAuth Token with scope for accessing the following endpoints: - -- [GET /v2/assessments](https://developer.onetrust.com/onetrust/reference/getallassessmentbasicdetailsusingget) -- [GET /v2/assessments/{assessmentId}/export](https://developer.onetrust.com/onetrust/reference/exportassessmentusingget) -- [GET /risks/{riskId}](https://developer.onetrust.com/onetrust/reference/getriskusingget) -- [GET /v2/Users/{userId}](https://developer.onetrust.com/onetrust/reference/getuserusingget) - -To learn how to generate the token, see the [OAuth 2.0 Scopes](https://developer.onetrust.com/onetrust/reference/oauth-20-scopes) and [Generate Access Token](https://developer.onetrust.com/onetrust/reference/getoauthtoken) pages. - -#### Examples - -**Syncs all assessments from the OneTrust instance to Transcend** - -```sh -transcend migration sync-ot \ - --hostname=trial.onetrust.com \ - --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ - --transcendAuth="$TRANSCEND_API_KEY" -``` - -**Set dryRun to true and sync the resource to disk (writes out file to ./oneTrustAssessments.json)** - -```sh -transcend migration sync-ot \ - --hostname=trial.onetrust.com \ - --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ - --dryRun \ - --file=./oneTrustAssessments.json -``` - -**Sync to Transcend by reading from file instead of OneTrust** - -```sh -transcend migration sync-ot --source=file --file=./oneTrustAssessments.json --transcendAuth="$TRANSCEND_API_KEY" -``` - - - -## Prompt Manager - -If you are integrating Transcend's Prompt Manager into your code, it may look like: - -```ts -import * as t from 'io-ts'; -import { TranscendPromptManager } from '@transcend-io/cli'; -import { - ChatCompletionMessage, - PromptRunProductArea, -} from '@transcend-io/privacy-types'; - -/** - * Example prompt integration - */ -export async function main(): Promise { - // Instantiate the Transcend Prompt Manager instance - const promptManager = new TranscendPromptManager({ - // API key - transcendApiKey: process.env.TRANSCEND_API_KEY, - // Define the prompts that are stored in Transcend - prompts: { - test: { - // identify by ID - id: '30bcaa79-889a-4af3-842d-2e8ba443d36d', - // no runtime variables - paramCodec: t.type({}), - // response is list of strings - outputCodec: t.array(t.string), - }, - json: { - // identify by title - title: 'test', - // one runtime variable "test" - paramCodec: t.type({ test: t.string }), - // runtime is json object - outputCodec: t.record(t.string, t.string), - // response is stored in atg - extractFromTag: 'json', - }, - predictProductLine: { - // identify by title - title: 'Predict Product Line', - // runtime parameter for slack channel name - paramCodec: t.type({ - slackChannelName: t.string, - }), - // response is specific JSON shape - outputCodec: t.type({ - product: t.union([t.string, t.null]), - clarification: t.union([t.string, t.null]), - }), - // response is stored in atg - extractFromTag: 'json', - }, - }, - // Optional arguments - // transcendUrl: 'https://api.us.transcend.io', // defaults to 'https://api.transcend.io' - // requireApproval: false, // defaults to true - // cacheDuration: 1000 * 60 * 60, // defaults to undefined, no cache - // defaultVariables: { myVariable: 'this is custom', other: [{ name: 'custom' }] }, // defaults to {} - // handlebarsOptions: { helpers, templates }, // defaults to {} - }); - - // Fetch the prompt from Transcend and template any variables - // in this case, we template the slack channel name in the LLM prompt - const systemPrompt = await promptManager.compilePrompt('predictProductLine', { - slackChannelName: channelName, - }); - - // Parameters to pass to the LLM - const input: ChatCompletionMessage[] = [ - { - role: 'system', - content: systemPrompt, - }, - { - role: 'user', - content: input, - }, - ]; - const largeLanguageModel = { - name: 'gpt-4', - client: 'openai' as const, - }; - const temperature = 1; - const topP = 1; - const maxTokensToSample = 1000; - - // Run prompt against LLM - let response: string; - const t0 = new Date().getTime(); - try { - response = await openai.createCompletion(input, { - temperature, - top_p: topP, - max_tokens: maxTokensToSample, - }); - } catch (err) { - // report error upon failure - await promptManager.reportPromptRunError('predictProductLine', { - promptRunMessages: input, - duration: new Date().getTime() - t0, - temperature, - topP, - error: err.message, - maxTokensToSample, - largeLanguageModel, - }); - } - const t1 = new Date().getTime(); - - // Parsed response as JSON and do not report to Transcend - // const parsedResponse = promptManager.parseAiResponse( - // 'predictProductLine', - // response, - // ); - - // Parsed response as JSON and report output to Transcend - const parsedResponse = await promptManager.reportAndParsePromptRun( - 'predictProductLine', - { - promptRunMessages: [ - ...input, - { - role: 'assistant', - content: response, - }, - ], - duration: t1 - t0, - temperature, - topP, - maxTokensToSample, - largeLanguageModel, - // Optional parameters - // name, // unique identifier for this run - // productArea, // Transcend product area that the prompt relates to - // runByEmployeeEmail, // Employee email that is executing the request - // promptGroupId, // The prompt group being reported - }, - ); -} -``` - -## Proxy usage - -If you are trying to use the CLI inside a corporate firewall and need to send traffic through a proxy, you can do so via the `http_proxy` environment variable,with a command like `http_proxy=http://localhost:5051 transcend inventory pull --auth=$TRANSCEND_API_KEY`. diff --git a/src/commands/consent/upload-preferences/artifacts/ExportManager.ts b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts new file mode 100644 index 00000000..3976d50e --- /dev/null +++ b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts @@ -0,0 +1,173 @@ +/* eslint-disable no-continue */ +import { resolve } from 'node:path'; +import { mkdirSync, writeFileSync } from 'node:fs'; +import { artifactAbsPath, type ExportKindWithCsv } from './artifactAbsPath'; +import { + copyToClipboard, + openPath, + extractBlocks, + isLogError, + isLogWarn, + type ExportArtifactResult, + type ExportStatusMap, + type LogExportKind, + revealInFileManager, +} from '../../../../lib/pooling'; +import { readSafe } from '../../../../lib/helpers'; + +/** + * Write the exports index file with the latest paths for each export kind. + */ +export class ExportManager { + constructor(private exportsDir?: string) {} + + /** + * Get the absolute path for an export artifact based on its kind. + * + * @param kind - The kind of the export artifact + * @param exportStatus - The status of the export + * @returns The absolute path for the export artifact + */ + artifactPath( + kind: ExportKindWithCsv, + exportStatus?: ExportStatusMap, + ): string { + return artifactAbsPath( + kind, + this.exportsDir, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (exportStatus as any)?.[kind], + ); + } + + /** + * Open an export artifact in the default application for that file type. + * + * @param kind - The kind of export artifact to open + * @param status - Optional status of the export artifact + * @returns An object containing the success status and the path of the artifact + */ + async open( + kind: ExportKindWithCsv, + status?: ExportStatusMap, + ): Promise { + const path = this.artifactPath(kind, status); + return { ok: await openPath(path), path }; + } + + /** + * Reveal an export artifact in the file manager. + * + * @param kind - The kind of export artifact to reveal + * @param status - Optional status of the export artifact + * @returns An object containing the success status and the path of the artifact + */ + async reveal( + kind: ExportKindWithCsv, + status?: ExportStatusMap, + ): Promise { + const path = this.artifactPath(kind, status); + return { ok: await revealInFileManager(path), path }; + } + + /** + * Copy the absolute path of an export artifact to the clipboard. + * + * @param kind - The kind of export artifact to copy + * @param status - Optional status of the export artifact + * @returns An object containing the success status and the path of the artifact + */ + async copy( + kind: ExportKindWithCsv, + status?: ExportStatusMap, + ): Promise { + const path = this.artifactPath(kind, status); + return { ok: await copyToClipboard(path), path }; + } + + /** + * Export combined logs from the slot log paths. + * + * @param slotLogPaths - A map of slot IDs to their log paths + * @param kind - The kind of logs to export + * @returns The absolute path to the combined logs file + */ + exportCombinedLogs( + slotLogPaths: Map< + number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { outPath?: string; errPath?: string; [k: string]: any } | undefined + >, + kind: LogExportKind, + ): string { + if (!this.exportsDir) throw new Error('exportsDir not set'); + + mkdirSync(this.exportsDir, { recursive: true }); + const ts = new Date().toISOString().replace(/[:.]/g, '-'); + const outPath = resolve( + this.exportsDir, + kind === 'error' + ? `combined-errors-${ts}.log` + : kind === 'warn' + ? `combined-warns-${ts}.log` + : kind === 'info' + ? `combined-info-${ts}.log` + : `combined-all-${ts}.log`, + ); + + const lines: string[] = []; + + for (const [, paths] of slotLogPaths) { + if (!paths) continue; + if (kind === 'all') { + [paths.outPath, paths.errPath, paths.structuredPath] + .filter(Boolean) + .forEach((p) => { + const t = readSafe(p); + if (t) lines.push(...t.split('\n').filter(Boolean)); + }); + continue; + } + if (kind === 'info') { + const text = readSafe(paths.infoPath) || readSafe(paths.outPath); + if (text) lines.push(...text.split('\n').filter(Boolean)); + continue; + } + if (kind === 'warn') { + let text = readSafe(paths.warnPath); + if (!text) { + const stderr = readSafe(paths.errPath); + if (stderr) { + const blocks = extractBlocks( + stderr, + (cl) => isLogWarn(cl) && !isLogError(cl), + ); + if (blocks.length) text = blocks.join('\n\n'); + } + } + if (text) lines.push(...text.split('\n').filter(Boolean)); + continue; + } + // error + let text = readSafe(paths.errorPath); + if (!text) { + const stderr = readSafe(paths.errPath); + if (stderr) { + const blocks = extractBlocks(stderr, (cl) => isLogError(cl)); + if (blocks.length) text = blocks.join('\n\n'); + } + } + if (text) lines.push(...text.split('\n').filter(Boolean)); + } + + lines.sort((a, b) => { + const ta = a.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + const tb = b.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; + return ta.localeCompare(tb); + }); + + writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); + return outPath; + } +} +/* eslint-enable no-continue */ diff --git a/src/commands/consent/upload-preferences/artifacts/artifactAbsPath.ts b/src/commands/consent/upload-preferences/artifacts/artifactAbsPath.ts new file mode 100644 index 00000000..58e8843a --- /dev/null +++ b/src/commands/consent/upload-preferences/artifacts/artifactAbsPath.ts @@ -0,0 +1,46 @@ +import { join, resolve } from 'node:path'; +import type { LogExportKind } from '../../../../lib/pooling'; + +export interface ExportArtifactStatus { + /** The absolute path to the export artifact */ + path: string; + /** The timestamp when the artifact was saved */ + savedAt?: number; + /** Whether the artifact was successfully exported */ + exported?: boolean; +} + +/** + * The kind of export artifact to retrieve the path for. + */ +export type ExportKindWithCsv = LogExportKind | 'failures-csv'; + +/** + * Get the absolute path for an export artifact based on its kind. + * + * @param kind - The kind of export artifact + * @param exportsDir - Optional directory where exports are stored + * @param status - Optional status of the export artifact + * @returns The absolute path to the export artifact + */ +export function artifactAbsPath( + kind: ExportKindWithCsv, + exportsDir?: string, + status?: ExportArtifactStatus, +): string { + const fallbackName = + kind === 'error' + ? 'combined-errors.log' + : kind === 'warn' + ? 'combined-warns.log' + : kind === 'info' + ? 'combined-info.log' + : kind === 'all' + ? 'combined-all.log' + : 'failing-updates.csv'; + + const rawPath = + status?.path || + (exportsDir ? join(exportsDir, fallbackName) : '(set exportsDir)'); + return rawPath.startsWith('(') ? rawPath : resolve(rawPath); +} diff --git a/src/commands/consent/upload-preferences/artifacts/index.ts b/src/commands/consent/upload-preferences/artifacts/index.ts new file mode 100644 index 00000000..b583d7f2 --- /dev/null +++ b/src/commands/consent/upload-preferences/artifacts/index.ts @@ -0,0 +1,4 @@ +export * from './artifactAbsPath'; +export * from './ExportManager'; +export * from './writeExportsIndex'; +export * from './writeFailingUpdatesCsv'; diff --git a/src/commands/consent/upload-preferences/artifacts/writeExportsIndex.ts b/src/commands/consent/upload-preferences/artifacts/writeExportsIndex.ts new file mode 100644 index 00000000..63c300a3 --- /dev/null +++ b/src/commands/consent/upload-preferences/artifacts/writeExportsIndex.ts @@ -0,0 +1,57 @@ +// artifacts/indexWriter.ts +import { join } from 'node:path'; +import { mkdirSync, writeFileSync } from 'node:fs'; +import { pathToFileURL } from 'node:url'; +import { artifactAbsPath, type ExportKindWithCsv } from './artifactAbsPath'; +import type { ExportStatusMap } from '../../../../lib/pooling'; + +let lastIndexFileContents = ''; + +/** + * Get the absolute path for an export artifact based on its kind. + * + * @param exportsDir - Optional directory where exports are stored + * @param exportStatus - Optional status of the export artifact + * @param exportsFile - The name of the exports index file + * @returns The absolute path to the export artifact + */ +export function writeExportsIndex( + exportsDir?: string, + exportStatus?: ExportStatusMap, + exportsFile = 'exports.index.txt', +): string | undefined { + if (!exportsDir) return undefined; + const lines: string[] = ['# Export artifacts — latest paths', '']; + + const kinds: Array< + [ + ExportKindWithCsv, + ExportStatusMap[keyof ExportStatusMap] | undefined, + string, + ] + > = [ + ['error', exportStatus?.error, 'Errors log'], + ['warn', exportStatus?.warn, 'Warnings log'], + ['info', exportStatus?.info, 'Info log'], + ['all', exportStatus?.all, 'All logs'], + ['failures-csv', exportStatus?.failuresCsv, 'Failing updates (CSV)'], + ]; + + for (const [k, st, label] of kinds) { + const abs = artifactAbsPath(k, exportsDir, st); + const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; + lines.push(`${label}:`); + lines.push(` path: ${abs}`); + lines.push(` url: ${url}`); + lines.push(''); + } + + const content = lines.join('\n'); + const out = join(exportsDir, exportsFile); + if (content !== lastIndexFileContents) { + mkdirSync(exportsDir, { recursive: true }); + writeFileSync(out, `${content}\n`, 'utf8'); + lastIndexFileContents = content; + } + return out; +} diff --git a/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts b/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts new file mode 100644 index 00000000..88dbc4de --- /dev/null +++ b/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts @@ -0,0 +1,41 @@ +import { mkdirSync, writeFileSync } from 'node:fs'; +import type { FailingUpdateRow } from '../receipts'; + +/** + * Write a CSV file for failing updates. + * + * @param folderName - The folder where the CSV file will be written + * @param rows - The rows to write to the CSV file + * @param outPath - The output path for the CSV file + * @returns The absolute path to the written CSV file + */ +export function writeFailingUpdatesCsv( + folderName: string, + rows: FailingUpdateRow[], + outPath: string, +): string { + mkdirSync(folderName, { recursive: true }); + const headers = Array.from( + rows.reduce>((acc, row) => { + Object.keys(row || {}).forEach((k) => acc.add(k)); + return acc; + }, new Set()), + ); + const esc = (v: unknown): string => { + if (v == null) return ''; + const s = + typeof v === 'string' + ? v + : typeof v === 'object' + ? JSON.stringify(v) + : String(v); + return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; + }; + const lines = [ + headers.join(','), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...rows.map((r) => headers.map((h) => esc((r as any)[h])).join(',')), + ]; + writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); + return outPath; +} diff --git a/src/commands/consent/upload-preferences/buildTaskOptions.ts b/src/commands/consent/upload-preferences/buildTaskOptions.ts new file mode 100644 index 00000000..d8723eea --- /dev/null +++ b/src/commands/consent/upload-preferences/buildTaskOptions.ts @@ -0,0 +1,92 @@ +// helpers/buildCommon.ts +import type { UploadPreferencesCommandFlags } from './impl'; + +/** Common options shared by upload tasks */ +export type TaskCommonOpts = Pick< + UploadPreferencesCommandFlags, + | 'auth' + | 'partition' + | 'sombraAuth' + | 'directory' + | 'transcendUrl' + | 'skipConflictUpdates' + | 'uploadConcurrency' + | 'uploadLogInterval' + | 'maxChunkSize' + | 'rateLimitRetryDelay' + | 'maxRecordsToReceipt' + | 'skipWorkflowTriggers' + | 'skipExistingRecordCheck' + | 'isSilent' + | 'dryRun' + | 'attributes' + | 'forceTriggerWorkflows' + | 'allowedIdentifierNames' + | 'identifierColumns' + | 'columnsToIgnore' +> & { + schemaFile: string; + receiptsFolder: string; +}; + +/** + * Copy the options from the main command over to the spawned tasks + * + * @param flags - All flags + * @param schemaFile - Schema file + * @param receiptsFolder - Receipts folder + * @returns Common task options + */ +export function buildCommonOpts( + flags: UploadPreferencesCommandFlags, + schemaFile: string, + receiptsFolder: string, +): TaskCommonOpts { + const { + auth, + directory, + sombraAuth, + partition, + transcendUrl, + skipConflictUpdates, + skipWorkflowTriggers, + skipExistingRecordCheck, + isSilent, + dryRun, + attributes, + forceTriggerWorkflows, + allowedIdentifierNames, + identifierColumns, + uploadConcurrency, + maxChunkSize, + rateLimitRetryDelay, + maxRecordsToReceipt, + uploadLogInterval, + columnsToIgnore = [], + } = flags; + + return { + schemaFile, + receiptsFolder, + auth, + directory, + sombraAuth, + partition, + transcendUrl, + skipConflictUpdates, + skipWorkflowTriggers, + skipExistingRecordCheck, + isSilent, + dryRun, + attributes, + forceTriggerWorkflows, + allowedIdentifierNames, + identifierColumns, + uploadConcurrency, + maxChunkSize, + rateLimitRetryDelay, + maxRecordsToReceipt, + uploadLogInterval, + columnsToIgnore, + }; +} diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 2deb9116..27d7c632 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -2,8 +2,7 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; -import { join } from 'node:path'; -import { mkdirSync, readFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; @@ -16,30 +15,35 @@ import { computePoolSize, getWorkerLogPaths, isIpcOpen, - renderDashboard, safeSend, - showCombinedLogs, spawnWorkerProcess, -} from '../../../lib/pooling'; -import { installInteractiveSwitcher } from '../../../lib/pooling/installInteractiveSwitcher'; -import { + isWorkerProgressMessage, + isWorkerReadyMessage, + isWorkerResultMessage, classifyLogLevel, makeLineSplitter, - resetWorkerLogs, -} from '../../../lib/pooling/logRotation'; -import type { WorkerState } from '../../../lib/pooling/assignWorkToWorker'; + initLogDir, + buildExportStatus, + assignWorkToSlot, + refillIdleWorkers, + safeGetLogPathsForSlot, + WorkerLogPaths, + WorkerMaps, + WorkerState, + makeOnKeypressExtra, + installInteractiveSwitcher, +} from '../../../lib/pooling'; import { RateCounter } from '../../../lib/helpers'; -import { resolveReceiptPath } from './resolveReceiptPath'; -import { - exportCombinedLogs, - readFailingUpdatesFromReceipt, - writeFailingUpdatesCsv, - type FailingUpdateRow, -} from '../../../lib/pooling/downloadArtifact'; -import type { - ExportStatusMap, - ExportArtifactStatus, -} from '../../../lib/pooling/renderDashboard'; +import { renderDashboard, AnyTotals } from './ui'; +import { writeFailingUpdatesCsv, ExportManager } from './artifacts'; + +import { applyReceiptSummary, FailingUpdateRow } from './receipts'; +import { buildCommonOpts } from './buildTaskOptions'; + +function getCurrentModulePath(): string { + if (typeof __filename !== 'undefined') return __filename as unknown as string; + return process.argv[1]; +} /** CLI flags */ export interface UploadPreferencesCommandFlags { @@ -70,142 +74,22 @@ export interface UploadPreferencesCommandFlags { columnsToIgnore?: string[]; } -/** Common options shared by upload tasks */ -export type TaskCommonOpts = Pick< - UploadPreferencesCommandFlags, - | 'auth' - | 'partition' - | 'sombraAuth' - | 'directory' - | 'transcendUrl' - | 'skipConflictUpdates' - | 'uploadConcurrency' - | 'uploadLogInterval' - | 'maxChunkSize' - | 'rateLimitRetryDelay' - | 'maxRecordsToReceipt' - | 'skipWorkflowTriggers' - | 'skipExistingRecordCheck' - | 'isSilent' - | 'dryRun' - | 'attributes' - | 'forceTriggerWorkflows' - | 'allowedIdentifierNames' - | 'identifierColumns' - | 'columnsToIgnore' -> & { - schemaFile: string; - receiptsFolder: string; -}; - -/** Totals union types for renderer */ -type UploadModeTotals = { - mode: 'upload'; - success: number; - skipped: number; - error: number; - errors: Record; -}; -type CheckModeTotals = { - mode: 'check'; - totalPending: number; - pendingConflicts: number; - pendingSafe: number; - skipped: number; -}; -type AnyTotals = UploadModeTotals | CheckModeTotals; - -/** - * Summarize a receipts JSON into dashboard counters. - * - * @param receiptPath - * @param dryRun - */ -function summarizeReceipt(receiptPath: string, dryRun: boolean): AnyTotals { - try { - const raw = readFileSync(receiptPath, 'utf8'); - const json = JSON.parse(raw) as any; - - const skippedCount = Object.values(json?.skippedUpdates ?? {}).length; - - if (!dryRun) { - const success = Object.values(json?.successfulUpdates ?? {}).length; - const failed = Object.values(json?.failingUpdates ?? {}).length; - const errors: Record = {}; - Object.values(json?.failingUpdates ?? {}).forEach((v) => { - const msg = (v as any)?.error ?? 'Unknown error'; - errors[msg] = (errors[msg] ?? 0) + 1; - }); - return { - mode: 'upload', - success, - skipped: skippedCount, - error: failed, - errors, - }; - } - - const totalPending = Object.values(json?.pendingUpdates ?? {}).length; - const pendingConflicts = Object.values( - json?.pendingConflictUpdates ?? {}, - ).length; - const pendingSafe = Object.values(json?.pendingSafeUpdates ?? {}).length; - - return { - mode: 'check', - totalPending, - pendingConflicts, - pendingSafe, - skipped: skippedCount, - }; - } catch { - return !dryRun - ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } - : { - mode: 'check', - totalPending: 0, - pendingConflicts: 0, - pendingSafe: 0, - skipped: 0, - }; - } -} - -function getCurrentModulePath(): string { - // @ts-ignore - __filename exists in CJS/ts-node - if (typeof __filename !== 'undefined') return __filename as unknown as string; - return process.argv[1]; -} - export async function uploadPreferences( this: LocalContext, - { - auth, + flags: UploadPreferencesCommandFlags, +): Promise { + const { partition, - sombraAuth, - transcendUrl, file = '', directory, dryRun, skipExistingRecordCheck, receiptFileDir, schemaFilePath, - skipWorkflowTriggers, - forceTriggerWorkflows, - skipConflictUpdates, isSilent, - attributes, concurrency, - allowedIdentifierNames, - maxChunkSize, - maxRecordsToReceipt, - rateLimitRetryDelay, - identifierColumns, - uploadConcurrency, - uploadLogInterval, - columnsToIgnore = [], - }: UploadPreferencesCommandFlags, -): Promise { + } = flags; + const files = collectCsvFilesOrExit(directory, file, this); doneInputValidation(this.process.exit); @@ -232,47 +116,15 @@ export async function uploadPreferences( const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); - const common: TaskCommonOpts = { - schemaFile, - receiptsFolder, - auth, - directory, - sombraAuth, - partition, - transcendUrl, - skipConflictUpdates, - skipWorkflowTriggers, - skipExistingRecordCheck, - isSilent, - dryRun, - attributes, - forceTriggerWorkflows, - allowedIdentifierNames, - identifierColumns, - uploadConcurrency, - maxChunkSize, - rateLimitRetryDelay, - maxRecordsToReceipt, - uploadLogInterval, - columnsToIgnore, - }; + const common = buildCommonOpts(flags, schemaFile, receiptsFolder); // ---- Worker pool lifecycle ---- - const LOG_DIR = join(directory || receiptsFolder, 'logs'); - mkdirSync(LOG_DIR, { recursive: true }); - - const RESET_MODE = - (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate'; - resetWorkerLogs(LOG_DIR, RESET_MODE); - + const logDir = initLogDir(directory || receiptsFolder); const modulePath = getCurrentModulePath(); const workers = new Map(); const workerState = new Map(); - const slotLogPaths = new Map< - number, - ReturnType | undefined - >(); + const slotLogPaths = new Map(); const failingUpdatesMem: FailingUpdateRow[] = []; const pending = [...files]; @@ -280,7 +132,13 @@ export async function uploadPreferences( let activeWorkers = 0; const agg: AnyTotals = !common.dryRun - ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } + ? { + mode: 'upload', + success: 0, + skipped: 0, + error: 0, + errors: {} as Record, + } : { mode: 'check', totalPending: 0, @@ -289,17 +147,13 @@ export async function uploadPreferences( skipped: 0, }; - // Export status shown permanently in the dashboard - const exportStatus: ExportStatusMap = { - error: { path: join(LOG_DIR, 'combined-errors.log') }, - warn: { path: join(LOG_DIR, 'combined-warns.log') }, - info: { path: join(LOG_DIR, 'combined-info.log') }, - all: { path: join(LOG_DIR, 'combined-all.log') }, - failuresCsv: { path: join(LOG_DIR, 'failing-updates.csv') }, - }; + // Export status + manager + const exportStatus = buildExportStatus(logDir); + const exportMgr = new ExportManager(logDir); + // Dashboard let dashboardPaused = false; - const repaint = (final = false) => { + const repaint = (final = false): void => { if (dashboardPaused && !final) return; renderDashboard({ poolSize, @@ -315,81 +169,24 @@ export async function uploadPreferences( r10s: meter.rate(10_000), r60s: meter.rate(60_000), }, - exportsDir: LOG_DIR, + exportsDir: logDir, exportStatus, }); }; - const assignWorkToSlot = (id: number) => { - const w = workers.get(id); - if (!isIpcOpen(w)) { - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - return; - } - - const filePath = pending.shift(); - if (!filePath) { - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - return; - } - - workerState.set(id, { - busy: true, - file: filePath, - startedAt: Date.now(), - lastLevel: 'ok', - progress: undefined, - }); - - if ( - !safeSend(w!, { type: 'task', payload: { filePath, options: common } }) - ) { - // IPC closed between check and send; requeue and mark idle - pending.unshift(filePath); - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - } - }; - - const refillIdleWorkers = () => { - for (const [id] of workers) { - const st = workerState.get(id); - if (!st || !st.busy) { - if (pending.length === 0) break; - assignWorkToSlot(id); - } - } - }; + const maps: WorkerMaps = { workers, workerState, slotLogPaths }; + const assign = (id: number): void => + assignWorkToSlot(id, pending, common, maps); + const refill = (): void => refillIdleWorkers(pending, maps, assign); - // Spawn the pool + // Spawn pool for (let i = 0; i < poolSize; i += 1) { const child = spawnWorkerProcess({ id: i, modulePath, - logDir: LOG_DIR, + logDir, openLogWindows: true, isSilent, - // childFlag omitted: spawn uses its own default CHILD_FLAG }); workers.set(i, child); workerState.set(i, { @@ -414,6 +211,7 @@ export async function uploadPreferences( }); child.stderr?.on('data', errLine); + // eslint-disable-next-line @typescript-eslint/no-explicit-any child.on('error', (err: any) => { if ( err?.code === 'ERR_IPC_CHANNEL_CLOSED' || @@ -425,21 +223,21 @@ export async function uploadPreferences( logger.error(colors.red(`Worker ${i} error: ${err?.stack || err}`)); }); - child.on('message', (msg: any) => { + // eslint-disable-next-line no-loop-func + child.on('message', (msg: unknown): void => { if (!msg || typeof msg !== 'object') return; - if (msg.type === 'ready') { - refillIdleWorkers(); + if (isWorkerReadyMessage(msg)) { + refill(); repaint(); return; } - if (msg.type === 'progress') { + if (isWorkerProgressMessage(msg)) { const { successDelta, successTotal, fileTotal, filePath } = msg.payload || {}; liveSuccessTotal += successDelta || 0; - // Update that worker’s live progress bar const prev = workerState.get(i)!; const processed = successTotal ?? prev.progress?.processed ?? 0; const total = fileTotal ?? prev.progress?.total ?? 0; @@ -454,37 +252,20 @@ export async function uploadPreferences( return; } - if (msg.type === 'result') { + if (isWorkerResultMessage(msg)) { const { ok, filePath, receiptFilepath } = msg.payload || {}; if (ok) totals.completed += 1; else totals.failed += 1; - const resolved = - (typeof receiptFilepath === 'string' && receiptFilepath) || - resolveReceiptPath(common.receiptsFolder, filePath); - if (resolved) { - const summary = summarizeReceipt(resolved, common.dryRun); - - failingUpdatesMem.push( - ...readFailingUpdatesFromReceipt(resolved, filePath), - ); - if (summary.mode === 'upload' && agg.mode === 'upload') { - agg.success += summary.success; - agg.skipped += summary.skipped; - agg.error += summary.error; - Object.entries(summary.errors).forEach(([k, v]) => { - (agg.errors as Record)[k] = - (agg.errors[k] ?? 0) + (v as number); - }); - } else if (summary.mode === 'check' && agg.mode === 'check') { - agg.totalPending += summary.totalPending; - agg.pendingConflicts += summary.pendingConflicts; - agg.pendingSafe += summary.pendingSafe; - agg.skipped += summary.skipped; - } - } + applyReceiptSummary({ + receiptsFolder: common.receiptsFolder, + filePath, + receiptFilepath, + agg, + dryRun, + failingUpdatesMem, + }); - // Mark idle; keep ERROR badge for failed task until next assignment const prev = workerState.get(i)!; workerState.set(i, { ...prev, @@ -495,14 +276,14 @@ export async function uploadPreferences( progress: undefined, }); - refillIdleWorkers(); + refill(); repaint(); } }); + // eslint-disable-next-line no-loop-func child.on('exit', (code, signal) => { activeWorkers -= 1; - const prev = workerState.get(i)!; const abnormal = (typeof code === 'number' && code !== 0) || !!signal; @@ -523,7 +304,7 @@ export async function uploadPreferences( // graceful Ctrl+C let cleanupSwitcher: () => void = () => {}; - const onSigint = () => { + const onSigint = (): void => { clearInterval(renderInterval); cleanupSwitcher(); process.stdout.write('\nStopping workers...\n'); @@ -531,18 +312,20 @@ export async function uploadPreferences( if (isIpcOpen(w)) safeSend(w!, { type: 'shutdown' }); try { w?.kill('SIGTERM'); - } catch {} + } catch { + // noop + } } this.process.exit(130); }; process.once('SIGINT', onSigint); // attach/switch UI with replay - const detachScreen = () => { + const detachScreen = (): void => { dashboardPaused = false; repaint(); }; - const attachScreen = (id: number) => { + const attachScreen = (id: number): void => { dashboardPaused = true; process.stdout.write('\x1b[2J\x1b[H'); process.stdout.write( @@ -550,137 +333,34 @@ export async function uploadPreferences( ); }; - function safeGetLogPathsForSlot(id: number) { - const live = workers.get(id); - if (isIpcOpen(live)) { - try { - const p = getWorkerLogPaths(live!); - if (p !== undefined && p !== null) return p; - } catch { - /* fall back */ - } - } - return slotLogPaths.get(id); - } - cleanupSwitcher = installInteractiveSwitcher({ workers, onAttach: attachScreen, onDetach: detachScreen, onCtrlC: onSigint, - getLogPaths: (id: number) => safeGetLogPathsForSlot(id), + getLogPaths: (id: number) => + safeGetLogPathsForSlot(id, workers, slotLogPaths), replayBytes: 200 * 1024, replayWhich: ['out', 'err'], onEnterAttachScreen: attachScreen, }); - // Lowercase keys open viewers; Uppercase keys write export files. - // e -> errors viewer (stderr filtered to ERROR) - // w -> warnings viewer (warn file + stderr non-error) - // i -> info viewer (info file) - // l -> all logs viewer (out + err + structured) - // E -> export combined errors - // W -> export combined warns - // I -> export combined info - // A -> export combined ALL logs - // F -> export failing-updates CSV - // Esc / Ctrl+] -> return to dashboard - const onKeypressExtra = (buf: Buffer): void => { - const s = buf.toString('utf8'); - - const view = ( - sources: Array<'out' | 'err' | 'structured' | 'warn' | 'info'>, - level: 'error' | 'warn' | 'all', - ) => { - dashboardPaused = true; - showCombinedLogs(slotLogPaths, sources, level); - }; - - const noteExport = (slot: keyof ExportStatusMap, p: string) => { - const now = Date.now(); - const current: ExportArtifactStatus = exportStatus[slot] || { path: p }; - exportStatus[slot] = { - path: p || current.path, - savedAt: now, - exported: true, - }; - repaint(); - }; - - // --- viewers (lowercase) --- - if (s === 'e') return view(['err'], 'error'); - if (s === 'w') return view(['warn', 'err'], 'warn'); - if (s === 'i') return view(['info'], 'all'); - if (s === 'l') return view(['out', 'err', 'structured'], 'all'); - - // --- exports (uppercase) --- - if (s === 'E') { - void exportCombinedLogs(slotLogPaths, 'error', LOG_DIR) - .then((p) => { - process.stdout.write(`\nWrote combined error logs to: ${p}\n`); - noteExport('error', p); - }) - .catch(() => - process.stdout.write('\nFailed to write combined error logs\n'), - ); - return; - } - if (s === 'W') { - void exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR) - .then((p) => { - process.stdout.write(`\nWrote combined warn logs to: ${p}\n`); - noteExport('warn', p); - }) - .catch(() => - process.stdout.write('\nFailed to write combined warn logs\n'), - ); - return; - } - if (s === 'I') { - void exportCombinedLogs(slotLogPaths, 'info', LOG_DIR) - .then((p) => { - process.stdout.write(`\nWrote combined info logs to: ${p}\n`); - noteExport('info', p); - }) - .catch(() => - process.stdout.write('\nFailed to write combined info logs\n'), - ); - return; - } - if (s === 'A') { - void exportCombinedLogs(slotLogPaths, 'all', LOG_DIR) - .then((p) => { - process.stdout.write(`\nWrote combined ALL logs to: ${p}\n`); - noteExport('all', p); - }) - .catch(() => - process.stdout.write('\nFailed to write combined ALL logs\n'), - ); - return; - } - if (s === 'F') { - const dest = join(LOG_DIR, 'failing-updates.csv'); - void writeFailingUpdatesCsv(failingUpdatesMem, dest) - .then((p) => { - process.stdout.write(`\nWrote failing updates CSV to: ${p}\n`); - noteExport('failuresCsv', p); - }) - .catch(() => - process.stdout.write('\nFailed to write failing updates CSV\n'), - ); - return; - } - - // --- back to dashboard --- - if (s === '\x1b' || s === '\x1d') { - dashboardPaused = false; - repaint(); - } - }; + // key handlers (viewer/export/dashboard) + const onKeypressExtra = makeOnKeypressExtra({ + slotLogPaths, + exportMgr, + exportStatus, + onRepaint: () => repaint(), + onPause: (p) => { + dashboardPaused = p; + }, + }); try { process.stdin.setRawMode?.(true); - } catch {} + } catch { + // ignore if not supported (e.g. Windows) + } process.stdin.resume(); process.stdin.on('data', onKeypressExtra); @@ -701,16 +381,17 @@ export async function uploadPreferences( // Final “auto-export” of artifacts try { - const e = await exportCombinedLogs(slotLogPaths, 'error', LOG_DIR); + const e = exportMgr.exportCombinedLogs(slotLogPaths, 'error'); exportStatus.error = { path: e, savedAt: Date.now(), exported: true }; - const w = await exportCombinedLogs(slotLogPaths, 'warn', LOG_DIR); + const w = exportMgr.exportCombinedLogs(slotLogPaths, 'warn'); exportStatus.warn = { path: w, savedAt: Date.now(), exported: true }; - const i = await exportCombinedLogs(slotLogPaths, 'info', LOG_DIR); + const i = exportMgr.exportCombinedLogs(slotLogPaths, 'info'); exportStatus.info = { path: i, savedAt: Date.now(), exported: true }; - const a = await exportCombinedLogs(slotLogPaths, 'all', LOG_DIR); + const a = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); exportStatus.all = { path: a, savedAt: Date.now(), exported: true }; - const fPath = join(LOG_DIR, 'failing-updates.csv'); - await writeFailingUpdatesCsv(failingUpdatesMem, fPath); + const fPath = join(logDir, 'failing-updates.csv'); + const folderName = resolve(logDir, '../'); + await writeFailingUpdatesCsv(folderName, failingUpdatesMem, fPath); exportStatus.failuresCsv = { path: fPath, savedAt: Date.now(), @@ -720,21 +401,21 @@ export async function uploadPreferences( `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${a}\n ${fPath}\n\n`, ); } catch { - // ignore + // noop } // Final repaint with exportStatus visible & green repaint(true); - if ((agg as AnyTotals).mode === 'upload') { - const a = agg as UploadModeTotals; + if (agg.mode === 'upload') { + const a = agg as any; process.stdout.write( colors.green( `\nAll done. Success:${a.success.toLocaleString()} Skipped:${a.skipped.toLocaleString()} Error:${a.error.toLocaleString()}\n`, ), ); } else { - const a = agg as CheckModeTotals; + const a = agg as any; process.stdout.write( colors.green( `\nAll done. Pending:${a.totalPending.toLocaleString()} PendingConflicts:${a.pendingConflicts.toLocaleString()} ` + @@ -756,7 +437,7 @@ export async function uploadPreferences( // --- Viewer mode: leave switcher active until user presses 'q' --- await new Promise((resolveViewer) => { - const onKeypress = (buf: Buffer) => { + const onKeypress = (buf: Buffer): void => { const s = buf.toString('utf8'); if (s === 'q' || s === 'Q') { process.stdin.off('data', onKeypress); @@ -765,7 +446,9 @@ export async function uploadPreferences( }; try { process.stdin.setRawMode?.(true); - } catch {} + } catch { + // noop + } process.stdin.resume(); process.stdin.on('data', onKeypress); }); diff --git a/src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts b/src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts new file mode 100644 index 00000000..10dd07db --- /dev/null +++ b/src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts @@ -0,0 +1,60 @@ +import type { AnyTotals } from '../ui/buildFrameModel'; +import { readFailingUpdatesFromReceipt } from './readFailingUpdatesFromReceipt'; +import { resolveReceiptPath } from './resolveReceiptPath'; +import { summarizeReceipt } from './summarizeReceipt'; + +/** + * Applies the summary of a receipt to the overall aggregation. + * + * @param opts - Options for applying the receipt summary + */ +export function applyReceiptSummary(opts: { + /** Folder where receipts are stored */ + receiptsFolder: string; + /** Path to the file being processed */ + filePath: string; + /** Path to the receipt file, if different from the default */ + receiptFilepath?: string | null; + /** Aggregation object to update */ + agg: AnyTotals; + /** Whether this is a dry run (no actual updates) */ + dryRun: boolean; + /** Array to collect failing updates from the receipt */ + failingUpdatesMem: Array; +}): void { + const { + receiptsFolder, + filePath, + receiptFilepath, + agg, + dryRun, + failingUpdatesMem, + } = opts; + + const resolved = + (typeof receiptFilepath === 'string' && receiptFilepath) || + resolveReceiptPath(receiptsFolder, filePath); + + if (!resolved) return; + + const summary = summarizeReceipt(resolved, dryRun); + + // collect failing updates + failingUpdatesMem.push(...readFailingUpdatesFromReceipt(resolved, filePath)); + + // merge totals + if (summary.mode === 'upload' && agg.mode === 'upload') { + agg.success += summary.success; + agg.skipped += summary.skipped; + agg.error += summary.error; + Object.entries(summary.errors).forEach(([k, v]) => { + (agg.errors as Record)[k] = + (agg.errors[k] ?? 0) + (v as number); + }); + } else if (summary.mode === 'check' && agg.mode === 'check') { + agg.totalPending += summary.totalPending; + agg.pendingConflicts += summary.pendingConflicts; + agg.pendingSafe += summary.pendingSafe; + agg.skipped += summary.skipped; + } +} diff --git a/src/commands/consent/upload-preferences/receipts/index.ts b/src/commands/consent/upload-preferences/receipts/index.ts new file mode 100644 index 00000000..16e82d18 --- /dev/null +++ b/src/commands/consent/upload-preferences/receipts/index.ts @@ -0,0 +1,5 @@ +export * from './readFailingUpdatesFromReceipt'; +export * from './summarizeReceipt'; +export * from './receiptsState'; +export * from './resolveReceiptPath'; +export * from './applyReceiptSummary'; diff --git a/src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts b/src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts new file mode 100644 index 00000000..9210951c --- /dev/null +++ b/src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts @@ -0,0 +1,49 @@ +import { readFileSync } from 'node:fs'; + +/** A single failing update row we will output to CSV */ +export interface FailingUpdateRow { + /** The primary key / userId from receipts map key */ + primaryKey: string; + /** When the upload attempt happened (ISO string) */ + uploadedAt: string; + /** Error message */ + error: string; + /** JSON-encoded "update" body (compact) */ + updateJson: string; + /** Optional source file the row came from (helps triage) */ + sourceFile?: string; +} + +/** + * Parse failing updates out of a receipts.json file. + * Returns rows you can merge into your in-memory buffer. + * + * @param receiptPath - The path to the receipts.json file + * @param sourceFile - Optional source file for context + * @returns An array of FailingUpdateRow objects + */ +export function readFailingUpdatesFromReceipt( + receiptPath: string, + sourceFile?: string, +): FailingUpdateRow[] { + try { + const raw = readFileSync(receiptPath, 'utf8'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = JSON.parse(raw) as any; + const failing = json?.failingUpdates ?? {}; + const out: FailingUpdateRow[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + for (const [primaryKey, val] of Object.entries(failing)) { + out.push({ + primaryKey, + uploadedAt: val?.uploadedAt ?? '', + error: val?.error ?? '', + updateJson: val?.update ? JSON.stringify(val.update) : '', + sourceFile, + }); + } + return out; + } catch { + return []; + } +} diff --git a/src/commands/consent/upload-preferences/receiptsState.ts b/src/commands/consent/upload-preferences/receipts/receiptsState.ts similarity index 98% rename from src/commands/consent/upload-preferences/receiptsState.ts rename to src/commands/consent/upload-preferences/receipts/receiptsState.ts index 6998d44e..a2508d5e 100644 --- a/src/commands/consent/upload-preferences/receiptsState.ts +++ b/src/commands/consent/upload-preferences/receipts/receiptsState.ts @@ -5,7 +5,7 @@ import { type PendingSafePreferenceUpdates, type PendingWithConflictPreferenceUpdates, type PreferenceUpdateMap, -} from '../../../lib/preference-management'; +} from '../../../../lib/preference-management'; export type PreferenceReceiptsInterface = { /** Path to file */ diff --git a/src/commands/consent/upload-preferences/resolveReceiptPath.ts b/src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts similarity index 95% rename from src/commands/consent/upload-preferences/resolveReceiptPath.ts rename to src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts index 3267468f..cfa234a5 100644 --- a/src/commands/consent/upload-preferences/resolveReceiptPath.ts +++ b/src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts @@ -1,5 +1,5 @@ import { join } from 'node:path'; -import { getFilePrefix } from './computeFiles'; +import { getFilePrefix } from '../computeFiles'; import { existsSync, readdirSync, statSync } from 'node:fs'; /** diff --git a/src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts b/src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts new file mode 100644 index 00000000..c69292f1 --- /dev/null +++ b/src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts @@ -0,0 +1,64 @@ +import { readFileSync } from 'node:fs'; +import type { AnyTotals } from '../ui/buildFrameModel'; + +/** + * Summarize a receipts JSON into dashboard counters. + * + * @param receiptPath - The path to the receipt file + * @param dryRun - Whether this is a dry run (no actual upload) + * @returns An object summarizing the receipt data + */ +export function summarizeReceipt( + receiptPath: string, + dryRun: boolean, +): AnyTotals { + try { + const raw = readFileSync(receiptPath, 'utf8'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const json = JSON.parse(raw) as any; + + const skippedCount = Object.values(json?.skippedUpdates ?? {}).length; + + if (!dryRun) { + const success = Object.values(json?.successfulUpdates ?? {}).length; + const failed = Object.values(json?.failingUpdates ?? {}).length; + const errors: Record = {}; + Object.values(json?.failingUpdates ?? {}).forEach((v) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const msg = (v as any)?.error ?? 'Unknown error'; + errors[msg] = (errors[msg] ?? 0) + 1; + }); + return { + mode: 'upload', + success, + skipped: skippedCount, + error: failed, + errors, + }; + } + + const totalPending = Object.values(json?.pendingUpdates ?? {}).length; + const pendingConflicts = Object.values( + json?.pendingConflictUpdates ?? {}, + ).length; + const pendingSafe = Object.values(json?.pendingSafeUpdates ?? {}).length; + + return { + mode: 'check', + totalPending, + pendingConflicts, + pendingSafe, + skipped: skippedCount, + }; + } catch { + return !dryRun + ? { mode: 'upload', success: 0, skipped: 0, error: 0, errors: {} } + : { + mode: 'check', + totalPending: 0, + pendingConflicts: 0, + pendingSafe: 0, + skipped: 0, + }; + } +} diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index c12e2aff..1049f14f 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -1,27 +1,50 @@ -// runChild.ts import { mkdirSync, createWriteStream } from 'node:fs'; import { join, dirname } from 'node:path'; import { getFilePrefix } from './computeFiles'; import { splitCsvToList } from '../../../lib/requests'; -import type { TaskCommonOpts } from './impl'; import { interactivePreferenceUploaderFromPlan } from './upload/interactivePreferenceUploaderFromPlan'; import { makeSchemaState } from './schemaState'; -import { makeReceiptsState } from './receiptsState'; +import { makeReceiptsState } from './receipts/receiptsState'; import { buildTranscendGraphQLClient, createSombraGotInstance, } from '../../../lib/graphql'; import { logger } from '../../../logger'; import { buildInteractiveUploadPreferencePlan } from './upload/buildInteractiveUploadPlan'; +import type { TaskCommonOpts } from './buildTaskOptions'; +export interface TaskMessage { + type: 'task'; + payload: { + filePath: string; + options: TaskCommonOpts; + }; +} + +export interface ShutdownMessage { + type: 'shutdown'; +} + +export type ParentMessage = TaskMessage | ShutdownMessage; + +/** + * Run the child process for handling upload preferences. + * This runs in a separate CPU if possible + */ export async function runChild(): Promise { + // Get worker ID from environment or default to 0 const workerId = Number(process.env.WORKER_ID || '0'); + + // Determine log file path from environment or default location const logFile = process.env.WORKER_LOG || join(process.cwd(), `logs/worker-${workerId}.log`); mkdirSync(dirname(logFile), { recursive: true }); + // Create a writable stream for logging const logStream = createWriteStream(logFile, { flags: 'a' }); + + // Helper function to write logs with timestamp and worker ID const log = (...args: unknown[]): void => { const line = `[w${workerId}] ${new Date().toISOString()} ${args .map((a) => String(a)) @@ -29,27 +52,32 @@ export async function runChild(): Promise { logStream.write(line); }; + // Log that the worker is ready and send a ready message to parent logger.info(`[w${workerId}] ready pid=${process.pid}`); process.send?.({ type: 'ready' }); - process.on('message', async (msg: any) => { + // Listen for messages from the parent process + process.on('message', async (msg: ParentMessage) => { if (!msg || typeof msg !== 'object') return; + // Handle 'task' messages to process a file if (msg.type === 'task') { const { filePath, options } = msg.payload as { filePath: string; options: TaskCommonOpts; }; + // Compute the path for receipts file const receiptFilepath = join( options.receiptsFolder, `${getFilePrefix(filePath)}-receipts.json`, ); try { + // Ensure receipts directory exists mkdirSync(dirname(receiptFilepath), { recursive: true }); logger.info(`[w${workerId}] START ${filePath}`); log(`START ${filePath}`); - // Construct common options + // Construct common state objects for the task const receipts = makeReceiptsState(receiptFilepath); const schema = await makeSchemaState(options.schemaFile); const client = buildTranscendGraphQLClient( @@ -62,7 +90,7 @@ export async function runChild(): Promise { options.sombraAuth, ); - // Step 1: Build the plan (validation-only) + // Step 1: Build the upload plan (validation-only) const plan = await buildInteractiveUploadPreferencePlan({ sombra, client, @@ -79,7 +107,7 @@ export async function runChild(): Promise { attributes: splitCsvToList(options.attributes), }); - // Step 2: Execute the upload (no parsing/validation here) + // Step 2: Execute the upload using the plan await interactivePreferenceUploaderFromPlan(plan, { receipts, sombra, @@ -92,8 +120,8 @@ export async function runChild(): Promise { maxChunkSize: options.maxChunkSize, uploadConcurrency: options.uploadConcurrency, maxRecordsToReceipt: options.maxRecordsToReceipt, + // Report progress to parent process onProgress: ({ successDelta, successTotal, fileTotal }) => { - // Emit progress messages up to the parent process.send?.({ type: 'progress', payload: { @@ -106,6 +134,7 @@ export async function runChild(): Promise { }, }); + // Log completion and send result to parent logger.info(`[w${workerId}] DONE ${filePath}`); log(`SUCCESS ${filePath}`); @@ -113,7 +142,9 @@ export async function runChild(): Promise { type: 'result', payload: { ok: true, filePath, receiptFilepath }, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { + // Handle errors, log them, and send failure result to parent const e = err?.stack || err?.message || String(err); logger.error( `[w${workerId}] ERROR ${filePath}: ${err?.message || err}\n\n${e}`, @@ -126,22 +157,28 @@ export async function runChild(): Promise { process.exit(1); } } else if (msg.type === 'shutdown') { + // Handle shutdown message: log and exit gracefully logger.info(`[w${workerId}] shutdown`); log('Shutting down.'); logStream.end(() => process.exit(0)); } }); + // Handle uncaught exceptions: log and exit process.on('uncaughtException', (err) => { logger.error(`[w${workerId}] uncaughtException: ${err?.stack || err}`); log(`uncaughtException\n${err?.stack || err}`); logStream.end(() => process.exit(1)); }); + // Handle unhandled promise rejections: log and exit process.on('unhandledRejection', (reason) => { logger.error(`[w${workerId}] unhandledRejection: ${String(reason)}`); log(`unhandledRejection\n${String(reason)}`); logStream.end(() => process.exit(1)); }); - await new Promise(() => {}); + // Keep the process alive indefinitely + await new Promise(() => { + // Keep the process alive + }); } diff --git a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts new file mode 100644 index 00000000..857ae9b3 --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts @@ -0,0 +1,164 @@ +// ui/frameModel.ts +import type { ExportStatusMap, WorkerState } from '../../../../lib/pooling'; + +export type UploadModeTotals = { + /** The mode of the export, always 'upload' */ + mode: 'upload'; + /** Number of records successfully uploaded */ + success: number; + /** Number of records skipped */ + skipped: number; + /** Number of records failed */ + error: number; + /** Number of records that were not processed */ + errors: Record; +}; + +export type CheckModeTotals = { + /** The mode of the export, always 'check' */ + mode: 'check'; + /** Number of records pending */ + pendingConflicts: number; + /** Number of records pending safe */ + pendingSafe: number; + /** Number of records pending */ + totalPending: number; + /** Number of records skipped */ + skipped: number; +}; + +/** + * Represents the totals for either upload or check mode. + */ +export type AnyTotals = UploadModeTotals | CheckModeTotals; + +export interface RenderDashboardInput { + /** The size of the worker pool */ + poolSize: number; + /** The number of CPU cores available */ + cpuCount: number; + /** Total number of files to process */ + filesTotal: number; + /** Number of files that have been completed */ + filesCompleted: number; + /** Number of files that have failed */ + filesFailed: number; + /** Map of worker ID to WorkerState */ + workerState: Map; + /** Totals for the current operation */ + totals?: AnyTotals; + /** Throughput statistics */ + throughput?: { successSoFar: number; r10s: number; r60s: number }; + /** Whether this is the final render (e.g., for a summary) */ + final?: boolean; + /** Directory where export artifacts are stored */ + exportsDir?: string; + /** Status of the export artifacts */ + exportStatus?: ExportStatusMap; +} + +export type FrameModel = { + /** The input parameters for the dashboard */ + input: RenderDashboardInput; + /** Number of workers currently processing files */ + inProgress: number; + /** Total number of files that have been completed */ + completedFiles: number; + /** Percentage of completion */ + pct: number; + /** Estimated total jobs to be processed */ + estTotalJobs?: number; + /** Estimated time of arrival text */ + etaText: string; +}; + +/** + * Builds the frame model for the dashboard. + * This function calculates various statistics based on the input parameters + * and returns a FrameModel object that can be used to render the dashboard. + * + * + * @param input - The input parameters for the dashboard. + * @returns The frame model for the dashboard. + */ +export function buildFrameModel(input: RenderDashboardInput): FrameModel { + const { + filesTotal, + filesCompleted, + filesFailed, + workerState, + totals, + throughput, + } = input; + + const inProgress = [...workerState.values()].filter((s) => s.busy).length; + const completedFiles = filesCompleted + filesFailed; + const pct = + filesTotal === 0 + ? 100 + : Math.floor((completedFiles / Math.max(1, filesTotal)) * 100); + + // receipts-based estimation + const jobsFromReceipts = + totals && totals.mode === 'upload' + ? (totals as UploadModeTotals).success + + (totals as UploadModeTotals).skipped + + (totals as UploadModeTotals).error + : undefined; + + const inflightJobsKnown = [...workerState.values()].reduce((sum, s) => { + const t = s.progress?.total ?? 0; + return sum + (t > 0 && s.busy ? t : 0); + }, 0); + + const avgJobsPerCompletedFile = + jobsFromReceipts !== undefined && completedFiles > 0 + ? jobsFromReceipts / completedFiles + : undefined; + + const remainingFiles = Math.max(filesTotal - completedFiles - inProgress, 0); + + let estTotalJobs: number | undefined; + if (avgJobsPerCompletedFile !== undefined) { + estTotalJobs = + (jobsFromReceipts ?? 0) + + inflightJobsKnown + + remainingFiles * avgJobsPerCompletedFile; + } else if (inProgress > 0) { + const avgInFlight = + inflightJobsKnown > 0 ? inflightJobsKnown / inProgress : 0; + if (avgInFlight > 0) { + estTotalJobs = inflightJobsKnown + remainingFiles * avgInFlight; + } + } + + // ETA + let etaText = ''; + if (throughput && estTotalJobs !== undefined) { + const uploaded = throughput.successSoFar; + const remainingJobs = Math.max(estTotalJobs - uploaded, 0); + const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; + if (ratePerSec > 0 && remainingJobs > 0) { + const secondsLeft = Math.round(remainingJobs / ratePerSec); + const eta = new Date(Date.now() + secondsLeft * 1000); + const hours = Math.floor(secondsLeft / 3600); + const minutes = Math.floor((secondsLeft % 3600) / 60); + const timeLeft = + hours > 0 + ? `${hours}h ${minutes}m` + : minutes > 0 + ? `${minutes}m` + : `${secondsLeft}s`; + etaText = `Expected completion: ${eta.toLocaleTimeString()} (${timeLeft} left)`; + } + } + + return { + input, + inProgress, + completedFiles, + pct, + estTotalJobs, + etaText, + }; +} diff --git a/src/commands/consent/upload-preferences/ui/headerLines.ts b/src/commands/consent/upload-preferences/ui/headerLines.ts new file mode 100644 index 00000000..b57c389f --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/headerLines.ts @@ -0,0 +1,228 @@ +// ui/lines.ts +import colors from 'colors'; +import { basename, resolve, join } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import type { + CheckModeTotals, + FrameModel, + UploadModeTotals, +} from './buildFrameModel'; +import { osc8Link, ExportStatusMap } from '../../../../lib/pooling'; +import type { ExportArtifactStatus } from '../artifacts/artifactAbsPath'; + +const fmtNum = (n: number): string => n.toLocaleString(); +const fmtTime = (ts?: number): string => + ts ? new Date(ts).toLocaleTimeString() : '—'; + +/** + * Generates header lines for the dashboard. + * This includes the status of the upload, worker information, and throughput statistics. + * + * @param m - The frame model containing the dashboard input and statistics. + * @returns An array of strings representing the header lines. + */ +export function headerLines(m: FrameModel): string[] { + const { input, inProgress, pct, etaText } = m; + const { + poolSize, + cpuCount, + filesTotal, + filesCompleted, + filesFailed, + throughput, + exportsDir, + totals, + } = input; + + const redIf = (n: number, s: string): string => (n > 0 ? colors.red(s) : s); + const barWidth = 40; + const filled = Math.floor((pct / 100) * barWidth); + const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); + + let estTotalJobsText = colors.dim('Est. total jobs: —'); + if (m.estTotalJobs !== undefined) { + estTotalJobsText = colors.dim( + `Est. total jobs: ${fmtNum(Math.round(m.estTotalJobs))}`, + ); + } + + const header = [ + `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( + `(CPU avail: ${cpuCount})`, + )}`, + `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim( + 'Completed', + )} ${fmtNum(filesCompleted)} ` + + `${colors.dim('Failed')} ${redIf( + filesFailed, + fmtNum(filesFailed), + )} ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, + `[${bar}] ${pct}% ${estTotalJobsText} ${ + etaText ? colors.magenta(etaText) : '' + }`, + ]; + if (exportsDir) header.push(colors.dim(`Exports dir: ${exportsDir}`)); + if (throughput) { + const perHour10 = Math.round(throughput.r10s * 3600); + const perHour60 = Math.round(throughput.r60s * 3600); + header.push( + colors.cyan( + `Throughput: ${fmtNum(perHour10)}/hr (1h: ${fmtNum( + perHour60, + )}/hr) Newly uploaded: ${fmtNum(throughput.successSoFar)}`, + ), + ); + } + if (totals) header.push(totalsBlock(totals)); + return header.filter(Boolean); +} + +/** + * Generates a block of text summarizing the upload or check mode totals. + * + * @param totals - The totals object containing upload or check mode totals. + * @returns A string representing the totals block. + */ +export function totalsBlock( + totals: UploadModeTotals | CheckModeTotals, +): string { + if (totals.mode === 'upload') { + const t = totals as UploadModeTotals; + const errorsList = Object.entries(t.errors || {}).map( + ([msg, count]) => + ` ${colors.red(`Count[${fmtNum(count)}]`)} ${colors.red(msg)}`, + ); + return [ + errorsList.length + ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` + : '', + `${colors.bold('Receipts totals')} — Success: ${fmtNum( + t.success, + )} Skipped: ${fmtNum(t.skipped)} Error: ${ + t.error ? colors.red(fmtNum(t.error)) : fmtNum(t.error) + }`, + ] + .filter(Boolean) + .join('\n\n'); + } + const t = totals as CheckModeTotals; + return ( + `${colors.bold('Receipts totals')} — Pending: ${fmtNum(t.totalPending)} ` + + `PendingConflicts: ${fmtNum(t.pendingConflicts)} PendingSafe: ${fmtNum( + t.pendingSafe, + )} ` + + `Skipped: ${fmtNum(t.skipped)}` + ); +} + +/** + * Generates worker lines for the dashboard. + * + * @param m - The frame model containing the dashboard input and statistics. + * @returns An array of strings representing the worker lines. + */ +export function workerLines(m: FrameModel): string[] { + const { workerState } = m.input; + const miniWidth = 18; + return [...workerState.entries()].map(([id, s]) => { + const badge = + s.lastLevel === 'error' + ? colors.red('ERROR ') + : s.lastLevel === 'warn' + ? colors.yellow('WARN ') + : s.busy + ? colors.green('WORKING') + : colors.dim('IDLE '); + const fname = s.file ? basename(s.file) : '-'; + const elapsed = s.startedAt + ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` + : '-'; + const processed = s.progress?.processed ?? 0; + const total = s.progress?.total ?? 0; + const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; + const ff = Math.floor((pctw / 100) * miniWidth); + const mini = + total > 0 + ? '█'.repeat(ff) + '░'.repeat(miniWidth - ff) + : ' '.repeat(miniWidth); + const miniTxt = + total > 0 + ? `${fmtNum(processed)}/${fmtNum(total)} (${pctw}%)` + : colors.dim('—'); + return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; + }); +} + +/** + * Generates a line for hotkeys and controls. + * This includes information on how to interact with the dashboard. + * + * @param poolSize - The size of the worker pool. + * @param final - Whether this is the final render (e.g., for a summary). + * @returns A string representing the hotkeys line. + */ +export function hotkeysLine(poolSize: number, final?: boolean): string { + const maxDigit = Math.min(poolSize - 1, 9); + const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + return final + ? colors.dim( + 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', + ) + : colors.dim( + `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit`, + ); +} + +/** + * Generates a block of text summarizing the export artifacts. + * This includes links to open or copy the paths of the artifacts. + * + * @param exportsDir - The directory where export artifacts are stored. + * @param exportStatus - The status of the export artifacts. + * @returns A string representing the export block. + */ +export function exportBlock( + exportsDir: string | undefined, + exportStatus: ExportStatusMap, +): string { + const makeLine = ( + key: 'E' | 'W' | 'I' | 'A' | 'F', + label: string, + status?: ExportArtifactStatus, + fallback?: string, + ): string => { + const exported = !!status?.exported; + const raw = + status?.path || + (exportsDir + ? join(exportsDir, fallback ?? `${label.toLowerCase()}.log`) + : '(set exportsDir)'); + const abs = raw.startsWith('(') ? raw : resolve(raw); + const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; + const openText = exported ? colors.green('open') : colors.dim('open'); + const openLink = abs.startsWith('(') ? openText : osc8Link(abs, openText); + const time = fmtTime(status?.savedAt); + const dot = exported ? colors.green('●') : colors.dim('○'); + return `${dot} ${colors.bold(`${key}=export-${label}`)}: ${openLink} ${ + exported ? colors.green(abs) : colors.dim(abs) + } ${colors.dim(`(last saved: ${time})`)}\n ${colors.dim( + 'url:', + )} ${url}`; + }; + + return [ + colors.dim('Exports (Cmd/Ctrl-click “open” or copy the plain path):'), + ` ${makeLine('E', 'errors', exportStatus.error, 'combined-errors.log')}`, + ` ${makeLine('W', 'warns', exportStatus.warn, 'combined-warns.log')}`, + ` ${makeLine('I', 'info', exportStatus.info, 'combined-info.log')}`, + ` ${makeLine('A', 'all', exportStatus.all, 'combined-all.log')}`, + ` ${makeLine( + 'F', + 'failures-csv', + exportStatus.failuresCsv, + 'failing-updates.csv', + )}`, + colors.dim(' (Also written to exports.index.txt for easy copying.)'), + ].join('\n'); +} diff --git a/src/commands/consent/upload-preferences/ui/index.ts b/src/commands/consent/upload-preferences/ui/index.ts new file mode 100644 index 00000000..76c150e4 --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/index.ts @@ -0,0 +1,3 @@ +export * from './buildFrameModel'; +export * from './renderDashboard'; +export * from './headerLines'; diff --git a/src/commands/consent/upload-preferences/ui/renderDashboard.ts b/src/commands/consent/upload-preferences/ui/renderDashboard.ts new file mode 100644 index 00000000..342b1132 --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/renderDashboard.ts @@ -0,0 +1,47 @@ +// ui/render.ts +import * as readline from 'node:readline'; +import { buildFrameModel, type RenderDashboardInput } from './buildFrameModel'; +import { + exportBlock, + headerLines, + hotkeysLine, + workerLines, +} from './headerLines'; +import { writeExportsIndex } from '../artifacts/writeExportsIndex'; + +let lastFrame = ''; + +/** + * Renders the dashboard for the upload preferences command. + * This function builds the frame model and writes the exports index. + * It then constructs the header, worker lines, hotkeys line, and export block, + * and outputs the complete frame to the console. + * + * @param input - The input parameters for rendering the dashboard. + */ +export function renderDashboard(input: RenderDashboardInput): void { + const m = buildFrameModel(input); + writeExportsIndex(input.exportsDir, input.exportStatus); + + const frame = [ + ...headerLines(m), + '', + ...workerLines(m), + '', + hotkeysLine(input.poolSize, input.final), + '', + exportBlock(input.exportsDir, input.exportStatus ?? {}), + ].join('\n'); + + if (!input.final && frame === lastFrame) return; + lastFrame = frame; + + if (!input.final) { + process.stdout.write('\x1b[?25l'); + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + } else { + process.stdout.write('\x1b[?25h'); + } + process.stdout.write(`${frame}\n`); +} diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index df18a749..1661203f 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -6,7 +6,7 @@ import { loadReferenceData, type PreferenceUploadReferenceData, } from './loadReferenceData'; -import { type PreferenceReceiptsInterface } from '../receiptsState'; +import { type PreferenceReceiptsInterface } from '../receipts/receiptsState'; import { type PreferenceSchemaInterface } from '../schemaState'; import { parsePreferenceManagementCsvWithCache } from '../../../../lib/preference-management'; import type { diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index e90f117c..813242db 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -8,7 +8,7 @@ import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; import { RETRYABLE_BATCH_STATUSES } from '../../../../constants'; import { extractErrorMessage, limitRecords } from '../../../../lib/helpers'; import type { InteractiveUploadPreferencePlan } from './buildInteractiveUploadPlan'; -import type { PreferenceReceiptsInterface } from '../receiptsState'; +import type { PreferenceReceiptsInterface } from '../receipts'; import type { Got } from 'got'; /** @@ -114,7 +114,8 @@ export async function interactivePreferenceUploaderFromPlan( colors.magenta( `Uploading ${ Object.values(pendingUpdates).length - } preferences to partition: ${partition}`, + } preferences to partition: ${partition}. Concurrency: ${uploadConcurrency}, Max Chunk Size: ${maxChunkSize}` + + `, Max Records to Receipt: ${maxRecordsToReceipt}`, ), ); diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index 2c652a04..acce1e3e 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -9,3 +9,4 @@ export * from './splitInHalf'; export * from './retrySamePromise'; export * from './limitRecords'; export * from './RateCounter'; +export * from './readSafe'; diff --git a/src/lib/helpers/readSafe.ts b/src/lib/helpers/readSafe.ts new file mode 100644 index 00000000..49e5b2ff --- /dev/null +++ b/src/lib/helpers/readSafe.ts @@ -0,0 +1,16 @@ +import { readFileSync } from 'node:fs'; + +/** + * Safely reads the contents of a file as a UTF-8 string. + * Returns an empty string if the path is not provided or if reading fails. + * + * @param p - The path to the file to read. + * @returns The file contents as a string, or an empty string on error. + */ +export function readSafe(p?: string): string { + try { + return p ? readFileSync(p, 'utf8') : ''; + } catch { + return ''; + } +} diff --git a/src/lib/pooling/assignWorkToWorker.ts b/src/lib/pooling/assignWorkToWorker.ts index 86d28bee..fd389756 100644 --- a/src/lib/pooling/assignWorkToWorker.ts +++ b/src/lib/pooling/assignWorkToWorker.ts @@ -106,6 +106,7 @@ export function assignWorkToWorker( options: payload, }, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { // If the pipe closed between the check and send: requeue + idle if ( diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts index 9dcca3d4..bd8750f8 100644 --- a/src/lib/pooling/attachWorkerHandlers.ts +++ b/src/lib/pooling/attachWorkerHandlers.ts @@ -1,55 +1,14 @@ -import type { ChildProcess } from 'child_process'; -import { assignWorkToWorker, type WorkerState } from './assignWorkToWorker'; -import { appendFileSync } from 'fs'; -import { join } from 'path'; +// attachWorkerHandlers.ts +import type { ChildProcess } from 'node:child_process'; import { spawnWorkerProcess } from './spawnWorkerProcess'; -import { classifyLogLevel, makeLineSplitter } from './logRotation'; - -/** Result message from child → parent */ -export interface WorkerResultMessage { - /** */ - type: 'result'; - /** */ - payload: { - /** */ - ok: boolean; - /** */ - filePath: string; - /** */ - error?: string; - /** Optional receipts path, if child provided it */ - receiptFilepath?: string; - }; -} - -/** Ready sentinel from child → parent */ -export interface WorkerReadyMessage { - /** */ - type: 'ready'; -} - -/** Live progress from child → parent (for per-worker bars & throughput) */ -export interface WorkerProgressMessage { - /** */ - type: 'progress'; - /** */ - payload: { - /** */ - filePath: string; - /** how many just succeeded in this tick */ - successDelta: number; - /** cumulative successes for the current file */ - successTotal: number; - /** total planned records for the current file */ - fileTotal: number; - }; -} - -/** Union of all worker IPC messages */ -export type WorkerMessage = - | WorkerResultMessage - | WorkerReadyMessage - | WorkerProgressMessage; +import { assignWorkToWorker, type WorkerState } from './assignWorkToWorker'; +import { + isWorkerReadyMessage, + isWorkerProgressMessage, + isWorkerResultMessage, + type ProgressInfo, +} from './ipc'; +import { wireStderrBadges, appendFailureLog } from './diagnostics'; /** * Wire up a worker's lifecycle: @@ -58,51 +17,64 @@ export type WorkerMessage = * - on "result": update counters, clear progress, queue next work * - on "exit": optionally respawn if there is still work pending * - * @param id - the worker ID - * @param child - the child process - * @param workers - map of all worker processes - * @param workerState - map of worker visual state - * @param state - global counters (completed/failed files) - * @param filesPending - FIFO of files yet to process - * @param repaint - re-render the dashboard - * @param common - common task options to send with each assignment - * @param onAllWorkersExited - callback when pool is fully drained/exited - * @param logDir - directory to write shared failure logs - * @param modulePath - module path for spawning worker processes - * @param spawnSilent - whether to spawn workers with silent stdio - * @param onProgress - optional aggregator for throughput metrics + * Keep this file focused on orchestration; protocol lived in ipc.ts and + * diagnostics (stderr badges, failure logs) live in diagnostics.ts. + * + * @param params - Options */ -export function attachWorkerHandlers( - id: number, - child: ChildProcess, - workers: Map, - workerState: Map, +export function attachWorkerHandlers(params: { + /** Worker slot ID (used as key in `workers` and `workerState` maps) */ + id: number; + /** Child process instance representing this worker */ + child: ChildProcess; + /** Map of all active worker processes, keyed by worker ID */ + workers: Map; + /** Map of UI/visual state per worker (progress, file, last log level, etc.) */ + workerState: Map; + /** Global counters for processed files */ state: { - /** */ + /** Number of successfully completed files */ completed: number; - /** */ + /** Number of failed files */ failed: number; - }, - filesPending: string[], - repaint: () => void, - common: T, - onAllWorkersExited: () => void, - logDir: string, - modulePath: string, - spawnSilent = false, - onProgress?: (info: { - /** */ - workerId: number; - /** */ - filePath: string; - /** */ - successDelta: number; - /** */ - successTotal: number; - /** */ - fileTotal: number; - }) => void, -): void { + }; + /** Queue of pending file paths still to be processed */ + filesPending: string[]; + /** Function to trigger a dashboard re-render (reflects latest worker state) */ + repaint: () => void; + /** Common task options to send with each work assignment */ + common: T; + /** Callback fired once all workers have exited and the pool is drained */ + onAllWorkersExited: () => void; + /** Directory path where logs and failure reports are written */ + logDir: string; + /** Absolute path to the worker module script (used when respawning workers) */ + modulePath: string; + /** If true, respawned workers are created with silent stdio (no inherited stdout/stderr) */ + spawnSilent?: boolean; + /** + * Optional aggregator callback for live throughput metrics. + * Invoked on every progress tick from workers. + */ + onProgress?: (info: ProgressInfo) => void; +}): void { + const { + id, + child, + workers, + workerState, + state, + filesPending, + repaint, + common, + onAllWorkersExited, + logDir, + modulePath, + spawnSilent = false, + onProgress, + } = params; + + // Register worker & initialize visual state workers.set(id, child); const prev = workerState.get(id); workerState.set(id, { @@ -113,26 +85,12 @@ export function attachWorkerHandlers( progress: undefined, }); - // LIVE: watch stderr to flip WARN/ERROR badges - if (child.stderr) { - const onErrLine = makeLineSplitter((line) => { - // If there is an explicit tag, use it; otherwise since it came from stderr, treat as WARN. - const explicit = classifyLogLevel(line); // returns 'warn' | 'error' | null - const lvl = explicit ?? 'warn'; - const prev = workerState.get(id)!; - if (prev.lastLevel !== lvl) { - workerState.set(id, { ...prev, lastLevel: lvl }); - repaint(); - } - }); - child.stderr.on('data', onErrLine); - } + // Live badge updates from stderr + wireStderrBadges(id, child, workerState, repaint); // IPC messages from child - child.on('message', (msg: WorkerMessage) => { - if (!msg || typeof msg !== 'object') return; - - if (msg.type === 'ready') { + child.on('message', (msg: unknown) => { + if (isWorkerReadyMessage(msg)) { assignWorkToWorker(id, common, { pending: filesPending, workers, @@ -142,7 +100,7 @@ export function attachWorkerHandlers( return; } - if (msg.type === 'progress') { + if (isWorkerProgressMessage(msg)) { const { filePath, successDelta, successTotal, fileTotal } = msg.payload; // Update per-worker progress bar @@ -156,7 +114,7 @@ export function attachWorkerHandlers( }, }); - // Bubble to an optional global aggregator (for throughput) + // Bubble to optional global aggregator (for throughput) if (onProgress && successDelta) { onProgress({ workerId: id, @@ -171,7 +129,7 @@ export function attachWorkerHandlers( return; } - if (msg.type === 'result') { + if (isWorkerResultMessage(msg)) { const { ok, filePath, error } = msg.payload; // Update file-level counters @@ -192,10 +150,7 @@ export function attachWorkerHandlers( // If failure, append to a shared log for triage if (!ok && error) { - appendFileSync( - join(logDir, 'failures.log'), - `[${new Date().toISOString()}] worker ${id} file=${filePath}\n${error}\n\n`, - ); + appendFailureLog(logDir, id, filePath, error); } // Keep the worker busy until the queue is empty @@ -206,8 +161,11 @@ export function attachWorkerHandlers( repaint, }); } + + // Ignore unknown messages to keep parent resilient to future protocol changes }); + // Process exit/crash handling child.on('exit', (code, signal) => { workers.delete(id); @@ -222,32 +180,32 @@ export function attachWorkerHandlers( repaint(); // If it crashed and there's still work to do, respawn a replacement - if ((code && code !== 0) || signal) { - if (filesPending.length > 0) { - const replacement = spawnWorkerProcess({ - id, - modulePath, - logDir, - openLogWindows: true, // attempt to open tail windows for respawns too - isSilent: spawnSilent, - }); - attachWorkerHandlers( - id, - replacement, - workers, - workerState, - state, - filesPending, - repaint, - common, - onAllWorkersExited, - logDir, - modulePath, - spawnSilent, - onProgress, - ); - return; - } + const abnormal = (typeof code === 'number' && code !== 0) || !!signal; + if (abnormal && filesPending.length > 0) { + const replacement = spawnWorkerProcess({ + id, + modulePath, + logDir, + openLogWindows: true, // attempt to open tail windows for respawns too + isSilent: spawnSilent, + }); + + attachWorkerHandlers({ + id, + child: replacement, + workers, + workerState, + state, + filesPending, + repaint, + common, + onAllWorkersExited, + logDir, + modulePath, + spawnSilent, + onProgress, + }); + return; } // If all workers have exited, let the parent clean up diff --git a/src/lib/pooling/diagnostics.ts b/src/lib/pooling/diagnostics.ts new file mode 100644 index 00000000..73b4cce3 --- /dev/null +++ b/src/lib/pooling/diagnostics.ts @@ -0,0 +1,59 @@ +// diagnostics.ts +import type { ChildProcess } from 'node:child_process'; +import { appendFileSync } from 'node:fs'; +import { join } from 'node:path'; +import type { WorkerState } from './assignWorkToWorker'; +import { classifyLogLevel, makeLineSplitter } from './logRotation'; + +/** + * Wire stderr to flip WARN/ERROR badges quickly. + * + * @param id - Worker ID + * @param child - Child process + * @param workerState - Map of worker states + * @param repaint - Repaint function + */ +export function wireStderrBadges( + id: number, + child: ChildProcess, + workerState: Map, + repaint: () => void, +): void { + if (!child.stderr) return; + + const onErrLine = makeLineSplitter((line) => { + // If there is an explicit tag, use it; otherwise since it came from stderr, treat as WARN. + const explicit = classifyLogLevel(line); // 'warn' | 'error' | null + const lvl = explicit ?? 'warn'; + const curr = workerState.get(id); + if (!curr || curr.lastLevel === lvl) return; + workerState.set(id, { ...curr, lastLevel: lvl }); + repaint(); + }); + + child.stderr.on('data', onErrLine); +} + +/** + * Best-effort shared failure log write. + * + * @param logDir - Directory to write logs + * @param workerId - Worker ID + * @param filePath - File path being processed + * @param error - Error message to log + */ +export function appendFailureLog( + logDir: string, + workerId: number, + filePath: string, + error: string, +): void { + try { + appendFileSync( + join(logDir, 'failures.log'), + `[${new Date().toISOString()}] worker ${workerId} file=${filePath}\n${error}\n\n`, + ); + } catch { + // ignore log write errors + } +} diff --git a/src/lib/pooling/downloadArtifact.ts b/src/lib/pooling/downloadArtifact.ts deleted file mode 100644 index 43eface9..00000000 --- a/src/lib/pooling/downloadArtifact.ts +++ /dev/null @@ -1,222 +0,0 @@ -// lib/pooling/downloadArtifacts.ts -import { - createReadStream, - createWriteStream, - statSync, - readFileSync, - mkdirSync, -} from 'node:fs'; -import { join, basename, dirname } from 'node:path'; -import { once } from 'node:events'; -import type { getWorkerLogPaths } from './spawnWorkerProcess'; - -/** Which combined log to export */ -export type LogKind = 'error' | 'warn' | 'info' | 'all'; - -/** Convenience alias for the optional return from getWorkerLogPaths */ -type SlotPaths = Map | undefined>; -/** - * - */ -type WorkerPaths = NonNullable>; - -/** - * Choose the best source file for a given kind, with safe fallbacks: - * - error: prefer errorPath → fallback to errPath - * - warn: prefer warnPath → fallback to errPath - * - info: prefer infoPath → fallback to outPath - * - * @param kind - * @param p - */ -function pickSourcePath( - kind: Exclude, - p: WorkerPaths, -): string | undefined { - if (kind === 'error') return (p as any).errorPath || p.errPath; - if (kind === 'warn') return (p as any).warnPath || p.errPath; - // kind === 'info' - return (p as any).infoPath || p.outPath; -} - -/** - * Combine all worker logs of the given kind into a single file. - * Writes simple headers per worker and concatenates in worker-id order. - * - * @param slotLogPaths - Map of workerId -> WorkerLogPaths - * @param kind - 'error' | 'warn' | 'info' | 'all' - * @param outDir - directory to write the combined file - * @param filename - optional override (default: combined-{kind}.log) - * @returns absolute path to the combined log file - */ -export async function exportCombinedLogs( - slotLogPaths: SlotPaths, - kind: LogKind, - outDir: string, - filename?: string, -): Promise { - mkdirSync(outDir, { recursive: true }); - - const outPath = join(outDir, filename ?? `combined-${kind}.log`); - const out = createWriteStream(outPath, { flags: 'w' }); - - for (const [id, pathsMaybe] of [...slotLogPaths.entries()].sort( - (a, b) => a[0] - b[0], - )) { - if (!pathsMaybe) continue; - const p = pathsMaybe as WorkerPaths; - - // Worker header - out.write(`\n==== worker ${id} ====\n`); - - if (kind === 'all') { - // Concatenate stdout, stderr, and structured (if present) - const sources: Array<{ - /** */ label: string /** */; - /** */ - path?: string; - }> = [ - { label: 'stdout', path: p.outPath }, - { label: 'stderr', path: p.errPath }, - { label: 'structured', path: (p as any).structuredPath }, - ]; - - for (const { label, path } of sources) { - out.write( - `\n---- ${label}${path ? ` (${basename(path)})` : ''} ----\n`, - ); - if (!path) { - out.write('[unavailable]\n'); - continue; - } - try { - const st = statSync(path); - if (st.size === 0) { - out.write('[empty]\n'); - continue; - } - const rs = createReadStream(path, { encoding: 'utf8' }); - rs.pipe(out, { end: false }); - await once(rs, 'end'); - } catch { - out.write(`[unavailable: ${path}]\n`); - } - } - continue; - } - - // error / warn / info (single best source with fallback) - const srcPath = pickSourcePath(kind, p); - out.write(`(${srcPath ? basename(srcPath) : 'unavailable'})\n`); - - if (!srcPath) { - out.write('[unavailable]\n'); - continue; - } - - try { - const st = statSync(srcPath); - if (st.size === 0) { - out.write('[empty]\n'); - continue; - } - const rs = createReadStream(srcPath, { encoding: 'utf8' }); - rs.pipe(out, { end: false }); - await once(rs, 'end'); - } catch { - // skip unreadable files - out.write(`[unavailable: ${srcPath}]\n`); - } - } - - await new Promise((r) => out.end(r)); - return outPath; -} - -/** A single failing update row we will output to CSV */ -export interface FailingUpdateRow { - /** The primary key / userId from receipts map key */ - primaryKey: string; - /** When the upload attempt happened (ISO string) */ - uploadedAt: string; - /** Error message */ - error: string; - /** JSON-encoded "update" body (compact) */ - updateJson: string; - /** Optional source file the row came from (helps triage) */ - sourceFile?: string; -} - -/** - * naive CSV escape: double any quotes and wrap with quotes if needed - * - * @param v - */ -function csvCell(v: string): string { - if (v == null) return ''; - const needs = /[",\n]/.test(v); - const s = String(v).replace(/"/g, '""'); - return needs ? `"${s}"` : s; -} - -/** - * Stream an array of failing updates to a CSV file. - * Overwrites any existing file at destPath. - * - * Columns: primaryKey,uploadedAt,error,updateJson,sourceFile - * - * @param rows - * @param destPath - */ -export async function writeFailingUpdatesCsv( - rows: FailingUpdateRow[], - destPath: string, -): Promise { - mkdirSync(dirname(destPath), { recursive: true }); - const ws = createWriteStream(destPath, { flags: 'w' }); - ws.write('primaryKey,uploadedAt,error,updateJson,sourceFile\n'); - for (const r of rows) { - ws.write( - `${[ - csvCell(r.primaryKey), - csvCell(r.uploadedAt), - csvCell(r.error), - csvCell(r.updateJson), - csvCell(r.sourceFile ?? ''), - ].join(',')}\n`, - ); - } - await new Promise((r) => ws.end(r)); - return destPath; -} - -/** - * Parse failing updates out of a receipts.json file. - * Returns rows you can merge into your in-memory buffer. - * - * @param receiptPath - * @param sourceFile - */ -export function readFailingUpdatesFromReceipt( - receiptPath: string, - sourceFile?: string, -): FailingUpdateRow[] { - try { - const raw = readFileSync(receiptPath, 'utf8'); - const json = JSON.parse(raw) as any; - const failing = json?.failingUpdates ?? {}; - const out: FailingUpdateRow[] = []; - for (const [primaryKey, val] of Object.entries(failing)) { - out.push({ - primaryKey, - uploadedAt: val?.uploadedAt ?? '', - error: val?.error ?? '', - updateJson: val?.update ? JSON.stringify(val.update) : '', - sourceFile, - }); - } - return out; - } catch { - return []; - } -} diff --git a/src/lib/pooling/exportCombinedLogs.ts b/src/lib/pooling/exportCombinedLogs.ts new file mode 100644 index 00000000..9b691919 --- /dev/null +++ b/src/lib/pooling/exportCombinedLogs.ts @@ -0,0 +1,131 @@ +/* eslint-disable no-continue */ +import { once } from 'node:events'; +import { + createReadStream, + createWriteStream, + mkdirSync, + statSync, +} from 'node:fs'; +import type { SlotPaths, WorkerLogPaths } from './spawnWorkerProcess'; +import { basename, join } from 'node:path'; + +/** Which combined log to export */ +export type LogKind = 'error' | 'warn' | 'info' | 'all'; + +/** + * Choose the best source file for a given kind, with safe fallbacks: + * - error: prefer errorPath → fallback to errPath + * - warn: prefer warnPath → fallback to errPath + * - info: prefer infoPath → fallback to outPath + * + * @param kind - The kind of log to pick + * @param p - The worker log paths + * @returns The best source path for the given kind, or undefined if not available + */ +function pickSourcePath( + kind: Exclude, + p: WorkerLogPaths, +): string | undefined { + if (kind === 'error') return p.errorPath || p.errPath; + if (kind === 'warn') return p.warnPath || p.errPath; + // kind === 'info' + return p.infoPath || p.outPath; +} + +/** + * Combine all worker logs of the given kind into a single file. + * Writes simple headers per worker and concatenates in worker-id order. + * + * @param slotLogPaths - Map of workerId -> WorkerLogPaths + * @param kind - 'error' | 'warn' | 'info' | 'all' + * @param outDir - directory to write the combined file + * @param filename - optional override (default: combined-{kind}.log) + * @returns absolute path to the combined log file + */ +export async function exportCombinedLogs( + slotLogPaths: SlotPaths, + kind: LogKind, + outDir: string, + filename?: string, +): Promise { + mkdirSync(outDir, { recursive: true }); + + const outPath = join(outDir, filename ?? `combined-${kind}.log`); + const out = createWriteStream(outPath, { flags: 'w' }); + + for (const [id, pathsMaybe] of [...slotLogPaths.entries()].sort( + (a, b) => a[0] - b[0], + )) { + if (!pathsMaybe) continue; + const p = pathsMaybe as WorkerLogPaths; + + // Worker header + out.write(`\n==== worker ${id} ====\n`); + + if (kind === 'all') { + // Concatenate stdout, stderr, and structured (if present) + const sources: Array<{ + /** Label */ + label: string; + /** Path */ + path?: string; + }> = [ + { label: 'stdout', path: p.outPath }, + { label: 'stderr', path: p.errPath }, + { label: 'structured', path: p.structuredPath }, + ]; + + for (const { label, path } of sources) { + out.write( + `\n---- ${label}${path ? ` (${basename(path)})` : ''} ----\n`, + ); + if (!path) { + out.write('[unavailable]\n'); + continue; + } + try { + const st = statSync(path); + if (st.size === 0) { + out.write('[empty]\n'); + continue; + } + const rs = createReadStream(path, { encoding: 'utf8' }); + rs.pipe(out, { end: false }); + await once(rs, 'end'); + } catch { + out.write(`[unavailable: ${path}]\n`); + } + } + continue; + } + + // error / warn / info (single best source with fallback) + const srcPath = pickSourcePath(kind, p); + out.write(`(${srcPath ? basename(srcPath) : 'unavailable'})\n`); + + if (!srcPath) { + out.write('[unavailable]\n'); + continue; + } + + try { + const st = statSync(srcPath); + if (st.size === 0) { + out.write('[empty]\n'); + continue; + } + const rs = createReadStream(srcPath, { encoding: 'utf8' }); + rs.pipe(out, { end: false }); + await once(rs, 'end'); + } catch { + // skip unreadable files + out.write(`[unavailable: ${srcPath}]\n`); + } + } + + await new Promise((r) => { + out.end(r); + }); + return outPath; +} +/* eslint-enable no-continue */ diff --git a/src/lib/pooling/index.ts b/src/lib/pooling/index.ts index a736ea82..13ae483d 100644 --- a/src/lib/pooling/index.ts +++ b/src/lib/pooling/index.ts @@ -1,7 +1,22 @@ export * from './computePoolSize'; export * from './openTerminal'; -export * from './renderDashboard'; export * from './ensureLogFile'; export * from './spawnWorkerProcess'; export * from './attachWorkerHandlers'; export * from './showCombinedLogs'; +export * from './assignWorkToWorker'; +export * from './attachWorkerHandlers'; +export * from './installInteractiveSwitcher'; +export * from './diagnostics'; +export * from './exportCombinedLogs'; +export * from './ipc'; +export * from './logRotation'; +export * from './osc8Link'; +export * from './keymap'; +export * from './replayTail'; +export * from './workerIds'; +export * from './os'; +export * from './spawnWorkerProcess'; +export * from './workerAssignment'; +export * from './safeGetLogPathsForSlot'; +export * from './keypressExtra'; diff --git a/src/lib/pooling/installInteractiveSwitcher.ts b/src/lib/pooling/installInteractiveSwitcher.ts index 9cccad50..26a303d9 100644 --- a/src/lib/pooling/installInteractiveSwitcher.ts +++ b/src/lib/pooling/installInteractiveSwitcher.ts @@ -1,26 +1,40 @@ // interactiveSwitcher.ts import * as readline from 'node:readline'; -import { createReadStream, statSync } from 'node:fs'; import type { ChildProcess } from 'node:child_process'; import type { WorkerLogPaths } from './spawnWorkerProcess'; +import { replayFileTailToStdout } from './replayTail'; +import { mapKey } from './keymap'; +import { cycleWorkers, getWorkerIds } from './workerIds'; +import type { WhichLogs } from './showCombinedLogs'; /** - * + * Key action types for the interactive switcher */ -export type WhichLogs = Array<'out' | 'err' | 'structured'>; +export type InteractiveDashboardMode = 'dashboard' | 'attached'; + +export interface SwitcherPorts { + /** Standard input stream */ + stdin: NodeJS.ReadStream; + /** Standard output stream */ + stdout: NodeJS.WriteStream; + /** Standard error stream */ + stderr: NodeJS.WriteStream; +} /** + * Install an interactive switcher for managing worker processes. * - * @param opts + * @param opts - Options for the switcher + * @returns A cleanup function to remove the switcher */ export function installInteractiveSwitcher(opts: { - /** */ + /** Registry of live workers by id */ workers: Map; - /** */ + /** Hooks */ onAttach?: (id: number) => void; - /** */ + /** Optional detach handler */ onDetach?: () => void; - /** */ + /** Optional Ctrl+C handler for parent graceful shutdown in dashboard */ onCtrlC?: () => void; // parent graceful shutdown in dashboard /** Provide log paths so we can replay the tail on attach */ getLogPaths?: (id: number) => WorkerLogPaths | undefined; @@ -30,7 +44,9 @@ export function installInteractiveSwitcher(opts: { replayWhich?: WhichLogs; /** Print a small banner/clear screen before replaying (optional) */ onEnterAttachScreen?: (id: number) => void; -}) { + /** Optional stdio ports for testing; defaults to process stdio */ + ports?: SwitcherPorts; +}): () => void { const { workers, onAttach, @@ -40,52 +56,39 @@ export function installInteractiveSwitcher(opts: { replayBytes = 200 * 1024, replayWhich = ['out', 'err'], onEnterAttachScreen, + ports, } = opts; - const { stdin } = process; - if (!stdin.isTTY) return () => {}; + const stdin = ports?.stdin ?? process.stdin; + const stdout = ports?.stdout ?? process.stdout; + const stderr = ports?.stderr ?? process.stderr; + + if (!stdin.isTTY) { + // Not a TTY; return a no-op cleanup + return () => { + // noop + }; + } readline.emitKeypressEvents(stdin); stdin.setRawMode?.(true); - let mode: 'dashboard' | 'attached' = 'dashboard'; + let mode: InteractiveDashboardMode = 'dashboard'; let focus: number | null = null; // live mirroring handlers while attached + // eslint-disable-next-line @typescript-eslint/no-explicit-any let outHandler: ((chunk: any) => void) | null = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let errHandler: ((chunk: any) => void) | null = null; - const workerIds = () => [...workers.keys()].sort((a, b) => a - b); - const idxOf = (id: number) => workerIds().indexOf(id); - /** + * Cycle through worker IDs, wrapping around. * - * @param path - * @param maxBytes + * @param id - The current worker ID to start cycling from. + * @returns The next worker ID after cycling, or null if no workers are available. */ - function replayFileTailToStdout( - path: string, - maxBytes: number, - ): Promise { - return new Promise((resolve) => { - try { - const st = statSync(path); - const start = Math.max(0, st.size - maxBytes); - const stream = createReadStream(path, { start, encoding: 'utf8' }); - stream.on('data', (chunk) => process.stdout.write(chunk)); - stream.on('end', () => resolve()); - stream.on('error', () => resolve()); - } catch { - resolve(); - } - }); - } - - /** - * - * @param id - */ - async function replayLogs(id: number) { + async function replayLogs(id: number): Promise { if (!getLogPaths) return; const paths = getLogPaths(id); if (!paths) return; @@ -98,20 +101,18 @@ export function installInteractiveSwitcher(opts: { } if (toReplay.length) { - process.stdout.write(`\n${'-'.repeat(12)} replay ${'-'.repeat(12)}\n`); + stdout.write('\n------------ replay ------------\n'); for (const p of toReplay) { - // small header for context - process.stdout.write( + stdout.write( `\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\n`, ); - - await replayFileTailToStdout(p, replayBytes); + await replayFileTailToStdout(p, replayBytes, (s) => stdout.write(s)); } - process.stdout.write(`\n${'-'.repeat(32)}\n\n`); + stdout.write('\n--------------------------------\n\n'); } } - const attach = async (id: number) => { + const attach = async (id: number): Promise => { const w = workers.get(id); if (!w) return; @@ -129,19 +130,21 @@ export function installInteractiveSwitcher(opts: { // 2) now mirror live child output to our terminal onAttach?.(id); - outHandler = (chunk: any) => process.stdout.write(chunk); - errHandler = (chunk: any) => process.stderr.write(chunk); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + outHandler = (chunk: any) => stdout.write(chunk); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + errHandler = (chunk: any) => stderr.write(chunk); w.stdout?.on('data', outHandler); w.stderr?.on('data', errHandler); // auto-detach if child exits - const onExit = () => { + const onExit = (): void => { if (focus === id) detach(); }; w.once('exit', onExit); }; - const detach = () => { + const detach = (): void => { if (focus == null) return; const id = focus; const w = workers.get(id); @@ -156,94 +159,96 @@ export function installInteractiveSwitcher(opts: { onDetach?.(); }; - const cycle = (delta: number) => { - const ids = workerIds(); - if (ids.length === 0) return; - const current = focus == null ? ids[0] : focus; - let i = idxOf(current); - if (i === -1) i = 0; - i = (i + delta + ids.length) % ids.length; - void attach(ids[i]); - }; + const onKey = (str: string, key: readline.Key): void => { + const act = mapKey(str, key, mode); + if (!act) return; - const onKey = (str: string, key: readline.Key) => { - if (!key) return; - - // Ctrl+C behavior - if (key.ctrl && key.name === 'c') { - if (mode === 'attached' && focus != null) { - const w = workers.get(focus); - try { - w?.kill('SIGINT'); - } catch {} - // optional: auto-detach so second Ctrl+C exits parent - detach(); + // eslint-disable-next-line default-case + switch (act.type) { + case 'CTRL_C': { + if (mode === 'attached' && focus != null) { + const w = workers.get(focus); + try { + w?.kill('SIGINT'); + } catch { + // noop + } + // optional: auto-detach so second Ctrl+C exits parent + detach(); + return; + } + onCtrlC?.(); return; } - onCtrlC?.(); - return; - } - if (mode === 'dashboard') { - if (key.name && /^[0-9]$/.test(key.name)) { - const n = Number(key.name); - if (workers.has(n)) void attach(n); + case 'ATTACH': { + if (mode !== 'dashboard') return; + // eslint-disable-next-line no-void + if (workers.has(act.id)) void attach(act.id); return; } - if (key.name === 'tab' && !key.shift) { - cycle(+1); + + case 'CYCLE': { + if (mode !== 'dashboard') return; + const next = cycleWorkers(getWorkerIds(workers), focus, act.delta); + // eslint-disable-next-line no-void + if (next != null) void attach(next); return; } - if (key.name === 'tab' && key.shift) { - cycle(-1); + + case 'QUIT': { + if (mode !== 'dashboard') return; + onCtrlC?.(); return; } - if (key.name === 'q') { - onCtrlC?.(); + + case 'DETACH': { + if (mode === 'attached') detach(); return; } - return; - } - // attached mode - if (key.name === 'escape' || (key.ctrl && key.name === ']')) { - detach(); - return; - } - if (key.ctrl && key.name === 'd') { - const w = focus != null ? workers.get(focus) : null; - try { - w?.stdin?.end(); - } catch {} - return; - } + case 'CTRL_D': { + if (mode === 'attached' && focus != null) { + const w = workers.get(focus); + try { + w?.stdin?.end(); + } catch { + // noop + } + } + return; + } - // forward keystrokes to child - const w = focus != null ? workers.get(focus) : null; - if (!w || !w.stdin) return; - const seq = (key as any).sequence ?? str ?? ''; - if (seq) { - try { - w.stdin.write(seq); - } catch {} + case 'FORWARD': { + if (mode === 'attached' && focus != null) { + const w = workers.get(focus); + try { + w?.stdin?.write(act.sequence); + } catch { + // noop + } + } + } } }; // Raw bytes fallback (usually not hit because keypress handles it) - const onData = (chunk: Buffer) => { + const onData = (chunk: Buffer): void => { if (mode === 'attached' && focus != null) { const w = workers.get(focus); try { w?.stdin?.write(chunk); - } catch {} + } catch { + // noop + } } }; - const cleanup = () => { + const cleanup = (): void => { stdin.off('keypress', onKey); stdin.off('data', onData); stdin.setRawMode?.(false); - process.stdout.write('\x1b[?25h'); + stdout.write('\x1b[?25h'); }; stdin.on('keypress', onKey); diff --git a/src/lib/pooling/ipc.ts b/src/lib/pooling/ipc.ts new file mode 100644 index 00000000..7c3ccb72 --- /dev/null +++ b/src/lib/pooling/ipc.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** Result message from child → parent */ +export interface WorkerResultMessage { + /** Result type from child → parent */ + type: 'result'; + /** Payload */ + payload: { + /** Whether the task was successful */ + ok: boolean; + /** Filepath processed */ + filePath: string; + /** Error message */ + error?: string; + /** Optional receipts path, if child provided it */ + receiptFilepath?: string; + }; +} + +/** Ready sentinel from child → parent */ +export interface WorkerReadyMessage { + /** Ready message */ + type: 'ready'; +} + +/** Live progress from child → parent (for per-worker bars & throughput) */ +export interface WorkerProgressMessage { + /** Progress message */ + type: 'progress'; + /** Payload */ + payload: { + /** File processed */ + filePath: string; + /** how many just succeeded in this tick */ + successDelta: number; + /** cumulative successes for the current file */ + successTotal: number; + /** total planned records for the current file */ + fileTotal: number; + }; +} + +/** Union of all worker IPC messages */ +export type WorkerMessage = + | WorkerResultMessage + | WorkerReadyMessage + | WorkerProgressMessage; + +/** + * Check if the message is a WorkerReadyMessage. + * + * @param msg - Message to check + * @returns True if the message is a WorkerReadyMessage + */ +export function isWorkerReadyMessage(msg: unknown): msg is WorkerReadyMessage { + return !!msg && typeof msg === 'object' && (msg as any).type === 'ready'; +} + +/** + * Check if the message is a WorkerProgressMessage. + * + * @param msg - Message to check + * @returns True if the message is a WorkerProgressMessage + */ +export function isWorkerProgressMessage( + msg: unknown, +): msg is WorkerProgressMessage { + return ( + !!msg && + typeof msg === 'object' && + (msg as any).type === 'progress' && + typeof (msg as any).payload?.filePath === 'string' + ); +} + +/** + * Check if the message is a WorkerResultMessage. + * + * @param msg - Message to check + * @returns True if the message is a WorkerResultMessage + */ +export function isWorkerResultMessage( + msg: unknown, +): msg is WorkerResultMessage { + return ( + !!msg && + typeof msg === 'object' && + (msg as any).type === 'result' && + typeof (msg as any).payload?.filePath === 'string' && + typeof (msg as any).payload?.ok === 'boolean' + ); +} + +/** Shape of the optional throughput callback */ +export type ProgressInfo = { + /** ID of the worker */ + workerId: number; + /** File path */ + filePath: string; + /** Number of success updates */ + successDelta: number; + /** Total number of success */ + successTotal: number; + /** Total number of items being processed */ + fileTotal: number; +}; +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/src/lib/pooling/keymap.ts b/src/lib/pooling/keymap.ts new file mode 100644 index 00000000..6f66015b --- /dev/null +++ b/src/lib/pooling/keymap.ts @@ -0,0 +1,75 @@ +import type * as readline from 'node:readline'; + +/** + * Map a key press to an action in the interactive dashboard. + */ +export type Action = + | { + /** Indicates attaching to a session by id. */ + type: 'ATTACH'; + /** The id of the session to attach to. */ + id: number; + } + | { + /** Indicates cycling through sessions. */ + type: 'CYCLE'; + /** The direction to cycle: +1 for next, -1 for previous. */ + delta: number; + } + | { + /** Indicates detaching from the current session. */ + type: 'DETACH'; + } + | { + /** Indicates the Ctrl+C key combination was pressed. */ + type: 'CTRL_C'; + } + | { + /** Indicates the Ctrl+D key combination was pressed. */ + type: 'CTRL_D'; + } + | { + /** Indicates quitting the dashboard. */ + type: 'QUIT'; + } + | { + /** Forwards an unhandled key sequence. */ + type: 'FORWARD'; + /** The key sequence to forward. */ + sequence: string; + }; + +/** + * Map a key press to an action in the interactive dashboard. + * + * @param str - The string representation of the key press. + * @param key - The key object containing details about the key press. + * @param mode - The current mode of the dashboard, either 'dashboard' or 'attached'. + * @returns An Action object representing the mapped action, or null if no action is mapped. + */ +export function mapKey( + str: string, + key: readline.Key, + mode: 'dashboard' | 'attached', +): Action | null { + if (key.ctrl && key.name === 'c') return { type: 'CTRL_C' }; + + if (mode === 'dashboard') { + if (key.name && /^[0-9]$/.test(key.name)) { + return { type: 'ATTACH', id: Number(key.name) }; + } + if (key.name === 'tab' && !key.shift) return { type: 'CYCLE', delta: +1 }; + if (key.name === 'tab' && key.shift) return { type: 'CYCLE', delta: -1 }; + if (key.name === 'q') return { type: 'QUIT' }; + return null; + } + + // attached + if (key.name === 'escape' || (key.ctrl && key.name === ']')) { + return { type: 'DETACH' }; + } + if (key.ctrl && key.name === 'd') return { type: 'CTRL_D' }; + + const sequence = key.sequence ?? str ?? ''; + return sequence ? { type: 'FORWARD', sequence } : null; +} diff --git a/src/lib/pooling/keypressExtra.ts b/src/lib/pooling/keypressExtra.ts new file mode 100644 index 00000000..4840f899 --- /dev/null +++ b/src/lib/pooling/keypressExtra.ts @@ -0,0 +1,120 @@ +import type { ExportManager } from '../../commands/consent/upload-preferences/artifacts'; +import type { ExportStatusMap } from './logRotation'; +import { showCombinedLogs } from './showCombinedLogs'; +import type { SlotPaths } from './spawnWorkerProcess'; + +/** + * Handles keypress events for extra functionalities in the CLI. + * + * @param args - Configuration for the keypress handler. + * @returns A function that processes keypress events. + */ +export function makeOnKeypressExtra({ + slotLogPaths, + exportMgr, + exportStatus, + onRepaint, + onPause, +}: { + /** Map of worker IDs to their log paths */ + slotLogPaths: SlotPaths; + /** Export manager for handling export operations */ + exportMgr: ExportManager; + /** Map of export statuses for different kinds of logs */ + exportStatus: ExportStatusMap; + /** Callback for repainting the UI */ + onRepaint: () => void; + /** Callback to pause the UI */ + onPause: (paused: boolean) => void; +}): (buf: Buffer) => void { + const noteExport = (slot: keyof ExportStatusMap, p: string): void => { + const now = Date.now(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const current: any = exportStatus[slot] || { path: p }; + // eslint-disable-next-line no-param-reassign + exportStatus[slot] = { + path: p || current.path, + savedAt: now, + exported: true, + }; + onRepaint(); + }; + + const view = ( + sources: Array<'out' | 'err' | 'structured' | 'warn' | 'info'>, + level: 'error' | 'warn' | 'all', + ): void => { + onPause(true); + showCombinedLogs(slotLogPaths, sources, level); + }; + + return (buf: Buffer): void => { + const s = buf.toString('utf8'); + + // viewers (lowercase) + if (s === 'e') { + view(['err'], 'error'); + return; + } + if (s === 'w') { + view(['warn', 'err'], 'warn'); + return; + } + if (s === 'i') { + view(['info'], 'all'); + return; + } + if (s === 'l') { + view(['out', 'err', 'structured'], 'all'); + return; + } + + // exports (uppercase) + if (s === 'E') { + try { + const p = exportMgr.exportCombinedLogs(slotLogPaths, 'error'); + process.stdout.write(`\nWrote combined error logs to: ${p}\n`); + noteExport('error', p); + } catch { + process.stdout.write('\nFailed to write combined error logs\n'); + } + return; + } + if (s === 'W') { + try { + const p = exportMgr.exportCombinedLogs(slotLogPaths, 'warn'); + process.stdout.write(`\nWrote combined warn logs to: ${p}\n`); + noteExport('warn', p); + } catch { + process.stdout.write('\nFailed to write combined warn logs\n'); + } + return; + } + if (s === 'I') { + try { + const p = exportMgr.exportCombinedLogs(slotLogPaths, 'info'); + process.stdout.write(`\nWrote combined info logs to: ${p}\n`); + noteExport('info', p); + } catch { + process.stdout.write('\nFailed to write combined info logs\n'); + } + return; + } + if (s === 'A') { + try { + const p = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); + process.stdout.write(`\nWrote combined ALL logs to: ${p}\n`); + noteExport('all', p); + } catch { + process.stdout.write('\nFailed to write combined ALL logs\n'); + } + return; + } + + // back to dashboard + if (s === '\x1b' || s === '\x1d') { + onPause(false); + onRepaint(); + } + }; +} diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts index aad9b3aa..3847d971 100644 --- a/src/lib/pooling/logRotation.ts +++ b/src/lib/pooling/logRotation.ts @@ -1,5 +1,11 @@ // logRotation.ts -import { readdirSync, writeFileSync, existsSync, unlinkSync } from 'node:fs'; +import { + readdirSync, + writeFileSync, + existsSync, + unlinkSync, + mkdirSync, +} from 'node:fs'; import { join } from 'node:path'; import colors from 'colors'; @@ -12,10 +18,7 @@ import colors from 'colors'; * @param dir - Directory to reset logs in * @param mode - 'truncate' or 'delete' */ -export function resetWorkerLogs( - dir: string, - mode: 'truncate' | 'delete', -): void { +function resetWorkerLogs(dir: string, mode: 'truncate' | 'delete'): void { const patterns = [ /worker-\d+\.log$/, /worker-\d+\.out\.log$/, @@ -117,3 +120,150 @@ export function makeLineSplitter( } }; } +/** + * Checks if a log line contains an error indicator. + * + * @param t - The log line to check + * @returns True if the line contains an error keyword, false otherwise + */ +export function isLogError(t: string): boolean { + return /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); +} + +/** + * Checks if a log line contains a warning indicator. + * + * @param t - The log line to check + * @returns True if the line contains a warning keyword, false otherwise + */ +export function isLogWarn(t: string): boolean { + return /\b(WARN|WARNING)\b/i.test(t); +} + +/** + * Determines if a log line is a new header (error, warning, worker tag, or ISO timestamp). + * + * @param t - The log line to check + * @returns True if the line is a new header, false otherwise + */ +export function isLogNewHeader(t: string): boolean { + return ( + isLogError(t) || + isLogWarn(t) || + /^\s*\[w\d+\]/.test(t) || + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(t) + ); +} + +// eslint-disable-next-line no-control-regex +const stripAnsi = (s: string): string => s.replace(/\x1B\[[0-9;]*m/g, ''); + +/** + * Extracts blocks of text from a larger body of text. + * + * @param text - The text to extract blocks from + * @param starts - A function that determines if a line starts a new block + * @returns An array of extracted blocks + */ +export function extractBlocks( + text: string, + starts: (cleanLine: string) => boolean, +): string[] { + if (!text) return []; + const out: string[] = []; + const lines = text.split('\n'); + let buf: string[] = []; + let inBlock = false; + + const flush = (): void => { + if (buf.length) out.push(buf.join('\n')); + buf = []; + inBlock = false; + }; + + for (const raw of lines) { + const clean = stripAnsi(raw || ''); + const headery = isLogNewHeader(clean); + if (!inBlock) { + if (starts(clean)) { + inBlock = true; + buf.push(raw); + } + // eslint-disable-next-line no-continue + continue; + } + if (!raw || headery) { + flush(); + if (starts(clean)) { + inBlock = true; + buf.push(raw); + } + } else { + buf.push(raw); + } + } + flush(); + return out.filter(Boolean); +} + +/** + * The kind of export artifact to retrieve the path for. + */ +export type LogExportKind = 'error' | 'warn' | 'info' | 'all'; + +/** + * Ensure log directory exists + * + * @param rootDir - Root directory + * @returns log dir + */ +export function initLogDir(rootDir: string): string { + const logDir = join(rootDir, 'logs'); + mkdirSync(logDir, { recursive: true }); + + const RESET_MODE = + (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate'; + resetWorkerLogs(logDir, RESET_MODE); + + return logDir; +} + +export interface ExportArtifactResult { + /** Whether the artifact was opened successfully */ + ok?: boolean; + /** The absolute path to the export artifact */ + path: string; + /** Time saved */ + savedAt?: number; + /** If exported */ + exported?: boolean; +} + +export interface ExportStatusMap { + /** The absolute paths to the error log artifacts */ + error?: ExportArtifactResult; + /** The absolute paths to the warn log artifacts */ + warn?: ExportArtifactResult; + /** The absolute paths to the info log artifacts */ + info?: ExportArtifactResult; + /** The absolute paths to all log artifacts */ + all?: ExportArtifactResult; + /** The absolute paths to the failures CSV artifacts */ + failuresCsv?: ExportArtifactResult; +} + +/** + * Return export statuses + * + * @param logDir - Log directory + * @returns Export map + */ +export function buildExportStatus(logDir: string): ExportStatusMap { + return { + error: { path: join(logDir, 'combined-errors.log') }, + warn: { path: join(logDir, 'combined-warns.log') }, + info: { path: join(logDir, 'combined-info.log') }, + all: { path: join(logDir, 'combined-all.log') }, + failuresCsv: { path: join(logDir, 'failing-updates.csv') }, + }; +} diff --git a/src/lib/pooling/os.ts b/src/lib/pooling/os.ts new file mode 100644 index 00000000..bb53e9fd --- /dev/null +++ b/src/lib/pooling/os.ts @@ -0,0 +1,90 @@ +import { spawn } from 'node:child_process'; + +import { dirname } from 'node:path'; + +/** + * Spawn a command in a detached process. + * This is a best-effort attempt to run the command in the background. + * + * @param cmd - The command to run + * @param args - The arguments for the command + * @returns True if the command was spawned successfully, false otherwise + */ +export function spawnDetached(cmd: string, args: string[]): boolean { + try { + const child = spawn(cmd, args, { stdio: 'ignore', detached: true }); + child.unref(); + return true; + } catch { + return false; + } +} + +/** + * Open a path in the default application for that file type. + * + * @param p - The path to open + * @returns True if the path was opened successfully, false otherwise + */ +export function openPath(p: string): boolean { + if (!p || p.startsWith('(')) return false; + if (process.platform === 'win32') { + return spawnDetached('cmd', ['/c', 'start', '', p]); + } + return spawnDetached('xdg-open', [p]); +} + +/** + * Reveal a file in the file manager. + * + * @param p - The path to the file to reveal + * @returns True if the file manager was opened successfully, false otherwise + */ +export function revealInFileManager(p: string): boolean { + if (!p || p.startsWith('(')) return false; + if (process.platform === 'darwin') return spawnDetached('open', ['-R', p]); + if (process.platform === 'win32') { + return spawnDetached('explorer.exe', ['/select,', p]); + } + return spawnDetached('xdg-open', [dirname(p)]); // Linux best-effort +} + +/** + * Copy text to the clipboard. + * This is a best-effort attempt to copy text to the clipboard. + * + * @param text - The text to copy to the clipboard + * @returns True if the text was copied successfully, false otherwise + */ +export function copyToClipboard(text: string): boolean { + if (!text || text.startsWith('(')) return false; + try { + if (process.platform === 'darwin') { + const p = spawn('pbcopy'); + p.stdin?.end(text); + return true; + } + if (process.platform === 'win32') { + const p = spawn('clip'); + p.stdin?.end(text.replace(/\n/g, '\r\n')); + return true; + } + try { + const p = spawn('xclip', ['-selection', 'clipboard']); + p.stdin?.end(text); + return true; + } catch { + // Fallback to xsel if xclip is not available + } + try { + const p2 = spawn('xsel', ['--clipboard', '--input']); + p2.stdin?.end(text); + return true; + } catch { + // If both xclip and xsel fail, we can't copy to clipboard + } + } catch { + // If spawning the process fails, we can't copy to clipboard + } + return false; +} diff --git a/src/lib/pooling/osc8Link.ts b/src/lib/pooling/osc8Link.ts new file mode 100644 index 00000000..887bef64 --- /dev/null +++ b/src/lib/pooling/osc8Link.ts @@ -0,0 +1,21 @@ +import { pathToFileURL } from 'node:url'; + +/** + * Generates an OSC 8 hyperlink for terminal output. + * + * @param absPath - Absolute path to the file + * @param label - Optional label for the link + * @returns A string formatted as an OSC 8 hyperlink + */ +export function osc8Link(absPath: string, label?: string): string { + if (!absPath || absPath.startsWith('(')) return label ?? absPath; // Skip placeholders + try { + const { href } = pathToFileURL(absPath); // file:///… URL + const OSC = '\u001B]8;;'; + const BEL = '\u0007'; + const text = label ?? absPath; // may contain SGR color codes + return `${OSC}${href}${BEL}${text}${OSC}${BEL}`; + } catch { + return label ?? absPath; + } +} diff --git a/src/lib/pooling/renderDashboard.ts b/src/lib/pooling/renderDashboard.ts deleted file mode 100644 index 8d32e91e..00000000 --- a/src/lib/pooling/renderDashboard.ts +++ /dev/null @@ -1,857 +0,0 @@ -// renderDashboard.ts — rewritten to make export artifacts easy to OPEN / REVEAL / COPY -// - Keeps the existing dashboard renderer and export helpers -// - Adds cross‑platform helpers: openExport, revealExport, copyExportPath -// - Prints OSC‑8 hyperlinks *and* plain absolute paths *and* file:// URLs -// - Writes a sidecar index file with the latest artifact paths for quick copy - -import { basename, dirname, join, resolve } from 'node:path'; -import colors from 'colors'; -import * as readline from 'node:readline'; -import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; -import type { WorkerState } from './assignWorkToWorker'; -import type { getWorkerLogPaths } from './spawnWorkerProcess'; -import { pathToFileURL } from 'node:url'; -import { spawn } from 'node:child_process'; - -/** Upload-mode totals (final-ish counters derived from receipts) */ -export type UploadModeTotals = { - /** */ - mode: 'upload'; - /** */ - success: number; - /** */ - skipped: number; - /** */ - error: number; - /** */ - errors: Record; -}; - -/** Check-mode totals (dry-run counters) */ -export type CheckModeTotals = { - /** */ - mode: 'check'; - /** */ - pendingConflicts: number; - /** */ - pendingSafe: number; - /** */ - totalPending: number; - /** */ - skipped: number; -}; - -/** Total type for both upload and check modes. */ -export type AnyTotals = UploadModeTotals | CheckModeTotals; - -/** Export kinds we support in the UI */ -export type LogExportKind = 'error' | 'warn' | 'info' | 'all'; -/** - * - */ -export type ExportKindWithCsv = LogExportKind | 'failures-csv'; - -/** Status for a single export artifact */ -export interface ExportArtifactStatus { - /** Absolute path of the last written artifact file */ - path: string; - /** Unix ms when that file was last written */ - savedAt?: number; - /** True once we’ve exported at least once during this parent run */ - exported?: boolean; -} - -/** Map of all export artifacts we show */ -export interface ExportStatusMap { - /** */ - error?: ExportArtifactStatus; - /** */ - warn?: ExportArtifactStatus; - /** */ - info?: ExportArtifactStatus; - /** */ - all?: ExportArtifactStatus; - /** */ - failuresCsv?: ExportArtifactStatus; // for Shift+F CSV of failing updates -} - -/** Render options for the dashboard */ -export interface RenderDashboardInput { - /** */ - poolSize: number; // Number of live workers in the pool - /** */ - cpuCount: number; // CPU count hint shown to user - - /** */ - filesTotal: number; // Total files discovered at start - /** */ - filesCompleted: number; // Completed files count - /** */ - filesFailed: number; // Failed files count - - /** */ - workerState: Map; // Live map of worker state - /** */ - totals?: AnyTotals; // Mode-specific aggregates - /** */ - final?: boolean; // Final frame? If true, show the cursor and freeze the output - - /** */ - throughput?: { - /** */ - successSoFar: number; - /** */ - r10s: number; - /** */ - r60s: number; - }; - - /** Directory where export artifacts (combined logs / CSV) are written. */ - exportsDir?: string; - - /** Current (latest) artifact status per kind. */ - exportStatus?: ExportStatusMap; -} - -let lastFrame = ''; -let lastIndexFileContents = ''; - -/* ------------------------------------------------------------ - * Small helpers - * ------------------------------------------------------------ */ - -const stripAnsi = (s: string) => s.replace(/\x1B\[[0-9;]*m/g, ''); -const isError = (t: string) => - /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); -const isWarn = (t: string) => /\b(WARN|WARNING)\b/i.test(t); - -const fmtNum = (n: number): string => n.toLocaleString(); -const fmtTime = (ts?: number): string => - ts ? new Date(ts).toLocaleTimeString() : '—'; - -/** - * - * @param t - */ -function isNewHeader(t: string) { - return ( - isError(t) || - isWarn(t) || - /^\s*\[w\d+\]/.test(t) || - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(t) - ); -} - -/** - * - * @param absPath - * @param label - */ -function osc8Link(absPath: string, label?: string): string { - if (!absPath || absPath.startsWith('(')) return label ?? absPath; // Skip placeholders - try { - const { href } = pathToFileURL(absPath); // file:///… URL - const OSC = '\u001B]8;;'; - const BEL = '\u0007'; - const text = label ?? absPath; // may contain SGR color codes - return `${OSC}${href}${BEL}${text}${OSC}${BEL}`; - } catch { - return label ?? absPath; - } -} - -/** - * - * @param text - * @param starts - */ -function extractBlocks( - text: string, - starts: (cleanLine: string) => boolean, -): string[] { - if (!text) return []; - const out: string[] = []; - const lines = text.split('\n'); - - let buf: string[] = []; - let inBlock = false; - - const flush = () => { - if (buf.length) out.push(buf.join('\n')); - buf = []; - inBlock = false; - }; - - for (const raw of lines) { - const clean = stripAnsi(raw || ''); - const headery = isNewHeader(clean); - - if (!inBlock) { - if (starts(clean)) { - inBlock = true; - buf.push(raw); - } - continue; - } - - if (!raw || headery) { - flush(); - if (starts(clean)) { - inBlock = true; - buf.push(raw); - } - } else { - buf.push(raw); - } - } - flush(); - return out.filter(Boolean); -} - -/** - * Compute an absolute path for a would‑be artifact (even before it exists). - * - * @param kind - * @param exportsDir - * @param status - */ -function artifactAbsPath( - kind: ExportKindWithCsv, - exportsDir?: string, - status?: ExportArtifactStatus, -): string { - const fallbackName = - kind === 'error' - ? 'combined-errors.log' - : kind === 'warn' - ? 'combined-warns.log' - : kind === 'info' - ? 'combined-info.log' - : kind === 'all' - ? 'combined-all.log' - : 'failing-updates.csv'; - - const rawPath = - status?.path || - (exportsDir ? join(exportsDir, fallbackName) : '(set exportsDir)'); - return rawPath.startsWith('(') ? rawPath : resolve(rawPath); -} - -/** - * Create (if needed) an exports index file summarizing artifact paths. - * - * @param exportsDir - * @param exportStatus - */ -function writeExportsIndex( - exportsDir?: string, - exportStatus?: ExportStatusMap, -): string | undefined { - if (!exportsDir) return undefined; - const lines: string[] = ['# Export artifacts — latest paths', '']; - - const kinds: Array< - [ExportKindWithCsv, ExportArtifactStatus | undefined, string] - > = [ - ['error', exportStatus?.error, 'Errors log'], - ['warn', exportStatus?.warn, 'Warnings log'], - ['info', exportStatus?.info, 'Info log'], - ['all', exportStatus?.all, 'All logs'], - ['failures-csv', exportStatus?.failuresCsv, 'Failing updates (CSV)'], - ]; - - for (const [k, st, label] of kinds) { - const abs = artifactAbsPath(k, exportsDir, st); - const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; - lines.push(`${label}:`); - lines.push(` path: ${abs}`); - lines.push(` url: ${url}`); - lines.push(''); - } - - const content = lines.join('\n'); - if (content === lastIndexFileContents) return join(exportsDir, 'exports.index.txt'); - - mkdirSync(exportsDir, { recursive: true }); - const out = join(exportsDir, 'exports.index.txt'); - writeFileSync(out, `${content}\n`, 'utf8'); - lastIndexFileContents = content; - return out; -} - -/* ------------------------------------------------------------ - * Cross‑platform OPEN / REVEAL / COPY helpers - * ------------------------------------------------------------ */ - -/** - * - * @param cmd - * @param args - */ -function spawnDetached(cmd: string, args: string[]) { - try { - const child = spawn(cmd, args, { stdio: 'ignore', detached: true }); - child.unref(); - return true; - } catch { - return false; - } -} - -/** - * - * @param p - */ -async function openPath(p: string): Promise { - if (!p || p.startsWith('(')) return false; - if (process.platform === 'darwin') return spawnDetached('open', [p]); - if (process.platform === 'win32') return spawnDetached('cmd', ['/c', 'start', '', p]); - return spawnDetached('xdg-open', [p]); -} - -/** - * - * @param p - */ -async function revealInFileManager(p: string): Promise { - if (!p || p.startsWith('(')) return false; - if (process.platform === 'darwin') return spawnDetached('open', ['-R', p]); - if (process.platform === 'win32') return spawnDetached('explorer.exe', ['/select,', p]); - // Linux: best effort — open folder - return spawnDetached('xdg-open', [dirname(p)]); -} - -/** - * - * @param text - */ -async function copyToClipboard(text: string): Promise { - if (!text || text.startsWith('(')) return false; - try { - if (process.platform === 'darwin') { - const p = spawn('pbcopy'); - p.stdin?.write(text); - p.stdin?.end(); - return true; - } - if (process.platform === 'win32') { - const p = spawn('clip'); - p.stdin?.write(text.replace(/\n/g, '\r\n')); - p.stdin?.end(); - return true; - } - // Linux: try xclip, then xsel - try { - const p = spawn('xclip', ['-selection', 'clipboard']); - p.stdin?.write(text); - p.stdin?.end(); - return true; - } catch {} - try { - const p2 = spawn('xsel', ['--clipboard', '--input']); - p2.stdin?.write(text); - p2.stdin?.end(); - return true; - } catch {} - } catch {} - return false; -} - -/** - * Action helpers you can call from your key handlers in impl.ts - * - * @param kind - * @param exportsDir - * @param exportStatus - */ -export async function openExport( - kind: ExportKindWithCsv, - exportsDir?: string, - exportStatus?: ExportStatusMap, -): Promise<{ - /** */ ok: boolean /** */; - /** */ - path: string; -}> { - const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); - return { ok: await openPath(path), path }; -} - -/** - * - * @param kind - * @param exportsDir - * @param exportStatus - */ -export async function revealExport( - kind: ExportKindWithCsv, - exportsDir?: string, - exportStatus?: ExportStatusMap, -): Promise<{ - /** */ ok: boolean /** */; - /** */ - path: string; -}> { - const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); - return { ok: await revealInFileManager(path), path }; -} - -/** - * - * @param kind - * @param exportsDir - * @param exportStatus - */ -export async function copyExportPath( - kind: ExportKindWithCsv, - exportsDir?: string, - exportStatus?: ExportStatusMap, -): Promise<{ - /** */ ok: boolean /** */; - /** */ - path: string; -}> { - const path = artifactAbsPath(kind, exportsDir, (exportStatus as any)?.[kind]); - return { ok: await copyToClipboard(path), path }; -} - -/* ------------------------------------------------------------ - * Main renderer - * ------------------------------------------------------------ */ - -/** - * - * @param root0 - */ -export function renderDashboard({ - poolSize, - cpuCount, - filesTotal, - filesCompleted, - filesFailed, - workerState, - totals, - final, - throughput, - exportsDir, - exportStatus = {}, -}: RenderDashboardInput): void { - const redIf = (n: number, s: string): string => (n > 0 ? colors.red(s) : s); - - const inProgress = [...workerState.values()].filter((s) => s.busy).length; - - // Global file-level progress (files) - const completedFiles = filesCompleted + filesFailed; - const pct = - filesTotal === 0 - ? 100 - : Math.floor((completedFiles / Math.max(1, filesTotal)) * 100); - const barWidth = 40; - const filled = Math.floor((pct / 100) * barWidth); - const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); - - // Estimate TOTAL JOBS (records) - const jobsFromReceipts = - totals && totals.mode === 'upload' - ? (totals.success || 0) + (totals.skipped || 0) + (totals.error || 0) - : undefined; - - const inflightJobsKnown = [...workerState.values()].reduce((sum, s) => { - const t = s.progress?.total ?? 0; - return sum + (t > 0 && s.busy ? t : 0); - }, 0); - - const avgJobsPerCompletedFile = - jobsFromReceipts !== undefined && completedFiles > 0 - ? jobsFromReceipts / completedFiles - : undefined; - - const remainingFiles = Math.max(filesTotal - completedFiles - inProgress, 0); - - let estTotalJobsText = colors.dim('Est. total jobs: —'); - if (avgJobsPerCompletedFile !== undefined) { - const est = - (jobsFromReceipts ?? 0) + - inflightJobsKnown + - remainingFiles * avgJobsPerCompletedFile; - estTotalJobsText = colors.dim( - `Est. total jobs: ${fmtNum(Math.round(est))}`, - ); - } else if (inProgress > 0) { - const avgInFlight = - inflightJobsKnown > 0 ? inflightJobsKnown / inProgress : 0; - if (avgInFlight > 0) { - const est = inflightJobsKnown + remainingFiles * avgInFlight; - estTotalJobsText = colors.dim( - `Est. total jobs: ${fmtNum(Math.round(est))}`, - ); - } - } - - // Estimate expected completion time (ETA) - let etaText = ''; - if (throughput && estTotalJobsText && avgJobsPerCompletedFile !== undefined) { - const est = - (jobsFromReceipts ?? 0) + - inflightJobsKnown + - remainingFiles * avgJobsPerCompletedFile; - const uploaded = throughput.successSoFar; - const remainingJobs = Math.max(est - uploaded, 0); - const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; - if (ratePerSec > 0 && remainingJobs > 0) { - const secondsLeft = Math.round(remainingJobs / ratePerSec); - const eta = new Date(Date.now() + secondsLeft * 1000); - const hours = Math.floor(secondsLeft / 3600); - const minutes = Math.floor((secondsLeft % 3600) / 60); - const timeLeft = - hours > 0 - ? `${hours}h ${minutes}m` - : minutes > 0 - ? `${minutes}m` - : `${secondsLeft}s`; - etaText = colors.magenta( - `Expected completion: ${eta.toLocaleTimeString()} (${timeLeft} left)`, - ); - } - } - - // Header - const header = [ - `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( - `(CPU avail: ${cpuCount})`, - )}`, - `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim( - 'Completed', - )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${redIf( - filesFailed, - fmtNum(filesFailed), - )} ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, - `[${bar}] ${pct}% ${estTotalJobsText} ${etaText}`, - ]; - - if (exportsDir) header.push(colors.dim(`Exports dir: ${exportsDir}`)); - - // Totals block - let totalsBlock = ''; - if (totals) { - if (totals.mode === 'upload') { - const t = totals as UploadModeTotals; - const errorsList = Object.entries(t.errors || {}).map( - ([msg, count]) => - ` ${colors.red(`Count[${fmtNum(count)}]`)} ${colors.red(msg)}`, - ); - totalsBlock = [ - errorsList.length - ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` - : '', - `${colors.bold('Receipts totals')} — Success: ${fmtNum( - t.success, - )} Skipped: ${fmtNum(t.skipped)} Error: ${redIf( - t.error, - fmtNum(t.error), - )}`, - ] - .filter(Boolean) - .join('\n\n'); - } else { - const t = totals as CheckModeTotals; - totalsBlock = - `${colors.bold('Receipts totals')} — Pending: ${fmtNum( - t.totalPending, - )} ` + - `PendingConflicts: ${fmtNum(t.pendingConflicts)} PendingSafe: ${fmtNum( - t.pendingSafe, - )} ` + - `Skipped: ${fmtNum(t.skipped)}`; - } - } - - // Throughput (records/hour) - const tp = throughput - ? (() => { - const perHour10 = Math.round(throughput.r10s * 3600); - const perHour60 = Math.round(throughput.r60s * 3600); - return `Throughput: ${fmtNum(perHour10)}/hr (1h: ${fmtNum( - perHour60, - )}/hr) Newly uploaded this run: ${fmtNum(throughput.successSoFar)}`; - })() - : ''; - - // Per-worker lines - const miniWidth = 18; - const workerLines = [...workerState.entries()].map(([id, s]) => { - const badge = - s.lastLevel === 'error' - ? colors.red('ERROR ') - : s.lastLevel === 'warn' - ? colors.yellow('WARN ') - : s.busy - ? colors.green('WORKING') - : colors.dim('IDLE '); - - const fname = s.file ? basename(s.file) : '-'; - const elapsed = s.startedAt - ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` - : '-'; - - const processed = s.progress?.processed ?? 0; - const total = s.progress?.total ?? 0; - const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; - const ff = Math.floor((pctw / 100) * miniWidth); - const mini = - total > 0 - ? '█'.repeat(ff) + '░'.repeat(miniWidth - ff) - : ' '.repeat(miniWidth); - const miniTxt = - total > 0 - ? `${fmtNum(processed)}/${fmtNum(total)} (${pctw}%)` - : colors.dim('—'); - - return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; - }); - - // Base hotkeys (viewer keys rendered like before; export keys moved below) - const maxDigit = Math.min(poolSize - 1, 9); - const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; - const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; - const hotkeysLine = final - ? colors.dim( - 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', - ) - : colors.dim( - `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit`, - ); - - // Pretty export block — one line per export key with path + indicator - const makeExportLine = ( - key: 'E' | 'W' | 'I' | 'A' | 'F', - label: string, - status?: ExportArtifactStatus, - fallbackName?: string, - ): string => { - const exported = !!status?.exported; - - const rawPath = - status?.path || - (exportsDir - ? join( - exportsDir, - fallbackName ?? `${label.toLowerCase().replace(/\s+/g, '-')}.log`, - ) - : '(set exportsDir)'); - - const abs = rawPath.startsWith('(') ? rawPath : resolve(rawPath); - const time = fmtTime(status?.savedAt); - const dot = exported ? colors.green('●') : colors.dim('○'); - const hotkey = colors.bold(`${key}=export-${label}`); - - const openText = exported ? colors.green('open') : colors.dim('open'); - const openLink = osc8Link(abs, openText); - const plainPath = exported ? colors.green(abs) : colors.dim(abs); - const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; - - return ( - `${dot} ${hotkey}: ${openLink} ${plainPath} ${colors.dim( - `(last saved: ${time})`, - )}\n` + ` ${colors.dim('url:')} ${url}` - ); - }; - - const exportBlock = [ - colors.dim('Exports (Cmd/Ctrl‑click “open” or copy the plain path):'), - ` ${makeExportLine( - 'E', - 'errors', - exportStatus.error, - 'combined-errors.log', - )}`, - ` ${makeExportLine( - 'W', - 'warns', - exportStatus.warn, - 'combined-warns.log', - )}`, - ` ${makeExportLine('I', 'info', exportStatus.info, 'combined-info.log')}`, - ` ${makeExportLine('A', 'all', exportStatus.all, 'combined-all.log')}`, - ` ${makeExportLine( - 'F', - 'failures-csv', - exportStatus.failuresCsv, - 'failing-updates.csv', - )}`, - colors.dim(' (Also written to exports.index.txt for easy copying.)'), - ].join('\n'); - - // Optionally write/update the sidecar index file each frame (cheap I/O, guarded by memo) - writeExportsIndex(exportsDir, exportStatus); - - const frame = [ - ...header, - tp ? colors.cyan(tp) : '', - totalsBlock ? `\n${totalsBlock}` : '', - '', - ...workerLines, - '', - hotkeysLine, - exportBlock ? `\n${exportBlock}` : '', - ] - .filter(Boolean) - .join('\n'); - - if (!final && frame === lastFrame) return; - lastFrame = frame; - - if (!final) { - process.stdout.write('\x1b[?25l'); - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); - } else { - process.stdout.write('\x1b[?25h'); - } - process.stdout.write(`${frame}\n`); -} - -/* ------------------------------------------------------------ - * Export helpers (used by key handlers in impl.ts) - * ------------------------------------------------------------ */ - -/** - * - * @param slotLogPaths - * @param kind - * @param outDir - */ -export async function exportCombinedLogs( - slotLogPaths: Map | undefined>, - kind: LogExportKind, - outDir: string, -): Promise { - mkdirSync(outDir, { recursive: true }); - - const ts = new Date().toISOString().replace(/[:.]/g, '-'); - const outPath = - kind === 'error' - ? join(outDir, `combined-errors-${ts}.log`) - : kind === 'warn' - ? join(outDir, `combined-warns-${ts}.log`) - : kind === 'info' - ? join(outDir, `combined-info-${ts}.log`) - : join(outDir, `combined-all-${ts}.log`); - - const lines: string[] = []; - - for (const [, paths] of slotLogPaths) { - if (!paths) continue; - - const readSafe = (p?: string) => { - try { - return p ? readFileSync(p, 'utf8') : ''; - } catch { - return ''; - } - }; - - if (kind === 'all') { - const blobs = [ - paths.outPath, - paths.errPath, - (paths as any).structuredPath, - ] - .filter(Boolean) - .map((p) => readSafe(p)); - blobs.forEach((text) => { - if (!text) return; - lines.push(...text.split('\n').filter(Boolean)); - }); - continue; - } - - if (kind === 'info') { - const infoPath = (paths as any).infoPath as string | undefined; - const text = readSafe(infoPath) || readSafe(paths.outPath); - if (!text) continue; - lines.push(...text.split('\n').filter(Boolean)); - continue; - } - - if (kind === 'warn') { - const warnPath = (paths as any).warnPath as string | undefined; - let text = readSafe(warnPath); - if (!text) { - const stderr = readSafe(paths.errPath); - if (stderr) { - const blocks = extractBlocks( - stderr, - (cl) => isWarn(cl) && !isError(cl), - ); - if (blocks.length) text = blocks.join('\n\n'); - } - } - if (!text) continue; - lines.push(...text.split('\n').filter(Boolean)); - continue; - } - - // kind === 'error' - const errorPath = (paths as any).errorPath as string | undefined; - let text = readSafe(errorPath); - if (!text) { - const stderr = readSafe(paths.errPath); - if (stderr) { - const blocks = extractBlocks(stderr, (cl) => isError(cl)); - if (blocks.length) text = blocks.join('\n\n'); - } - } - if (!text) continue; - lines.push(...text.split('\n').filter(Boolean)); - } - - // naive time-sort - lines.sort((a, b) => { - const ta = a.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; - const tb = b.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] ?? ''; - return ta.localeCompare(tb); - }); - - writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); - return outPath; -} - -/** - * - * @param rows - * @param outPath - */ -export async function writeFailingUpdatesCsv( - rows: Array>, - outPath: string, -): Promise { - mkdirSync(join(outPath, '..'), { recursive: true }); - - const headers = Array.from( - rows.reduce>((acc, row) => { - Object.keys(row || {}).forEach((k) => acc.add(k)); - return acc; - }, new Set()), - ); - - const esc = (v: unknown): string => { - if (v == null) return ''; - const s = - typeof v === 'string' - ? v - : typeof v === 'object' - ? JSON.stringify(v) - : String(v); - if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`; - return s; - }; - - const lines = [ - headers.join(','), - ...rows.map((r) => headers.map((h) => esc((r as any)[h])).join(',')), - ]; - - writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); - return outPath; -} diff --git a/src/lib/pooling/replayTail.ts b/src/lib/pooling/replayTail.ts new file mode 100644 index 00000000..50da3d7b --- /dev/null +++ b/src/lib/pooling/replayTail.ts @@ -0,0 +1,27 @@ +import { createReadStream, statSync } from 'node:fs'; + +/** + * Replay the tail of a file to stdout. + * + * @param path - The absolute path to the file to read. + * @param maxBytes - The maximum number of bytes to read from the end of the file. + * @param write - A function to write the output to stdout. + */ +export async function replayFileTailToStdout( + path: string, + maxBytes: number, + write: (s: string) => void, +): Promise { + await new Promise((resolve) => { + try { + const st = statSync(path); + const start = Math.max(0, st.size - maxBytes); + const stream = createReadStream(path, { start, encoding: 'utf8' }); + stream.on('data', (chunk) => write(chunk as string)); + stream.on('end', resolve); + stream.on('error', resolve); + } catch { + resolve(undefined); + } + }); +} diff --git a/src/lib/pooling/safeGetLogPathsForSlot.ts b/src/lib/pooling/safeGetLogPathsForSlot.ts new file mode 100644 index 00000000..3074ae75 --- /dev/null +++ b/src/lib/pooling/safeGetLogPathsForSlot.ts @@ -0,0 +1,32 @@ +// pool/safeGetLogPathsForSlot.ts +import type { ChildProcess } from 'node:child_process'; +import { + getWorkerLogPaths, + isIpcOpen, + type WorkerLogPaths, +} from './spawnWorkerProcess'; + +/** + * Safely retrieve log paths for a worker slot. + * + * @param id - The worker slot ID + * @param workers - Map of worker IDs to their ChildProcess instances + * @param slotLogPaths - Map of worker IDs to their log paths + * @returns The log paths for the worker slot, or undefined if not available + */ +export function safeGetLogPathsForSlot( + id: number, + workers: Map, + slotLogPaths: Map, +): WorkerLogPaths | undefined { + const live = workers.get(id); + if (isIpcOpen(live)) { + try { + const p = getWorkerLogPaths(live!); + if (p !== undefined && p !== null) return p; + } catch { + /* fall back */ + } + } + return slotLogPaths.get(id); +} diff --git a/src/lib/pooling/showCombinedLogs.ts b/src/lib/pooling/showCombinedLogs.ts index 1f54a36f..c3e9e50e 100644 --- a/src/lib/pooling/showCombinedLogs.ts +++ b/src/lib/pooling/showCombinedLogs.ts @@ -1,11 +1,17 @@ /* eslint-disable no-continue, no-control-regex */ import { readFileSync } from 'node:fs'; -import type { getWorkerLogPaths } from './spawnWorkerProcess'; +import type { WorkerLogPaths } from './spawnWorkerProcess'; /** - * + * Log locations */ -export type WhichLogs = Array<'out' | 'err' | 'structured' | 'warn' | 'info'>; +export type LogLocation = 'out' | 'err' | 'structured' | 'warn' | 'info'; + +/** + * Which logs to show in the combined output. + * Can include 'out' (stdout), 'err' (stderr), 'structured' ( + */ +export type WhichLogs = Array; /** * Show combined logs from all worker processes. @@ -15,15 +21,15 @@ export type WhichLogs = Array<'out' | 'err' | 'structured' | 'warn' | 'info'>; * @param filterLevel - 'error', 'warn', or 'all' to filter log levels. */ export function showCombinedLogs( - slotLogPaths: Map | undefined>, + slotLogPaths: Map, whichList: WhichLogs, filterLevel: 'error' | 'warn' | 'all', ): void { process.stdout.write('\x1b[2J\x1b[H'); - const isError = (t: string) => + const isError = (t: string): boolean => /\b(ERROR|uncaughtException|unhandledRejection)\b/i.test(t); - const isWarnTag = (t: string) => /\b(WARN|WARNING)\b/i.test(t); + const isWarnTag = (t: string): boolean => /\b(WARN|WARNING)\b/i.test(t); const lines: string[] = []; @@ -31,16 +37,27 @@ export function showCombinedLogs( if (!paths) continue; const files: Array<{ - /** */ path: string /** */; - /** */ - src: 'out' | 'err' | 'structured' | 'warn' | 'info'; + /** Absolute file path to read from */ + path: string; + /** Source type for this file, used for classification */ + src: LogLocation; }> = []; for (const which of whichList) { - if (which === 'out' && paths.outPath) files.push({ path: paths.outPath, src: 'out' }); - if (which === 'err' && paths.errPath) files.push({ path: paths.errPath, src: 'err' }); - if (which === 'structured' && (paths as any).structuredPath) files.push({ path: (paths as any).structuredPath, src: 'structured' }); - if ((paths as any).warnPath && which === 'warn') files.push({ path: (paths as any).warnPath, src: 'warn' }); - if ((paths as any).infoPath && which === 'info') files.push({ path: (paths as any).infoPath, src: 'info' }); + if (which === 'out' && paths.outPath) { + files.push({ path: paths.outPath, src: 'out' }); + } + if (which === 'err' && paths.errPath) { + files.push({ path: paths.errPath, src: 'err' }); + } + if (which === 'structured' && paths.structuredPath) { + files.push({ path: paths.structuredPath, src: 'structured' }); + } + if (paths.warnPath && which === 'warn') { + files.push({ path: paths.warnPath, src: 'warn' }); + } + if (paths.infoPath && which === 'info') { + files.push({ path: paths.infoPath, src: 'info' }); + } } for (const { path, src } of files) { diff --git a/src/lib/pooling/spawnWorkerProcess.ts b/src/lib/pooling/spawnWorkerProcess.ts index a9a2f542..75ed68a3 100644 --- a/src/lib/pooling/spawnWorkerProcess.ts +++ b/src/lib/pooling/spawnWorkerProcess.ts @@ -26,6 +26,9 @@ export interface WorkerLogPaths { errorPath: string; } +/** Convenience alias for the optional return from getWorkerLogPaths */ +export type SlotPaths = Map; + /** * Retrieve the paths we stashed on the child. * @@ -46,7 +49,8 @@ export function getWorkerLogPaths( * @returns True if the IPC channel is open, false otherwise. */ export function isIpcOpen(w: ChildProcess | undefined | null): boolean { - const ch = w && (w as any).channel; + const ch = w && w.channel; + // eslint-disable-next-line @typescript-eslint/no-explicit-any return !!(w && w.connected && ch && !(ch as any).destroyed); } @@ -63,6 +67,7 @@ export function safeSend(w: ChildProcess, msg: unknown): boolean { // eslint-disable-next-line @typescript-eslint/no-explicit-any w.send?.(msg as any); return true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { if ( err?.code === 'ERR_IPC_CHANNEL_CLOSED' || @@ -101,7 +106,8 @@ export interface SpawnWorkerOptions { * - worker-{id}.warn.log (classified WARN lines from stderr) * - worker-{id}.error.log (classified ERROR lines from stderr) * - * @param opts + * @param opts - Options for spawning the worker process. + * @returns The spawned ChildProcess instance. */ export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { const { @@ -145,7 +151,7 @@ export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { child.stderr?.pipe(errStream); // Headers so tail windows show something immediately - const hdr = (name: string) => + const hdr = (name: string): string => `[parent] ${name} capture active for w${id} (pid ${child.pid})\n`; outStream.write(hdr('stdout')); errStream.write(hdr('stderr')); @@ -187,6 +193,7 @@ export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { } // Stash log path metadata on the child + // eslint-disable-next-line @typescript-eslint/no-explicit-any (child as any)[LOG_PATHS_SYM] = { structuredPath, outPath, @@ -205,11 +212,21 @@ export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { } // Best-effort error suppression on file streams - outStream.on('error', () => {}); - errStream.on('error', () => {}); - infoStream.on('error', () => {}); - warnStream.on('error', () => {}); - errorStream.on('error', () => {}); + outStream.on('error', () => { + /* ignore */ + }); + errStream.on('error', () => { + /* ignore */ + }); + infoStream.on('error', () => { + /* ignore */ + }); + warnStream.on('error', () => { + /* ignore */ + }); + errorStream.on('error', () => { + /* ignore */ + }); return child; } diff --git a/src/lib/pooling/workerAssignment.ts b/src/lib/pooling/workerAssignment.ts new file mode 100644 index 00000000..1a01cb86 --- /dev/null +++ b/src/lib/pooling/workerAssignment.ts @@ -0,0 +1,104 @@ +// pool/workerAssignment.ts +import type { ChildProcess } from 'node:child_process'; +import { isIpcOpen, safeSend, type WorkerLogPaths } from './spawnWorkerProcess'; +import type { WorkerState } from './assignWorkToWorker'; + +/** + * Assigns work to a specific worker slot. + */ +export type WorkerMaps = { + /** Map of worker IDs to their ChildProcess instances */ + workers: Map; + /** Map of worker IDs to their state, including progress and last log level */ + workerState: Map; + /** Queue of file paths pending processing */ + slotLogPaths: Map; +}; + +/** + * Assigns work to a specific worker slot. + * + * @param id - The worker slot ID to assign work to + * @param filesQueue - The queue of file paths to process + * @param commonOpts - Common options to send with the work assignment + * @param maps - Maps containing workers and their states + */ +export function assignWorkToSlot( + id: number, + filesQueue: string[], + commonOpts: unknown, + maps: WorkerMaps, +): void { + const { workers, workerState } = maps; + const w = workers.get(id); + + // mark idle if IPC closed + if (!isIpcOpen(w)) { + const prev = workerState.get(id); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); + return; + } + + const filePath = filesQueue.shift(); + if (!filePath) { + const prev = workerState.get(id); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); + return; + } + + workerState.set(id, { + busy: true, + file: filePath, + startedAt: Date.now(), + lastLevel: 'ok', + progress: undefined, + }); + + if ( + !safeSend(w!, { type: 'task', payload: { filePath, options: commonOpts } }) + ) { + // IPC closed between check and send; re-queue and mark idle + filesQueue.unshift(filePath); + const prev = workerState.get(id); + workerState.set(id, { + busy: false, + file: null, + startedAt: null, + lastLevel: prev?.lastLevel ?? 'ok', + progress: undefined, + }); + } +} + +/** + * Refill idle workers with pending files. + * + * @param filesQueue - The queue of file paths to process + * @param maps - Maps containing workers and their states + * @param assign - Function to assign work to a worker + */ +export function refillIdleWorkers( + filesQueue: string[], + maps: WorkerMaps, + assign: (id: number) => void, +): void { + for (const [id] of maps.workers) { + const st = maps.workerState.get(id); + if (!st || !st.busy) { + if (filesQueue.length === 0) break; + assign(id); + } + } +} diff --git a/src/lib/pooling/workerIds.ts b/src/lib/pooling/workerIds.ts new file mode 100644 index 00000000..7ec20a90 --- /dev/null +++ b/src/lib/pooling/workerIds.ts @@ -0,0 +1,35 @@ +import type { ChildProcess } from 'node:child_process'; + +/** + * Get the sorted list of worker IDs from a map of ChildProcess instances. + * + * @param m - Map of worker IDs to ChildProcess instances. + * @returns Sorted array of worker IDs. + */ +export function getWorkerIds(m: Map): number[] { + return [...m.keys()].sort((a, b) => a - b); +} + +/** + * Cycles through an array of numeric IDs, returning the next ID based on a delta. + * + * If the `current` ID is not provided or not found in the array, the first ID is used as the starting point. + * The function then moves forward or backward in the array by `delta` positions, wrapping around if necessary. + * + * @param ids - Array of numeric IDs to cycle through. + * @param current - The current ID to start cycling from. If `null` or not found, starts from the first ID. + * @param delta - The number of positions to move forward (positive) or backward (negative) in the array. + * @returns The next ID in the array after cycling, or `null` if the array is empty. + */ +export function cycleWorkers( + ids: number[], + current: number | null, + delta: number, +): number | null { + if (!ids.length) return null; + const cur = current == null ? ids[0] : current; + let i = ids.indexOf(cur); + if (i === -1) i = 0; + i = (i + delta + ids.length) % ids.length; + return ids[i]!; +} diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 588c7482..8f367000 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -261,7 +261,7 @@ export async function addTranscendIdToPreferences( // Add a transcendent ID to each preference if it doesn't already exist const emailList = (process.env.EMAIL_LIST || '') .split(',') - .map((email) => email.trim()); + .map((email) => email.trim().split('"').join('').split('"').join('')); const disallowedEmails = [ 'noemail@costco.com', 'NOEMAILYET@GMAIL.COM', @@ -372,7 +372,7 @@ export async function addTranscendIdToPreferences( ...pref, person_id: pref.person_id !== '-2' ? pref.person_id : '', email_address: - !email || disallowedEmails.includes(email) ? '' : pref.email_address, + !email || disallowedEmails.includes(email) ? '' : pref.email_address, // FIXME transcendID: pref.person_id && pref.person_id !== '-2' ? pref.person_id From c2dbe77f10929407692ce079d8c21f9ed6c7f266 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 00:58:04 -0700 Subject: [PATCH 31/72] More updates --- .../consent/upload-preferences/impl.ts | 29 +++++++++++----- .../upload-preferences/ui/buildFrameModel.ts | 33 +++++++++++++++++++ .../interactivePreferenceUploaderFromPlan.ts | 21 ++++++------ 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 27d7c632..79cf18d8 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -34,7 +34,12 @@ import { installInteractiveSwitcher, } from '../../../lib/pooling'; import { RateCounter } from '../../../lib/helpers'; -import { renderDashboard, AnyTotals } from './ui'; +import { + renderDashboard, + AnyTotals, + isUploadModeTotals, + isCheckModeTotals, +} from './ui'; import { writeFailingUpdatesCsv, ExportManager } from './artifacts'; import { applyReceiptSummary, FailingUpdateRow } from './receipts'; @@ -303,7 +308,9 @@ export async function uploadPreferences( const renderInterval = setInterval(() => repaint(false), 350); // graceful Ctrl+C - let cleanupSwitcher: () => void = () => {}; + let cleanupSwitcher: () => void = () => { + // noop, will be replaced by installInteractiveSwitcher + }; const onSigint = (): void => { clearInterval(renderInterval); cleanupSwitcher(); @@ -407,21 +414,25 @@ export async function uploadPreferences( // Final repaint with exportStatus visible & green repaint(true); - if (agg.mode === 'upload') { - const a = agg as any; + if (isUploadModeTotals(agg)) { process.stdout.write( colors.green( - `\nAll done. Success:${a.success.toLocaleString()} Skipped:${a.skipped.toLocaleString()} Error:${a.error.toLocaleString()}\n`, + `\nAll done. Success:${agg.success.toLocaleString()} Skipped:${agg.skipped.toLocaleString()} Error:${agg.error.toLocaleString()}\n`, ), ); - } else { - const a = agg as any; + } else if (isCheckModeTotals(agg)) { process.stdout.write( colors.green( - `\nAll done. Pending:${a.totalPending.toLocaleString()} PendingConflicts:${a.pendingConflicts.toLocaleString()} ` + - `PendingSafe:${a.pendingSafe.toLocaleString()} Skipped:${a.skipped.toLocaleString()}\n`, + `\nAll done. Pending:${agg.totalPending.toLocaleString()} PendingConflicts:${agg.pendingConflicts.toLocaleString()} ` + + `PendingSafe:${agg.pendingSafe.toLocaleString()} Skipped:${agg.skipped.toLocaleString()}\n`, ), ); + } else { + throw new Error( + `Unknown totals type, expected UploadModeTotals or CheckModeTotals. ${JSON.stringify( + agg, + )}`, + ); } process.stdout.write( diff --git a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts index 857ae9b3..503f0ca3 100644 --- a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts +++ b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts @@ -27,6 +27,38 @@ export type CheckModeTotals = { skipped: number; }; +/** + * Type guard for UploadModeTotals + * + * @param totals - The totals object to check + * @returns True if the totals object is of type UploadModeTotals, false otherwise + */ +export function isUploadModeTotals( + totals: unknown, +): totals is UploadModeTotals { + return ( + typeof totals === 'object' && + totals !== null && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (totals as any).mode === 'upload' + ); +} + +/** + * Type guard for CheckModeTotals + * + * @param totals - The totals object to check + * @returns True if the totals object is of type CheckModeTotals, false otherwise + */ +export function isCheckModeTotals(totals: unknown): totals is CheckModeTotals { + return ( + typeof totals === 'object' && + totals !== null && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (totals as any).mode === 'check' + ); +} + /** * Represents the totals for either upload or check mode. */ @@ -149,6 +181,7 @@ export function buildFrameModel(input: RenderDashboardInput): FrameModel { : minutes > 0 ? `${minutes}m` : `${secondsLeft}s`; + // FIXME broken etaText = `Expected completion: ${eta.toLocaleTimeString()} (${timeLeft} left)`; } } diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 813242db..abe43e69 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import colors from 'colors'; import { map as pMap } from 'bluebird'; import { chunk } from 'lodash-es'; @@ -160,6 +161,7 @@ export async function interactivePreferenceUploaderFromPlan( maxAttempts: 3, delayMs: 10_000, shouldRetry: (status?: number) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any !!status && RETRYABLE_BATCH_STATUSES.has(status as any), }; @@ -176,10 +178,10 @@ export async function interactivePreferenceUploaderFromPlan( for (const [userId] of entries) { successfulUpdates[userId] = true; - delete (pendingUpdates as any)[userId]; + delete pendingUpdates[userId]; // Also keep the safe/conflict mirrors in sync in case of resume - delete (pendingSafeUpdates as any)[userId]; - delete (pendingConflictUpdates as any)[userId]; + delete pendingSafeUpdates[userId]; + delete pendingConflictUpdates[userId]; } uploadedCount += entries.length; onProgress?.({ @@ -225,11 +227,6 @@ export async function interactivePreferenceUploaderFromPlan( err: unknown, ): Promise => { const msg = extractErrorMessage(err); - // FIXME - // if (msg.includes('Too many identifiers')) { - // // Add first identifier clue to speed up triage - // msg += `\n ----> ${userId.split('___')[0]}`; - // } logger.error( colors.red( `Failed to upload preferences for ${userId} (partition=${partition}): ${msg}`, @@ -242,9 +239,9 @@ export async function interactivePreferenceUploaderFromPlan( error: msg, }; - delete (pendingUpdates as any)[userId]; - delete (pendingSafeUpdates as any)[userId]; - delete (pendingConflictUpdates as any)[userId]; + delete pendingUpdates[userId]; + delete pendingSafeUpdates[userId]; + delete pendingConflictUpdates[userId]; await receipts.setFailing(failing); }; @@ -288,6 +285,7 @@ export async function interactivePreferenceUploaderFromPlan( retryPolicy, options: { skipWorkflowTriggers, forceTriggerWorkflows }, isRetryableStatus: (s) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any !!s && RETRYABLE_BATCH_STATUSES.has(s as any), }, { @@ -325,3 +323,4 @@ export async function interactivePreferenceUploaderFromPlan( ); } } +/* eslint-enable no-param-reassign */ From 735bf8c70322ec17665fb9330201c7b6f8648424 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 01:06:24 -0700 Subject: [PATCH 32/72] ud --- src/lib/preference-management/getPreferenceUpdatesFromRow.ts | 1 + .../parsePreferenceIdentifiersFromCsv.ts | 4 ++-- src/lib/preference-management/parsePreferenceManagementCsv.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts index 92eadf9e..11c33c4c 100644 --- a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts +++ b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts @@ -65,6 +65,7 @@ export function getPreferenceUpdatesFromRow({ // The value to parse const rawValue = row[columnName]; const rawMapping = valueMapping[rawValue]; + // When mapping is undefined, it means we should omit this column if (rawMapping === undefined) { // FIXME return; diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 8f367000..f03c11f5 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -191,7 +191,7 @@ export function getPreferenceIdentifiersFromRow({ /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; }): PreferenceStoreIdentifier[] { - // TODO: Remove this COSTCO specific logic + // FIXME: Remove this COSTCO specific logic const emailColumn = Object.keys(columnToIdentifier).find((x) => x.includes('email'), ); @@ -201,7 +201,7 @@ export function getPreferenceIdentifiersFromRow({ return ( Object.entries(columnToIdentifier) .filter(([col]) => !!row[col]) - // TODO: Remove this COSTCO specific logic + // FIXME: Remove this COSTCO specific logic .filter( ([col]) => !(col === 'transcendID' && row[col] && row[emailColumn]), ) diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 0376a1b3..0743eb50 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -89,7 +89,7 @@ export async function parsePreferenceManagementCsvWithCache( let preferences = readCsv(file, t.record(t.string, t.string)); logger.info(colors.magenta(`Read in ${preferences.length} rows`)); - // TODO: Remove this COSTCO specific logic + // FIXME: Remove this COSTCO specific logic const updatedPreferences = await addTranscendIdToPreferences(preferences); preferences = updatedPreferences; From 6ab7fb17aec35f709a2b6029808bdd530f568079 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 01:26:26 -0700 Subject: [PATCH 33/72] Mostly fixed --- .../upload-preferences/transform/index.ts | 2 + .../transform/transformCsv.ts | 30 ++++ .../upload/buildInteractiveUploadPlan.ts | 10 +- src/lib/code-scanning/integrations/swift.ts | 4 +- .../getPreferencesForIdentifiers.ts | 1 + .../parsePreferenceIdentifiersFromCsv.ts | 165 +----------------- .../parsePreferenceManagementCsv.ts | 21 +-- 7 files changed, 55 insertions(+), 178 deletions(-) create mode 100644 src/commands/consent/upload-preferences/transform/index.ts create mode 100644 src/commands/consent/upload-preferences/transform/transformCsv.ts diff --git a/src/commands/consent/upload-preferences/transform/index.ts b/src/commands/consent/upload-preferences/transform/index.ts new file mode 100644 index 00000000..cbc30d10 --- /dev/null +++ b/src/commands/consent/upload-preferences/transform/index.ts @@ -0,0 +1,2 @@ +export * from './buildPendingUpdates'; +export * from './transformCsv'; diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/transform/transformCsv.ts new file mode 100644 index 00000000..95632aef --- /dev/null +++ b/src/commands/consent/upload-preferences/transform/transformCsv.ts @@ -0,0 +1,30 @@ +// FIXME + +/** + * Add Transcend ID to preferences if email_id is present + * + * @param preferences - List of preferences + * @returns The updated preferences with Transcend ID added + */ +export function transformCsv( + preferences: Record[], +): Record[] { + // Add a transcendent ID to each preference if it doesn't already exist + const disallowedEmails = (process.env.EMAIL_LIST || '') + .split(',') + .map((email) => email.trim().toLowerCase()); + + return preferences.map((pref) => { + const email = (pref.email_address || '').toLowerCase().trim(); + return { + ...pref, + person_id: pref.person_id !== '-2' ? pref.person_id : '', + email_address: + !email || disallowedEmails.includes(email) ? '' : pref.email_address, // FIXME + transcendID: + pref.person_id && pref.person_id !== '-2' + ? pref.person_id + : pref.member_id, + }; + }); +} diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index 1661203f..d92117f0 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -1,7 +1,8 @@ import colors from 'colors'; +import * as t from 'io-ts'; import type { Got } from 'got'; import { logger } from '../../../../logger'; -import { parseAttributesFromString } from '../../../../lib/requests'; +import { parseAttributesFromString, readCsv } from '../../../../lib/requests'; import { loadReferenceData, type PreferenceUploadReferenceData, @@ -18,6 +19,7 @@ import type { import type { FormattedAttribute } from '../../../../lib/graphql/formatAttributeValues'; import type { GraphQLClient } from 'graphql-request'; import { limitRecords } from '../../../../lib/helpers'; +import { transformCsv } from '../transform'; export interface InteractiveUploadPreferencePlan { /** CSV file path to load preference records from */ @@ -116,8 +118,14 @@ export async function buildInteractiveUploadPreferencePlan({ // Build clients + reference data (purposes/topics/identifiers) const references = await loadReferenceData(client, forceTriggerWorkflows); + // Read in the file + logger.info(colors.magenta(`Reading in file: "${file}"`)); + const preferences = transformCsv(readCsv(file, t.record(t.string, t.string))); + logger.info(colors.magenta(`Read in ${preferences.length} rows`)); + // Parse & validate CSV → derive safe/conflict/skipped sets (no uploading) const parsed = await parsePreferenceManagementCsvWithCache( + preferences, { file, purposeSlugs: references.purposes.map((x) => x.trackingType), diff --git a/src/lib/code-scanning/integrations/swift.ts b/src/lib/code-scanning/integrations/swift.ts index f7fde38d..f47816c5 100644 --- a/src/lib/code-scanning/integrations/swift.ts +++ b/src/lib/code-scanning/integrations/swift.ts @@ -30,8 +30,8 @@ export const swift: CodeScanningConfig = { return [ { - name: dirname(filePath).split('/').pop() || '', // FIXME pull from Package.swift ->> name if possible - type: CodePackageType.CocoaPods, // FIXME should be swift + name: dirname(filePath).split('/').pop() || '', // TODO pull from Package.swift ->> name if possible + type: CodePackageType.CocoaPods, // TODO should be swift softwareDevelopmentKits: parsed.pins.map((target) => ({ name: target.identity, version: target.state.version, diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 720f9dbf..961fb639 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -90,6 +90,7 @@ export async function getPreferencesForIdentifiers( total += group.length; // progressBar.update(total); // log every 1000 + // FIXME if (total % 1000 === 0 && !skipLogging) { logger.info( colors.green( diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index f03c11f5..63c3304c 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -191,25 +191,12 @@ export function getPreferenceIdentifiersFromRow({ /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; }): PreferenceStoreIdentifier[] { - // FIXME: Remove this COSTCO specific logic - const emailColumn = Object.keys(columnToIdentifier).find((x) => - x.includes('email'), - ); - if (!emailColumn) { - throw new Error('Email column not found in csv file.'); - } - return ( - Object.entries(columnToIdentifier) - .filter(([col]) => !!row[col]) - // FIXME: Remove this COSTCO specific logic - .filter( - ([col]) => !(col === 'transcendID' && row[col] && row[emailColumn]), - ) - .map(([col, identifierMapping]) => ({ - name: identifierMapping.name, - value: row[col], - })) - ); + return Object.entries(columnToIdentifier) + .filter(([col]) => !!row[col]) + .map(([col, identifierMapping]) => ({ + name: identifierMapping.name, + value: row[col], + })); } /** @@ -240,143 +227,3 @@ export function getUniquePreferenceIdentifierNamesFromRow({ } return columns; } - -/** - * Add Transcend ID to preferences if email_id is present - * - * @param preferences - List of preferences - * @returns The updated preferences with Transcend ID added - * // FIXME: Remove this COSTCO specific logic - */ -export async function addTranscendIdToPreferences( - preferences: Record[], -): Promise[]> { - // const haveTranscendId = await inquirerConfirmBoolean({ - // message: 'Would you like transcendID for costco upload?', - // }); - // if (!haveTranscendId) { - // logger.info(colors.yellow('Skipping adding Transcend ID to preferences.')); - // return preferences; - // } - // Add a transcendent ID to each preference if it doesn't already exist - const emailList = (process.env.EMAIL_LIST || '') - .split(',') - .map((email) => email.trim().split('"').join('').split('"').join('')); - const disallowedEmails = [ - 'noemail@costco.com', - 'NOEMAILYET@GMAIL.COM', - 'noemail@gmail.com', - 'noemail@aol.com', - 'IDONTCARE@YAHOO.COM', - 'none@none.com', - 'noemail@mail.com', - 'no@email.com', - 'noemail@no.com', - '123@gmail.com', - 'no.no@gmail.com', - 'BC@GMAIL.COM', - 'NA@COMCAST.NET', - 'NO@YAHOO.COM', - 'R@GMAIL.COM', - 'noemail@email.com', - 'NOEMAIL@ME.COM', - 'NONAME@GMAIL.COM', - 'NOEMAIL@HOTMAIL.COM', - 'notoemail@gmail.com', - 'NOMAIL@MAIL.COM', - 'DONOTHAVE@YAHOO.COM', - 'NAME1@AOL.COM', - 'DAN@GMAIL.COM', - 'NA@YAHOO.COM', - 'NONE.NONE@GMAIL.COM', - 'KC@COSTCO.COM', - 'NONE1@GMAIL.COM', - 'NONE@HOTMAIL.COM', - 'COSTCO@NON.COM', - 'NOEMAILATM@YAHOO.COM', - 'NO@MAIL.COM', - 'N@N.COM', - 'NOEMAIL@COSTOC.COM', - 'DONTHAVEEMAIL@YAHOO.COM', - 'PAT@GMAIL.COM', - 'NO@NOEMAIL.COM', - 'sam@gmail.com', - 'OPTOUT@NOEMAIL.NET', - 'M@GMAIL.COM', - 'ADDEMAIL@YAHOO.COM', - 'JM@YAHOO.COM', - 'NOEMAIL@INTERNET.COM', - 'sam@gmail.com', - 'NO@AOL.COM', - '111@GMAIL.COM', - 'NOTHING@HOTMAIL.COM', - 'CHEN@GMAIL.COM', - 'JM@YAHOO.COM', - 'M@GMAIL.COM', - 'WII@GMAIL.COM', - 'NOTHANKS@COSTCO.COM', - 'NOGMAIL@GMAIL.COM', - 'NA@NA.COM', - 'NOPE@COSTCO.COM', - 'USER@GMAIL.COM', - 'Noemail@outlook.com', - 'none@gmail.com', - 'GETEMAIL@GMAIL.COM', - 'EMAIL@EMAIL.COM', - 'NAME@AOL.COM', - 'NOTHING@GMAIL.COM', - 'NO2@GMAIL.COM', - 'NO@INFO.COM', - 'NOMAIL@NOMAIL.COM', - 'COSTCO@GMAIL.COM', - 'NO@NE.COM', - 'NONE@AOL.COM', - 'donthave@hotmail.com', - 'FIRSTNAME.LASTNAME@GMAIL.COM', - 'NOEMAIL@COSTO.COM', - 'update@gmail.com', - 'NOMAIL@MAIL.COM ', - 'JUNKMAILER@AOL.COM', - 'noemail@yahoo.com', - 'EMAIL@GMAIL.COM', - 'no@gmail.com', - 'na@gmail.com', - 'NOMAIL@GMAIL.COM', - 'costco@costco.com', - 'ABC@GMAIL.COM', - 'noemail@noemail.com', - 'replace@gmail.com', - 'j@gmail.com', - 'NONE@YAHOO.COM', - 'JESUS@GMAIL.COM', - 'a@gmail.com', - 'ME@ME.COM', - 'NEEDEMAIL@GMAIL.COM', - '_NONE@EMAIL.COM', - 'NONE1@YAHOO.COM', - 'NOMEMBEREMAIL@COSTCO.COM', - 'MAILDUMP@MAIL.COM', - 'NONE@EMAIL.COM', - 'no@no.com', - 'none@outlook.com', - 'none@yahoo.com', - '123@LIVE.COM', - // FIXME - ...emailList, - ].map((email) => email.toLowerCase()); - console.log(emailList[0], emailList[50]); - - return preferences.map((pref) => { - const email = (pref.email_address || '').toLowerCase().trim(); - return { - ...pref, - person_id: pref.person_id !== '-2' ? pref.person_id : '', - email_address: - !email || disallowedEmails.includes(email) ? '' : pref.email_address, // FIXME - transcendID: - pref.person_id && pref.person_id !== '-2' - ? pref.person_id - : pref.member_id, - }; - }); -} diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 0743eb50..241dcc00 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -1,7 +1,6 @@ import { PersistedState } from '@transcend-io/persisted-state'; import type { Got } from 'got'; import { keyBy } from 'lodash-es'; -import * as t from 'io-ts'; import colors from 'colors'; import { type FileFormatState, @@ -11,13 +10,11 @@ import { type SkippedPreferenceUpdates, } from './codecs'; import { logger } from '../../logger'; -import { readCsv } from '../requests'; import { getPreferencesForIdentifiers } from './getPreferencesForIdentifiers'; import { PreferenceTopic, type Identifier } from '../graphql'; import { getPreferenceUpdatesFromRow } from './getPreferenceUpdatesFromRow'; import { parsePreferenceFileFormatFromCsv } from './parsePreferenceFileFormatFromCsv'; import { - addTranscendIdToPreferences, getUniquePreferenceIdentifierNamesFromRow, parsePreferenceIdentifiersFromCsv, } from './parsePreferenceIdentifiersFromCsv'; @@ -30,12 +27,13 @@ import type { ObjByString } from '@transcend-io/type-utils'; * Parse a file into the cache * * + * @param rawPreferences - The preferences to parse * @param options - Options * @param schemaState - The schema state to use for parsing the file - * @param schema * @returns The cache with the parsed file */ export async function parsePreferenceManagementCsvWithCache( + rawPreferences: Record[], { file, sombra, @@ -84,26 +82,17 @@ export async function parsePreferenceManagementCsvWithCache( // Start the timer const t0 = new Date().getTime(); - // Read in the file - logger.info(colors.magenta(`Reading in file: "${file}"`)); - let preferences = readCsv(file, t.record(t.string, t.string)); - logger.info(colors.magenta(`Read in ${preferences.length} rows`)); - - // FIXME: Remove this COSTCO specific logic - const updatedPreferences = await addTranscendIdToPreferences(preferences); - preferences = updatedPreferences; - // Validate that all timestamps are present in the file - await parsePreferenceFileFormatFromCsv(preferences, schemaState); + await parsePreferenceFileFormatFromCsv(rawPreferences, schemaState); // Validate that all identifiers are present and unique - const result = await parsePreferenceIdentifiersFromCsv(preferences, { + const result = await parsePreferenceIdentifiersFromCsv(rawPreferences, { schemaState, orgIdentifiers, allowedIdentifierNames, identifierColumns, }); - preferences = result.preferences; + const { preferences } = result; // Ensure all other columns are mapped to purpose and preference slug values await parsePreferenceAndPurposeValuesFromCsv(preferences, schemaState, { From d120efe670b9a20b02762ba39c8e3c499e95fd3c Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 01:32:10 -0700 Subject: [PATCH 34/72] Mostly fixed --- .../upload-preferences/transform/transformCsv.ts | 10 ++++++---- .../parsePreferenceIdentifiersFromCsv.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/transform/transformCsv.ts index 95632aef..9fbb4058 100644 --- a/src/commands/consent/upload-preferences/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/transform/transformCsv.ts @@ -21,10 +21,12 @@ export function transformCsv( person_id: pref.person_id !== '-2' ? pref.person_id : '', email_address: !email || disallowedEmails.includes(email) ? '' : pref.email_address, // FIXME - transcendID: - pref.person_id && pref.person_id !== '-2' - ? pref.person_id - : pref.member_id, + // preference email address over transcendID + transcendID: pref.email_address + ? '' + : pref.person_id && pref.person_id !== '-2' + ? pref.person_id + : pref.member_id, }; }); } diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 63c3304c..bbcecb01 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -216,7 +216,7 @@ export function getUniquePreferenceIdentifierNamesFromRow({ /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; }): string[] { - // FIXME remove email logic + // TODO: https://linear.app/transcend/issue/PIK-285/set-precedence-of-unique-identifiers - remove email logic const columns = Object.keys(columnToIdentifier).filter( (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, ); From e00075eaa691dcbb27abb4ac14b1f35853f7bd42 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 01:43:36 -0700 Subject: [PATCH 35/72] Fixes transform --- .../consent/upload-preferences/impl.ts | 2 +- .../transform/transformCsv.ts | 7 ++--- .../upload-preferences/ui/buildFrameModel.ts | 27 ++++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 79cf18d8..1801d8ee 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -241,7 +241,7 @@ export async function uploadPreferences( if (isWorkerProgressMessage(msg)) { const { successDelta, successTotal, fileTotal, filePath } = msg.payload || {}; - liveSuccessTotal += successDelta || 0; + liveSuccessTotal += successTotal || 0; const prev = workerState.get(i)!; const processed = successTotal ?? prev.progress?.processed ?? 0; diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/transform/transformCsv.ts index 9fbb4058..877aa673 100644 --- a/src/commands/consent/upload-preferences/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/transform/transformCsv.ts @@ -16,13 +16,14 @@ export function transformCsv( return preferences.map((pref) => { const email = (pref.email_address || '').toLowerCase().trim(); + const emailAddress = + !email || disallowedEmails.includes(email) ? '' : pref.email_address; return { ...pref, person_id: pref.person_id !== '-2' ? pref.person_id : '', - email_address: - !email || disallowedEmails.includes(email) ? '' : pref.email_address, // FIXME + email_address: emailAddress, // preference email address over transcendID - transcendID: pref.email_address + transcendID: emailAddress ? '' : pref.person_id && pref.person_id !== '-2' ? pref.person_id diff --git a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts index 503f0ca3..d746069c 100644 --- a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts +++ b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts @@ -170,19 +170,28 @@ export function buildFrameModel(input: RenderDashboardInput): FrameModel { const uploaded = throughput.successSoFar; const remainingJobs = Math.max(estTotalJobs - uploaded, 0); const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; + if (ratePerSec > 0 && remainingJobs > 0) { const secondsLeft = Math.round(remainingJobs / ratePerSec); const eta = new Date(Date.now() + secondsLeft * 1000); - const hours = Math.floor(secondsLeft / 3600); + + const days = Math.floor(secondsLeft / 86400); // 24 * 3600 + const hours = Math.floor((secondsLeft % 86400) / 3600); const minutes = Math.floor((secondsLeft % 3600) / 60); - const timeLeft = - hours > 0 - ? `${hours}h ${minutes}m` - : minutes > 0 - ? `${minutes}m` - : `${secondsLeft}s`; - // FIXME broken - etaText = `Expected completion: ${eta.toLocaleTimeString()} (${timeLeft} left)`; + const seconds = secondsLeft % 60; + + let timeLeft = ''; + if (days > 0) { + timeLeft = `${days}d ${hours}h ${minutes}m`; + } else if (hours > 0) { + timeLeft = `${hours}h ${minutes}m`; + } else if (minutes > 0) { + timeLeft = `${minutes}m ${seconds}s`; + } else { + timeLeft = `${seconds}s`; + } + + etaText = `Expected completion: ${eta.toLocaleString()} (${timeLeft} left)`; } } From 1fef12c0442fee778ce39ab6170a4a952b816f83 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 01:50:06 -0700 Subject: [PATCH 36/72] cleaned --- .../consent/upload-preferences/runChild.ts | 1 + .../upload/buildInteractiveUploadPlan.ts | 4 ++++ .../getPreferenceUpdatesFromRow.ts | 1 - .../getPreferencesForIdentifiers.ts | 21 ++++++++----------- .../parsePreferenceManagementCsv.ts | 6 +++++- src/lib/requests/skipPreflightJobs.ts | 4 ++-- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 1049f14f..52a9880c 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -98,6 +98,7 @@ export async function runChild(): Promise { partition: options.partition, receipts, schema, + uploadLogInterval: options.uploadLogInterval, skipExistingRecordCheck: options.skipExistingRecordCheck, forceTriggerWorkflows: options.forceTriggerWorkflows, allowedIdentifierNames: options.allowedIdentifierNames, diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index d92117f0..19adbf73 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -69,6 +69,7 @@ export async function buildInteractiveUploadPreferencePlan({ skipExistingRecordCheck = false, forceTriggerWorkflows = false, allowedIdentifierNames, + uploadLogInterval = 1000, maxRecordsToReceipt = 50, identifierColumns, columnsToIgnore = [], @@ -98,6 +99,8 @@ export async function buildInteractiveUploadPreferencePlan({ columnsToIgnore?: string[]; /** Extra workflow attributes (pre-parsed Key:Value strings) */ attributes?: string[]; + /** Interval to log upload progress */ + uploadLogInterval?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; }): Promise { @@ -137,6 +140,7 @@ export async function buildInteractiveUploadPreferencePlan({ orgIdentifiers: references.identifiers, allowedIdentifierNames, identifierColumns, + uploadLogInterval, columnsToIgnore, }, schema.state, diff --git a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts index 11c33c4c..d6953344 100644 --- a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts +++ b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts @@ -67,7 +67,6 @@ export function getPreferenceUpdatesFromRow({ const rawMapping = valueMapping[rawValue]; // When mapping is undefined, it means we should omit this column if (rawMapping === undefined) { - // FIXME return; } diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 961fb639..466feec8 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -38,6 +38,7 @@ export async function getPreferencesForIdentifiers( identifiers, partitionKey, skipLogging = false, + uploadLogInterval = 1000, }: { /** The list of identifiers to look up */ identifiers: { @@ -50,6 +51,8 @@ export async function getPreferencesForIdentifiers( partitionKey: string; /** Whether to skip logging */ skipLogging?: boolean; + /** The interval to log upload progress */ + uploadLogInterval?: number; }, ): Promise { const results: PreferenceQueryResponseItem[] = []; @@ -57,13 +60,6 @@ export async function getPreferencesForIdentifiers( // create a new progress bar instance and use shades_classic theme const t0 = new Date().getTime(); - // const progressBar = new cliProgress.SingleBar( - // {}, - // cliProgress.Presets.shades_classic, - // ); - // if (!skipLogging) { - // progressBar.start(identifiers.length, 0); - // } let total = 0; await map( @@ -88,10 +84,12 @@ export async function getPreferencesForIdentifiers( const result = decodeCodec(PreferenceRecordsQueryResponse, rawResult); results.push(...result.nodes); total += group.length; - // progressBar.update(total); - // log every 1000 - // FIXME - if (total % 1000 === 0 && !skipLogging) { + const shouldLog = + !skipLogging && + (total % uploadLogInterval === 0 || + Math.floor((total - identifiers.length) / uploadLogInterval) < + Math.floor(total / uploadLogInterval)); + if (shouldLog) { logger.info( colors.green( `Fetched ${total}/${identifiers.length} user preferences from partition ${partitionKey}`, @@ -125,7 +123,6 @@ export async function getPreferencesForIdentifiers( }, ); - // progressBar.stop(); const t1 = new Date().getTime(); const totalTime = t1 - t0; diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 241dcc00..1019a729 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -45,6 +45,7 @@ export async function parsePreferenceManagementCsvWithCache( orgIdentifiers, allowedIdentifierNames, identifierColumns, + uploadLogInterval, columnsToIgnore, }: { /** File to parse */ @@ -69,6 +70,8 @@ export async function parsePreferenceManagementCsvWithCache( identifierColumns: string[]; /** Columns to ignore in the CSV file */ columnsToIgnore: string[]; + /** The interval to log upload progress */ + uploadLogInterval: number; }, schemaState: PersistedState, ): Promise<{ @@ -122,6 +125,7 @@ export async function parsePreferenceManagementCsvWithCache( ? [] : await getPreferencesForIdentifiers(sombra, { identifiers, + uploadLogInterval, partitionKey, }); const consentRecordByIdentifier = keyBy(existingConsentRecords, 'userId'); @@ -227,7 +231,7 @@ export async function parsePreferenceManagementCsvWithCache( currentConsentRecord, pendingUpdates, preferenceTopics, - log: false, // FIXME + log: false, // update this to log for debugging purposes }) ) { pendingConflictUpdates[primaryKey] = { diff --git a/src/lib/requests/skipPreflightJobs.ts b/src/lib/requests/skipPreflightJobs.ts index a22d90e5..59516f75 100644 --- a/src/lib/requests/skipPreflightJobs.ts +++ b/src/lib/requests/skipPreflightJobs.ts @@ -70,7 +70,7 @@ export async function skipPreflightJobs({ await map( requests, async (request) => { - // FIXME dont pull all in + // TODO dont pull all in const requestEnrichers = await fetchAllRequestEnrichers(client, { requestId: request.id, }); @@ -84,7 +84,7 @@ export async function skipPreflightJobs({ ].includes(enricher.status as any), ); - // FIXME + // TODO if (requestEnrichersFiltered.length > 0) { await mapSeries(requestEnrichersFiltered, async (requestEnricher) => { try { From e920f7d2416d56d6fa82e6ca4c5435353c7c02b3 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 02:20:04 -0700 Subject: [PATCH 37/72] just nearly fully working --- .../artifacts/ExportManager.ts | 2 +- .../artifacts/writeFailingUpdatesCsv.ts | 5 ++-- .../consent/upload-preferences/impl.ts | 14 ++++++----- .../upload-preferences/ui/buildFrameModel.ts | 18 +++++++++++---- .../interactivePreferenceUploaderFromPlan.ts | 5 ---- src/lib/pooling/keypressExtra.ts | 23 ++++++++++++++++++- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/commands/consent/upload-preferences/artifacts/ExportManager.ts b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts index 3976d50e..c3040a46 100644 --- a/src/commands/consent/upload-preferences/artifacts/ExportManager.ts +++ b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts @@ -19,7 +19,7 @@ import { readSafe } from '../../../../lib/helpers'; * Write the exports index file with the latest paths for each export kind. */ export class ExportManager { - constructor(private exportsDir?: string) {} + constructor(public exportsDir: string) {} /** * Get the absolute path for an export artifact based on its kind. diff --git a/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts b/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts index 88dbc4de..f049ce40 100644 --- a/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts +++ b/src/commands/consent/upload-preferences/artifacts/writeFailingUpdatesCsv.ts @@ -1,20 +1,19 @@ import { mkdirSync, writeFileSync } from 'node:fs'; import type { FailingUpdateRow } from '../receipts'; +import { dirname } from 'node:path'; /** * Write a CSV file for failing updates. * - * @param folderName - The folder where the CSV file will be written * @param rows - The rows to write to the CSV file * @param outPath - The output path for the CSV file * @returns The absolute path to the written CSV file */ export function writeFailingUpdatesCsv( - folderName: string, rows: FailingUpdateRow[], outPath: string, ): string { - mkdirSync(folderName, { recursive: true }); + mkdirSync(dirname(outPath), { recursive: true }); const headers = Array.from( rows.reduce>((acc, row) => { Object.keys(row || {}).forEach((k) => acc.add(k)); diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 1801d8ee..daa78700 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -2,7 +2,7 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; -import { join, resolve } from 'node:path'; +import { join } from 'node:path'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; @@ -241,7 +241,7 @@ export async function uploadPreferences( if (isWorkerProgressMessage(msg)) { const { successDelta, successTotal, fileTotal, filePath } = msg.payload || {}; - liveSuccessTotal += successTotal || 0; + liveSuccessTotal += successDelta || 0; const prev = workerState.get(i)!; const processed = successTotal ?? prev.progress?.processed ?? 0; @@ -357,6 +357,7 @@ export async function uploadPreferences( slotLogPaths, exportMgr, exportStatus, + failingUpdates: failingUpdatesMem, onRepaint: () => repaint(), onPause: (p) => { dashboardPaused = p; @@ -397,8 +398,7 @@ export async function uploadPreferences( const a = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); exportStatus.all = { path: a, savedAt: Date.now(), exported: true }; const fPath = join(logDir, 'failing-updates.csv'); - const folderName = resolve(logDir, '../'); - await writeFailingUpdatesCsv(folderName, failingUpdatesMem, fPath); + await writeFailingUpdatesCsv(failingUpdatesMem, fPath); exportStatus.failuresCsv = { path: fPath, savedAt: Date.now(), @@ -407,8 +407,10 @@ export async function uploadPreferences( process.stdout.write( `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${a}\n ${fPath}\n\n`, ); - } catch { - // noop + } catch (err) { + process.stdout.write( + colors.red(`Failed to download CSV:${err.stack}`), + ); } // Final repaint with exportStatus visible & green diff --git a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts index d746069c..76274b3a 100644 --- a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts +++ b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts @@ -167,12 +167,22 @@ export function buildFrameModel(input: RenderDashboardInput): FrameModel { // ETA let etaText = ''; if (throughput && estTotalJobs !== undefined) { - const uploaded = throughput.successSoFar; - const remainingJobs = Math.max(estTotalJobs - uploaded, 0); + // Prefer receipts totals; fall back to throughput.successSoFar if not available + const processedSoFar = + jobsFromReceipts !== undefined + ? jobsFromReceipts + : throughput.successSoFar; + + const remainingJobs = Math.max(estTotalJobs - processedSoFar, 0); + + // Pick stable per-second rate const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; + const ratePerHour = ratePerSec * 3600; - if (ratePerSec > 0 && remainingJobs > 0) { - const secondsLeft = Math.round(remainingJobs / ratePerSec); + if (ratePerHour > 0 && remainingJobs > 0) { + // Formula: (estTotalJobs - (success + skipped + error)) / throughput per hour + const hoursLeft = remainingJobs / ratePerHour; + const secondsLeft = Math.max(1, Math.round(hoursLeft * 3600)); const eta = new Date(Date.now() + secondsLeft * 1000); const days = Math.floor(secondsLeft / 86400); // 24 * 3600 diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index abe43e69..35364f22 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -129,11 +129,6 @@ export async function interactivePreferenceUploaderFromPlan( >; const filtered = allEntries.filter(([userId]) => !successful[userId]); const fileTotal = filtered.length; - onProgress?.({ - successDelta: 0, - successTotal: uploadedCount, - fileTotal, - }); if (filtered.length === 0) { logger.warn( diff --git a/src/lib/pooling/keypressExtra.ts b/src/lib/pooling/keypressExtra.ts index 4840f899..85c46009 100644 --- a/src/lib/pooling/keypressExtra.ts +++ b/src/lib/pooling/keypressExtra.ts @@ -1,7 +1,12 @@ -import type { ExportManager } from '../../commands/consent/upload-preferences/artifacts'; +import { join } from 'node:path'; +import { + writeFailingUpdatesCsv, + type ExportManager, +} from '../../commands/consent/upload-preferences/artifacts'; import type { ExportStatusMap } from './logRotation'; import { showCombinedLogs } from './showCombinedLogs'; import type { SlotPaths } from './spawnWorkerProcess'; +import type { FailingUpdateRow } from '../../commands/consent/upload-preferences/receipts'; /** * Handles keypress events for extra functionalities in the CLI. @@ -12,10 +17,13 @@ import type { SlotPaths } from './spawnWorkerProcess'; export function makeOnKeypressExtra({ slotLogPaths, exportMgr, + failingUpdates, exportStatus, onRepaint, onPause, }: { + /** Rows that failed to update */ + failingUpdates: FailingUpdateRow[]; /** Map of worker IDs to their log paths */ slotLogPaths: SlotPaths; /** Export manager for handling export operations */ @@ -110,6 +118,19 @@ export function makeOnKeypressExtra({ } return; } + if (s === 'F') { + try { + const fPath = join(exportMgr.exportsDir, 'failing-updates.csv'); + writeFailingUpdatesCsv(failingUpdates, fPath); + process.stdout.write(`\nWrote failing updates CSV to: ${fPath}\n`); + noteExport('failuresCsv', fPath); + } catch (err) { + process.stdout.write( + `\nFailed to write failing updates CSV - ${err.stack}\n`, + ); + } + return; + } // back to dashboard if (s === '\x1b' || s === '\x1d') { From 54fdf05ba74e2425cf7e7fb1f73e650b54884368 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 02:52:51 -0700 Subject: [PATCH 38/72] Adds new commands --- .vscode/settings.json | 1 + src/commands/admin/chunk-csv/command.ts | 46 + src/commands/admin/chunk-csv/impl.ts | 198 + src/commands/admin/chunk-csv/readme.ts | 28 + src/commands/admin/routes.ts | 2 + .../receipts/receiptsState.ts | 62 +- .../consent/upload-preferences/runChild.ts | 2 +- transcend-yml-schema-v9.json | 53637 ++++++++++++++++ 8 files changed, 53964 insertions(+), 12 deletions(-) create mode 100644 src/commands/admin/chunk-csv/command.ts create mode 100644 src/commands/admin/chunk-csv/impl.ts create mode 100644 src/commands/admin/chunk-csv/readme.ts create mode 100644 transcend-yml-schema-v9.json diff --git a/.vscode/settings.json b/.vscode/settings.json index fbf0a61b..71259a7b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "Airgap", "Blocklist", "camelcase", + "chunker", "cli", "codomain", "compat", diff --git a/src/commands/admin/chunk-csv/command.ts b/src/commands/admin/chunk-csv/command.ts new file mode 100644 index 00000000..6ad3e6c6 --- /dev/null +++ b/src/commands/admin/chunk-csv/command.ts @@ -0,0 +1,46 @@ +import { buildCommand } from '@stricli/core'; + +export const chunkCsvCommand = buildCommand({ + loader: async () => { + const { chunkCsvImpl } = await import('./impl'); + return chunkCsvImpl; + }, + parameters: { + flags: { + inputFile: { + kind: 'parsed', + parse: String, + brief: 'Absolute or relative path to the large CSV file to split', + }, + outputDir: { + kind: 'parsed', + parse: String, + brief: + "Directory to write chunk files (defaults to the input file's directory)", + optional: true, + }, + chunkSizeMB: { + kind: 'parsed', + parse: (v: string) => { + const n = Number(v); + if (!Number.isFinite(n) || n <= 0) { + throw new Error('chunkSizeMB must be a positive number'); + } + return n; + }, + brief: + 'Approximate chunk size in megabytes. Keep well under JS string size limits. Default 10MB.', + optional: true, + }, + }, + }, + docs: { + brief: 'Chunk a large CSV into smaller CSV files', + fullDescription: `Chunks a large CSV file into smaller CSV files of approximately N MB each. + +Notes: +- The script streams the input CSV and writes out chunks incrementally to avoid high memory usage. +- You may still need to increase Node's memory limit for very large inputs. +- It validates row length consistency against the header row and logs periodic progress/memory usage.`, + }, +}); diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts new file mode 100644 index 00000000..99a39a27 --- /dev/null +++ b/src/commands/admin/chunk-csv/impl.ts @@ -0,0 +1,198 @@ +#!/usr/bin/env node + +import { Parser } from 'csv-parse'; +import { createReadStream } from 'node:fs'; +import { basename, dirname, join } from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { Transform } from 'node:stream'; +import colors from 'colors'; +import { logger } from '../../../logger'; +import { appendCsvSync, writeCsvSync } from '../../../lib/cron'; +import type { LocalContext } from '../../../context'; + +export interface ChunkCsvCommandFlags { + inputFile: string; + outputDir?: string; + chunkSizeMB?: number; +} + +/** Size of each chunk in bytes (need to stay WELL under JS string size limit of 512MB) */ +const DEFAULT_CHUNK_SIZE_BYTES = 10 * 1024 * 1024; + +/** + * Format memory usage for logging + * + * @param memoryData - The NodeJS memory usage data to format + * @returns A formatted string showing memory usage in MB + */ +function formatMemoryUsage(memoryData: NodeJS.MemoryUsage): string { + return Object.entries(memoryData) + .map(([key, value]) => `${key}: ${(value / 1024 / 1024).toFixed(2)} MB`) + .join(', '); +} + +/** + * Chunks a large CSV file into smaller files of approximately 1.5GB each + * Note that you may need to increase the node memory limit for this script to run!! + * + * Dev Usage: + * yarn ts-node ./src/cli-chunk-csv.ts --inputFile=./working/full_export.csv + * + * Standard usage: + * yarn tr-chunk-csv --inputFile=/path/to/large.csv --outputDir=/path/to/output + * + * @param this - The local context (not used here, but required by the CLI framework) + * @param flags - The command line flags containing input file, output directory, and chunk size + * @returns A promise that resolves when the chunking is complete + */ +export async function chunkCsvImpl( + this: LocalContext, + { + flags, + }: { + flags: ChunkCsvCommandFlags; + }, +): Promise { + const { inputFile, outputDir, chunkSizeMB } = flags; + + // Ensure inputFile is provided + if (!inputFile) { + logger.error( + colors.red( + 'An input file must be provided. You can specify using --inputFile=/path/to/large.csv', + ), + ); + process.exit(1); + } + + const CHUNK_SIZE = + typeof chunkSizeMB === 'number' && Number.isFinite(chunkSizeMB) + ? Math.floor(chunkSizeMB * 1024 * 1024) + : DEFAULT_CHUNK_SIZE_BYTES; + + const baseFileName = basename(inputFile, '.csv'); + const outputDirectory = outputDir || dirname(inputFile); + let currentChunkSize = 0; + let currentChunkNumber = 1; + let headerRow: string[] | null = null; + let currentOutputFile = join(outputDirectory, `${baseFileName}_chunk1.csv`); + let expectedColumnCount: number | null = null; + let totalLinesProcessed = 0; + + const parser = new Parser({ + columns: false, + skip_empty_lines: true, + }); + + const chunker = new Transform({ + objectMode: true, + /** + * Transform function that processes each chunk of CSV data + * + * @param chunk - Array of strings representing a CSV row + * @param _encoding - The encoding of the chunk + * @param callback - Callback function to signal completion + */ + transform(chunk: string[], _encoding, callback) { + if (!headerRow) { + headerRow = chunk; + expectedColumnCount = headerRow.length; + logger.info( + colors.blue( + `Found header row with ${expectedColumnCount} columns: ${headerRow.join( + ', ', + )}`, + ), + ); + callback(); + return; + } + + // Validate row structure + if ( + expectedColumnCount !== null && + chunk.length !== expectedColumnCount + ) { + logger.warn( + colors.yellow( + `Warning: Row ${totalLinesProcessed + 1} has ${ + chunk.length + } columns, expected ${expectedColumnCount}`, + ), + ); + } + + totalLinesProcessed += 1; + if (totalLinesProcessed % 1_000_000 === 0) { + const memoryUsage = formatMemoryUsage(process.memoryUsage()); + logger.info( + colors.blue( + `Processed ${totalLinesProcessed.toLocaleString()} lines... ` + + `Memory usage: ${memoryUsage}`, + ), + ); + } + + const rowSize = Buffer.byteLength(chunk.join(','), 'utf8'); + + // Prepare row object from header/values + const data = [ + { + ...Object.fromEntries( + headerRow.map((header, index) => [header, chunk[index]]), + ), + }, + ]; + + // If this is the first write of the current chunk, write with headers + if (currentChunkSize === 0) { + logger.info( + colors.yellow( + `Starting new chunk ${currentChunkNumber} at ${currentOutputFile}`, + ), + ); + writeCsvSync(currentOutputFile, data, headerRow); + currentChunkSize += rowSize; + } else { + appendCsvSync(currentOutputFile, data); + currentChunkSize += rowSize; + } + + // Determine if we need to start a new chunk + if (currentChunkSize >= CHUNK_SIZE) { + currentChunkNumber += 1; + currentChunkSize = 0; + currentOutputFile = join( + outputDirectory, + `${baseFileName}_chunk${currentChunkNumber}.csv`, + ); + } + + callback(); + }, + /** + * Flush function that writes the final chunk of data + * + * @param callback - Callback function to signal completion + */ + flush(callback) { + callback(); + }, + }); + + const readStream = createReadStream(inputFile); + + try { + logger.info(colors.blue(`Starting to process ${inputFile}...`)); + await pipeline(readStream, parser, chunker); + logger.info( + colors.green( + `Successfully chunked ${inputFile} into ${currentChunkNumber} files ` + + `(${totalLinesProcessed.toLocaleString()} total lines processed)`, + ), + ); + } catch (error) { + logger.error(colors.red('Error chunking CSV file:'), error); + process.exit(1); + } +} diff --git a/src/commands/admin/chunk-csv/readme.ts b/src/commands/admin/chunk-csv/readme.ts new file mode 100644 index 00000000..f384eb45 --- /dev/null +++ b/src/commands/admin/chunk-csv/readme.ts @@ -0,0 +1,28 @@ +import { buildExamples } from '../../../lib/docgen/buildExamples'; +import type { ChunkCsvCommandFlags } from './impl'; + +const examples = buildExamples( + ['admin', 'chunk-csv'], + [ + { + description: 'Chunk a file into smaller CSV files', + flags: { + inputFile: './working/full_export.csv', + outputDir: './working/chunks', + }, + }, + { + description: 'Specify chunk size in MB', + flags: { + inputFile: './working/full_export.csv', + outputDir: './working/chunks', + chunkSizeMB: 250, + }, + }, + ], +); + +export default `#### Examples + +${examples} +`; diff --git a/src/commands/admin/routes.ts b/src/commands/admin/routes.ts index 279d5a74..d9d7567b 100644 --- a/src/commands/admin/routes.ts +++ b/src/commands/admin/routes.ts @@ -1,9 +1,11 @@ import { buildRouteMap } from '@stricli/core'; import { generateApiKeysCommand } from './generate-api-keys/command'; +import { chunkCsvCommand } from './chunk-csv/command'; export const adminRoutes = buildRouteMap({ routes: { 'generate-api-keys': generateApiKeysCommand, + 'chunk-csv': chunkCsvCommand, }, docs: { brief: 'Admin commands', diff --git a/src/commands/consent/upload-preferences/receipts/receiptsState.ts b/src/commands/consent/upload-preferences/receipts/receiptsState.ts index a2508d5e..e5071a80 100644 --- a/src/commands/consent/upload-preferences/receipts/receiptsState.ts +++ b/src/commands/consent/upload-preferences/receipts/receiptsState.ts @@ -6,6 +6,10 @@ import { type PendingWithConflictPreferenceUpdates, type PreferenceUpdateMap, } from '../../../../lib/preference-management'; +import { + retrySamePromise, + type RetryPolicy, +} from '../../../../lib/helpers/retrySamePromise'; export type PreferenceReceiptsInterface = { /** Path to file */ @@ -51,22 +55,58 @@ export type PreferenceReceiptsInterface = { /** * Build a receipts state adapter for the given file path. * + * Retries creation of the underlying PersistedState with **exponential backoff** + * when the receipts file cannot be parsed due to a transient write (e.g., empty + * or partially written file) indicated by "Unexpected end of JSON input". + * * @param filepath - Where to persist/read upload receipts * @returns Receipt state port with strongly-named methods */ -export function makeReceiptsState( +export async function makeReceiptsState( filepath: string, -): PreferenceReceiptsInterface { +): Promise { + // Initial shape if file does not exist or is empty. + const initial = { + failingUpdates: {}, + pendingConflictUpdates: {}, + skippedUpdates: {}, + pendingSafeUpdates: {}, + successfulUpdates: {}, + pendingUpdates: {}, + lastFetchedAt: new Date().toISOString(), + } as const; + + // Retry policy: only retry on the specific JSON truncation message. + const policy: RetryPolicy = { + maxAttempts: 5, + delayMs: 50, // start small and backoff + shouldRetry: (_status, message) => + typeof message === 'string' && + /Unexpected end of JSON input/i.test(message ?? ''), + }; + + // Exponential backoff cap to avoid unbounded waits. + const MAX_DELAY_MS = 2_000; + try { - const s = new PersistedState(filepath, RequestUploadReceipts, { - failingUpdates: {}, - pendingConflictUpdates: {}, - skippedUpdates: {}, - pendingSafeUpdates: {}, - successfulUpdates: {}, - pendingUpdates: {}, - lastFetchedAt: new Date().toISOString(), - }); + const s = await retrySamePromise( + () => + // Wrap constructor in a Promise so thrown sync errors reject properly. + Promise.resolve( + new PersistedState(filepath, RequestUploadReceipts, initial), + ), + policy, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_note) => { + // Double the delay on each backoff (cap at MAX_DELAY_MS) + policy.delayMs = Math.min( + MAX_DELAY_MS, + Math.max(1, policy.delayMs * 2), + ); + // Optional local diagnostics: + // process.stderr.write(`[receiptsState] ${_note}; next delay=${policy.delayMs}ms\n`); + }, + ); return { receiptsFilepath: filepath, diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 52a9880c..50d3d73f 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -78,7 +78,7 @@ export async function runChild(): Promise { log(`START ${filePath}`); // Construct common state objects for the task - const receipts = makeReceiptsState(receiptFilepath); + const receipts = await makeReceiptsState(receiptFilepath); const schema = await makeSchemaState(options.schemaFile); const client = buildTranscendGraphQLClient( options.transcendUrl, diff --git a/transcend-yml-schema-v9.json b/transcend-yml-schema-v9.json new file mode 100644 index 00000000..ac30d6f7 --- /dev/null +++ b/transcend-yml-schema-v9.json @@ -0,0 +1,53637 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/transcend-io/cli/main/transcend-yml-schema-v9.json", + "title": "transcend.yml", + "description": "Define personal data schema and Transcend config as code with the Transcend CLI.", + "type": "object", + "properties": { + "action-items": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "type", + "collections" + ], + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "ACTION_MISSING_SILOS", + "API_KEY_STALE", + "ASSESSMENT_FORM_ASSIGNED", + "ASSESSMENT_FORM_NEEDS_REVIEW", + "REQUEST_DATA_SILO_MARKED_ACTIONABLE", + "REQUEST_DATA_SILO_FILES_NEED_REVIEW", + "COMMUNICATION_UNREAD", + "DATA_SILO_COMMUNICATION_UNREAD", + "DATA_POINT", + "DATA_POINT_MISSING_FIELDS", + "DATA_SILO_NEEDS_RECONNECT", + "DATA_SILO_NEEDS_RECONNECT_ASSIGNED", + "DATA_SILO_NOT_CONFIGURED", + "DATA_SILO_NOT_CONFIGURED_ASSIGNED", + "DATA_SILO_MISSING_IDENTIFIERS", + "LOOKUP_PROCESSES_WITH_ERRORS", + "DATA_SILOS_NEEDING_MANUAL_ENTRY", + "PROFILE_DATA_POINT_STATUS", + "REQUEST_EXPIRY", + "REQUEST_DATA_SILO_ERROR", + "REQUEST_ENRICHER_ERROR", + "REQUEST_IDENTIFIER_NEEDS_VERIFICATION", + "REQUEST_ENRICHER_PERSON_NEEDS_MANUAL_ENTRY", + "REQUEST_ACTIONABLE_STATUS", + "REQUEST_ON_HOLD", + "USER_AWAITING_NOTIFICATION", + "USER_NEEDS_CONFIGURATION", + "SOMBRA_VERSION_UPGRADE", + "SOMBRA_NEEDS_KEY_ROTATION", + "DATA_FLOW_NEEDS_REVIEW", + "DATA_FLOW_ASSIGNED_FOR_REVIEW", + "COOKIE_NEEDS_REVIEW", + "COOKIE_ASSIGNED_FOR_REVIEW", + "CONSENT_MANAGER_VERSION_UPGRADE", + "PLUGINS_WITH_ERRORS", + "ONBOARDING", + "REQUEST_ASSIGNED_TO_USER", + "DATA_SILO_NEEDS_TRAINING", + "BUSINESS_ENTITY_NEEDS_DOCUMENTATION", + "DATA_POINT_DATABASE_QUERY_NEEDS_APPROVAL" + ] + }, + "collections": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "properties": { + "priority": { + "type": "string", + "enum": [ + "WONT_DO", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ] + }, + "customerExperienceActionItemId": { + "type": "string" + }, + "dueDate": { + "type": "string" + }, + "resolved": { + "type": "boolean" + }, + "notes": { + "type": "string" + }, + "link": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "action-item-collections": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "productLine" + ], + "properties": { + "title": { + "type": "string" + }, + "productLine": { + "type": "string", + "enum": [ + "DSR_AUTOMATION", + "DATA_INVENTORY", + "CONSENT_MANAGEMENT", + "PRIVACY_CENTER", + "ADMINISTRATION", + "ASSESSMENTS", + "PATHFINDER", + "PREFERENCE_MANAGEMENT", + "PROMPT_MANAGER", + "CONTRACT_SCANNING", + "WEB_AUDITOR", + "SOMBRA", + "SILO_DISCOVERY", + "STRUCTURED_DISCOVERY", + "UNSTRUCTURED_DISCOVERY", + "DATA_LINEAGE" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "hidden": { + "type": "boolean" + } + } + } + ] + } + }, + "api-keys": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + } + }, + "teams": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "description" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "sso-department": { + "type": "string" + }, + "sso-group": { + "type": "string" + }, + "sso-title": { + "type": "string" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readOnly", + "fullAdmin", + "viewAllActionItems", + "manageAllActionItems", + "makeDataSubjectRequest", + "connectDataSilos", + "deployPrivacyCenter", + "manageConsentManager", + "viewConsentManager", + "manageConsentManagerDisplaySettings", + "manageConsentManagerDeveloperSettings", + "manageDataFlow", + "deployConsentManager", + "deployTestConsentManager", + "viewCustomerDataDataMapping", + "viewCustomerDataPrivacyRequests", + "manageAccessControl", + "manageApiKeys", + "manageBilling", + "manageDataMap", + "managedAssignedIntegrations", + "managedAssignedDataInventory", + "managedAssignedConsentManager", + "managedAssignedRequests", + "viewAssignedIntegrations", + "viewAssignedDataInventory", + "viewAssignedConsentManager", + "viewAssignedRequests", + "manageDataSubjectRequestSettings", + "manageEmailTemplates", + "manageOrganizationInfo", + "managePrivacyCenter", + "viewPrivacyCenter", + "manageLegalHold", + "manageRequestCompilation", + "manageRequestIdentities", + "manageRequestSecurity", + "manageSombraRootKeys", + "manageSSO", + "manageEmailDomains", + "requestApproval", + "viewApiKeys", + "viewDataFlow", + "viewDataMap", + "viewLegalHold", + "viewDataSubjectRequestSettings", + "viewEmailTemplates", + "viewEmailDomains", + "viewRequestCompilation", + "viewRequestIdentitySettings", + "viewRequests", + "viewScopes", + "viewSSO", + "viewEmployees", + "viewDataInventory", + "manageDataInventory", + "manageGlobalAttributes", + "viewGlobalAttributes", + "viewAssessments", + "manageAssessments", + "viewAssignedAssessments", + "manageAssignedAssessments", + "managePrompts", + "managePromptRuns", + "executePrompt", + "viewPromptRuns", + "viewPrompts", + "approvePrompts", + "manageAuditor", + "executeAuditor", + "viewCodeScanning", + "manageCodeScanning", + "viewPathfinder", + "managePathfinder", + "viewContractScanning", + "manageContractScanning", + "viewAuditorRuns", + "viewAuditEvents", + "manageActionItemCollections", + "viewManagedConsentDatabaseAdminApi", + "manageStoredPreferences", + "managePreferenceStoreSettings", + "viewPreferenceStoreSettings", + "managePolicies", + "viewPolicies", + "manageIntlMessages", + "viewIntlMessages", + "llmLogTransfer", + "manageWorkflows", + "viewDataSubCategories", + "manageDataSubCategories" + ] + } + } + } + } + ] + } + }, + "templates": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + } + }, + "enrichers": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "output-identifiers" + ], + "properties": { + "title": { + "type": "string" + }, + "output-identifiers": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "SOMBRA", + "email", + "phone", + "SERVER", + "PERSON", + "LEGAL_HOLD", + "AUTO_APPROVE", + "REGEX_MATCH", + "REGION_MATCH", + "DATABASE", + "LOOKER", + "WAIT_PERIOD", + "GOVERNMENT_ID", + "CUSTOM_FUNCTION" + ] + }, + "input-identifier": { + "type": "string" + }, + "testRegex": { + "type": "string" + }, + "lookerQueryTitle": { + "type": "string" + }, + "expirationDuration": { + "type": "number" + }, + "transitionRequestStatus": { + "type": "string", + "enum": [ + "CANCELED", + "ON_HOLD" + ] + }, + "phoneNumbers": { + "type": "array", + "items": { + "type": "string" + } + }, + "regionList": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW", + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + }, + "data-subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "headers": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "isSecret": { + "type": "boolean" + } + } + } + ] + } + }, + "privacy-actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "AUTOMATED_DECISION_MAKING_OPT_OUT", + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT", + "CONTACT_OPT_OUT", + "SALE_OPT_OUT", + "TRACKING_OPT_OUT", + "CUSTOM_OPT_OUT", + "AUTOMATED_DECISION_MAKING_OPT_IN", + "USE_OF_SENSITIVE_INFORMATION_OPT_IN", + "SALE_OPT_IN", + "TRACKING_OPT_IN", + "CONTACT_OPT_IN", + "CUSTOM_OPT_IN", + "ACCESS", + "ERASURE", + "RECTIFICATION", + "RESTRICTION", + "BUSINESS_PURPOSE", + "PLACE_ON_LEGAL_HOLD", + "REMOVE_FROM_LEGAL_HOLD" + ] + } + } + } + } + ] + } + }, + "attributes": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "MULTI_SELECT", + "SINGLE_SELECT", + "TEXT", + "EMAIL", + "TELEPHONE", + "URL", + "ASSESSMENT" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "actionItem", + "airgapCookie", + "airgapDataFlow", + "assessmentForm", + "assessmentGroup", + "auditorRun", + "auditorSchedule", + "businessEntity", + "dataSubCategory", + "dataPoint", + "dataPointLevel", + "dataSilo", + "enricher", + "identifier", + "legalHold", + "legalMatter", + "processingActivity", + "processingPurposeSubCategory", + "prompt", + "promptGroup", + "promptRun", + "request", + "scannedObject", + "scannedObjectPath", + "subject", + "subDataPoint", + "vendor" + ] + } + }, + "values": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "color": { + "type": "string" + } + } + } + ] + } + } + } + } + ] + } + }, + "business-entities": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "address": { + "type": "string" + }, + "headquarterCountry": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "headquarterSubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + }, + "dataProtectionOfficerName": { + "type": "string" + }, + "dataProtectionOfficerEmail": { + "type": "string" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "vendors": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "dataProcessingAgreementLink": { + "type": "string" + }, + "contactName": { + "type": "string" + }, + "contactPhone": { + "type": "string" + }, + "address": { + "type": "string" + }, + "headquarterCountry": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "headquarterSubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + }, + "websiteUrl": { + "type": "string" + }, + "businessEntity": { + "type": "string" + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "data-categories": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "category" + ], + "properties": { + "name": { + "type": "string" + }, + "category": { + "type": "string", + "enum": [ + "FINANCIAL", + "HEALTH", + "CONTACT", + "LOCATION", + "DEMOGRAPHIC", + "ID", + "ONLINE_ACTIVITY", + "USER_PROFILE", + "SOCIAL_MEDIA", + "CONNECTION", + "TRACKING", + "DEVICE", + "SURVEY", + "OTHER", + "UNSPECIFIED", + "NOT_PERSONAL_DATA", + "INTEGRATION_IDENTIFIER" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "processing-purposes": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "purpose" + ], + "properties": { + "name": { + "type": "string" + }, + "purpose": { + "type": "string", + "enum": [ + "ESSENTIAL", + "ADDITIONAL_FUNCTIONALITY", + "ADVERTISING", + "MARKETING", + "ANALYTICS", + "PERSONALIZATION", + "OPERATION_SECURITY", + "LEGAL", + "TRANSFER", + "SALE", + "HR", + "OTHER", + "UNSPECIFIED" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "data-subjects": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "active": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "adminDashboardDefaultSilentMode": { + "type": "boolean" + }, + "actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "AUTOMATED_DECISION_MAKING_OPT_OUT", + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT", + "CONTACT_OPT_OUT", + "SALE_OPT_OUT", + "TRACKING_OPT_OUT", + "CUSTOM_OPT_OUT", + "AUTOMATED_DECISION_MAKING_OPT_IN", + "USE_OF_SENSITIVE_INFORMATION_OPT_IN", + "SALE_OPT_IN", + "TRACKING_OPT_IN", + "CONTACT_OPT_IN", + "CUSTOM_OPT_IN", + "ACCESS", + "ERASURE", + "RECTIFICATION", + "RESTRICTION", + "BUSINESS_PURPOSE", + "PLACE_ON_LEGAL_HOLD", + "REMOVE_FROM_LEGAL_HOLD" + ] + } + } + } + } + ] + } + }, + "actions": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "AUTOMATED_DECISION_MAKING_OPT_OUT", + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT", + "CONTACT_OPT_OUT", + "SALE_OPT_OUT", + "TRACKING_OPT_OUT", + "CUSTOM_OPT_OUT", + "AUTOMATED_DECISION_MAKING_OPT_IN", + "USE_OF_SENSITIVE_INFORMATION_OPT_IN", + "SALE_OPT_IN", + "TRACKING_OPT_IN", + "CONTACT_OPT_IN", + "CUSTOM_OPT_IN", + "ACCESS", + "ERASURE", + "RECTIFICATION", + "RESTRICTION", + "BUSINESS_PURPOSE", + "PLACE_ON_LEGAL_HOLD", + "REMOVE_FROM_LEGAL_HOLD" + ] + } + } + }, + { + "type": "object", + "properties": { + "skipSecondaryIfNoFiles": { + "type": "boolean" + }, + "skipDownloadableStep": { + "type": "boolean" + }, + "requiresReview": { + "type": "boolean" + }, + "waitingPeriod": { + "type": "number" + }, + "regionDetectionMethod": { + "type": "string", + "enum": [ + "DISABLED", + "AUTO", + "FORM" + ] + }, + "regionList": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW", + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + }, + "regionBlockList": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW", + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + } + } + } + ] + } + }, + "identifiers": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "regex": { + "type": "string" + }, + "selectOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "privacyCenterVisibility": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "AUTOMATED_DECISION_MAKING_OPT_OUT", + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT", + "CONTACT_OPT_OUT", + "SALE_OPT_OUT", + "TRACKING_OPT_OUT", + "CUSTOM_OPT_OUT", + "AUTOMATED_DECISION_MAKING_OPT_IN", + "USE_OF_SENSITIVE_INFORMATION_OPT_IN", + "SALE_OPT_IN", + "TRACKING_OPT_IN", + "CONTACT_OPT_IN", + "CUSTOM_OPT_IN", + "ACCESS", + "ERASURE", + "RECTIFICATION", + "RESTRICTION", + "BUSINESS_PURPOSE", + "PLACE_ON_LEGAL_HOLD", + "REMOVE_FROM_LEGAL_HOLD" + ] + } + }, + "dataSubjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "isRequiredInForm": { + "type": "boolean" + }, + "placeholder": { + "type": "string" + }, + "displayTitle": { + "type": "string" + }, + "displayDescription": { + "type": "string" + }, + "displayOrder": { + "type": "number" + }, + "isUniqueOnPreferenceStore": { + "type": "boolean" + } + } + } + ] + } + }, + "data-silos": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "integrationName" + ], + "properties": { + "title": { + "type": "string" + }, + "integrationName": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "outer-type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "api-key-title": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "isSecret": { + "type": "boolean" + } + } + } + ] + } + }, + "data-subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "identity-keys": { + "type": "array", + "items": { + "type": "string" + } + }, + "deletion-dependencies": { + "type": "array", + "items": { + "type": "string" + } + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "disabled": { + "type": "boolean" + }, + "datapoints": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "path": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "data-collection-tag": { + "type": "string" + }, + "privacy-action-queries": { + "type": "object", + "properties": { + "ACCESS": { + "type": "string" + }, + "ERASURE": { + "type": "string" + }, + "RECTIFICATION": { + "type": "string" + }, + "RESTRICTION": { + "type": "string" + }, + "BUSINESS_PURPOSE": { + "type": "string" + }, + "PLACE_ON_LEGAL_HOLD": { + "type": "string" + }, + "REMOVE_FROM_LEGAL_HOLD": { + "type": "string" + }, + "AUTOMATED_DECISION_MAKING_OPT_OUT": { + "type": "string" + }, + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT": { + "type": "string" + }, + "CONTACT_OPT_OUT": { + "type": "string" + }, + "SALE_OPT_OUT": { + "type": "string" + }, + "TRACKING_OPT_OUT": { + "type": "string" + }, + "CUSTOM_OPT_OUT": { + "type": "string" + }, + "AUTOMATED_DECISION_MAKING_OPT_IN": { + "type": "string" + }, + "USE_OF_SENSITIVE_INFORMATION_OPT_IN": { + "type": "string" + }, + "SALE_OPT_IN": { + "type": "string" + }, + "TRACKING_OPT_IN": { + "type": "string" + }, + "CONTACT_OPT_IN": { + "type": "string" + }, + "CUSTOM_OPT_IN": { + "type": "string" + } + } + }, + "privacy-actions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCESS", + "ERASURE", + "RECTIFICATION", + "RESTRICTION", + "BUSINESS_PURPOSE", + "PLACE_ON_LEGAL_HOLD", + "REMOVE_FROM_LEGAL_HOLD", + "AUTOMATED_DECISION_MAKING_OPT_OUT", + "USE_OF_SENSITIVE_INFORMATION_OPT_OUT", + "CONTACT_OPT_OUT", + "SALE_OPT_OUT", + "TRACKING_OPT_OUT", + "CUSTOM_OPT_OUT", + "AUTOMATED_DECISION_MAKING_OPT_IN", + "USE_OF_SENSITIVE_INFORMATION_OPT_IN", + "SALE_OPT_IN", + "TRACKING_OPT_IN", + "CONTACT_OPT_IN", + "CUSTOM_OPT_IN" + ] + } + }, + "fields": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "purposes": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "purpose" + ], + "properties": { + "purpose": { + "type": "string", + "enum": [ + "ESSENTIAL", + "ADDITIONAL_FUNCTIONALITY", + "ADVERTISING", + "MARKETING", + "ANALYTICS", + "PERSONALIZATION", + "OPERATION_SECURITY", + "LEGAL", + "TRANSFER", + "SALE", + "HR", + "OTHER", + "UNSPECIFIED" + ] + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + } + }, + "categories": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "category" + ], + "properties": { + "category": { + "type": "string", + "enum": [ + "FINANCIAL", + "HEALTH", + "CONTACT", + "LOCATION", + "DEMOGRAPHIC", + "ID", + "ONLINE_ACTIVITY", + "USER_PROFILE", + "SOCIAL_MEDIA", + "CONNECTION", + "TRACKING", + "DEVICE", + "SURVEY", + "OTHER", + "UNSPECIFIED", + "NOT_PERSONAL_DATA", + "INTEGRATION_IDENTIFIER" + ] + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + } + }, + "guessed-categories": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "category", + "status", + "confidence" + ], + "properties": { + "category": { + "allOf": [ + { + "type": "object", + "required": [ + "category" + ], + "properties": { + "category": { + "type": "string", + "enum": [ + "FINANCIAL", + "HEALTH", + "CONTACT", + "LOCATION", + "DEMOGRAPHIC", + "ID", + "ONLINE_ACTIVITY", + "USER_PROFILE", + "SOCIAL_MEDIA", + "CONNECTION", + "TRACKING", + "DEVICE", + "SURVEY", + "OTHER", + "UNSPECIFIED", + "NOT_PERSONAL_DATA", + "INTEGRATION_IDENTIFIER" + ] + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "PENDING", + "REJECTED" + ] + }, + "confidence": { + "type": "number" + } + } + }, + { + "type": "object", + "properties": { + "classifierVersion": { + "type": "number" + } + } + } + ] + } + }, + "access-request-visibility-enabled": { + "type": "boolean" + }, + "erasure-request-redaction-enabled": { + "type": "boolean" + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "email-settings": { + "type": "object", + "properties": { + "notify-email-address": { + "type": "string" + }, + "send-frequency": { + "type": "number" + }, + "send-type": { + "type": "string", + "enum": [ + "PER_DSR", + "CROSS_DSR" + ] + }, + "include-identifiers-attachment": { + "type": "boolean" + }, + "completion-link-type": { + "type": "string", + "enum": [ + "LOGGED_IN_USER", + "UNAUTHENTICATED_EXTERNAL_USER", + "NO_LINK_MARK_DATAPOINT_AS_RESOLVED" + ] + }, + "manual-work-retry-frequency": { + "type": "string" + } + } + }, + "country": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "countrySubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "businessEntityTitles": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "data-flows": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "value", + "type" + ], + "properties": { + "value": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "HOST", + "PATH", + "QUERY_PARAM", + "REGEX", + "CSP" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "trackingPurposes": { + "type": "array", + "items": { + "type": "string" + } + }, + "service": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "LIVE", + "NEEDS_REVIEW" + ] + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "cookies": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "isRegex": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "trackingPurposes": { + "type": "array", + "items": { + "type": "string" + } + }, + "service": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "LIVE", + "NEEDS_REVIEW" + ] + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "consent-manager": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "bundleUrls": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + }, + "partition": { + "type": "string" + }, + "consentPrecedence": { + "type": "string", + "enum": [ + "user", + "signal" + ] + }, + "unknownRequestPolicy": { + "type": "string", + "enum": [ + "ALLOW", + "REQUIRE_FULL_CONSENT", + "BLOCK" + ] + }, + "unknownCookiePolicy": { + "type": "string", + "enum": [ + "ALLOW", + "REQUIRE_FULL_CONSENT", + "BLOCK" + ] + }, + "syncEndpoint": { + "type": "string" + }, + "telemetryPartitioning": { + "type": "string", + "enum": [ + "origin", + "path", + "url" + ] + }, + "signedIabAgreement": { + "type": "string", + "enum": [ + "yes", + "no", + "unknown" + ] + }, + "experiences": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "regions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "countrySubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + } + } + }, + "onConsentExpiry": { + "type": "string", + "enum": [ + "Prompt", + "ResetAll", + "ResetOptIns" + ] + }, + "consentExpiry": { + "type": "number" + }, + "operator": { + "type": "string", + "enum": [ + "IN", + "NOT_IN" + ] + }, + "displayPriority": { + "type": "number" + }, + "viewState": { + "type": "string", + "enum": [ + "QuickOptions", + "QuickOptions3", + "AcceptAll", + "AcceptAllRejectAllToggle", + "AcceptAllOrMoreChoices", + "AcceptOrRejectAll", + "AcceptOrRejectAllOrMoreChoices", + "AcceptOrRejectAnalytics", + "AcceptOrRejectAdvertising", + "NoticeAndDoNotSell", + "DoNotSellExplainer", + "CompleteOptionsToggles", + "PrivacyPolicyNotice", + "PrivacyPolicyNoticeWithCloseButton", + "CompleteOptions", + "CompleteOptionsInverted", + "Hidden", + "TCF_EU" + ] + }, + "purposes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "trackingType" + ], + "properties": { + "trackingType": { + "type": "string" + } + } + } + }, + "optedOutPurposes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "trackingType" + ], + "properties": { + "trackingType": { + "type": "string" + } + } + } + }, + "browserLanguages": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "om", + "ab", + "aa", + "af", + "sq", + "am", + "ar-DZ", + "ar-BH", + "ar-EG", + "ar-IQ", + "ar-JO", + "ar-KW", + "ar-LB", + "ar-LY", + "ar-MA", + "ar-OM", + "ar-QA", + "ar-SA", + "ar-SY", + "ar-TN", + "ar-AE", + "ar-YE", + "ar", + "hy", + "as", + "ay", + "az", + "ba", + "eu", + "be", + "bn", + "dz", + "bh", + "bi", + "br", + "bg-BG", + "bg", + "my", + "km", + "ca", + "zh-CN", + "zh-HK", + "zh-MO", + "zh-SG", + "zh-TW", + "zh", + "co", + "hr-HR", + "hr", + "cs-CZ", + "cs", + "da-DK", + "da", + "div", + "nl-BE", + "nl-NL", + "nl", + "en-AU", + "en-BZ", + "en-CA", + "en-IE", + "en-JM", + "en-NZ", + "en-PH", + "en-ZA", + "en-CH", + "en-TT", + "en-GB", + "en-US", + "en-ZW", + "us", + "eo", + "et", + "et-EE", + "fo", + "fa", + "fj", + "fi-FI", + "fi", + "fr-BE", + "fr-CA", + "fr-FR", + "fr-LU", + "fr-MC", + "fr-CH", + "fr", + "fy", + "mk", + "gd", + "gl", + "ka", + "de-AT", + "de-DE", + "de-LI", + "de-LU", + "de-CH", + "de", + "el-GR", + "el", + "kl", + "gn", + "gu", + "ha", + "iw", + "hi", + "hu-HU", + "hu", + "is", + "is-IS", + "in", + "ia", + "ie", + "ik", + "ga", + "ga-IE", + "it-IT", + "it-CH", + "it", + "ja", + "jw", + "kn", + "ks", + "kk", + "rw", + "ky", + "rn", + "kok", + "ko", + "ku", + "kz", + "lo", + "la", + "lv", + "lv-LV", + "ln", + "lt-LT", + "lt", + "mg", + "ms", + "ml", + "mt", + "mt-MT", + "mi", + "mr", + "mo", + "mn", + "na", + "ne", + "no", + "nb", + "nn", + "no-NO", + "nb-NO", + "nn-NO", + "oc", + "or", + "ps", + "pl-PL", + "pl", + "pt-BR", + "pt-PT", + "pt-CH", + "pt", + "pa", + "qu", + "rm", + "ro-MD", + "ro-RO", + "ro", + "ru-MD", + "ru", + "sm", + "sg", + "sa", + "sr", + "sh", + "st", + "sn", + "sd", + "si", + "ss", + "sk", + "sk-SK", + "sl", + "sl-SI", + "so", + "sb", + "es-AR", + "es-BO", + "es-CL", + "es-CO", + "es-CR", + "es-DO", + "es-EC", + "es-SV", + "es-ES", + "es-GT", + "es-HN", + "es-MX", + "es-NI", + "es-PA", + "es-PY", + "es-PE", + "es-PR", + "es-US", + "es-UY", + "es-VE", + "es", + "su", + "sx", + "sw", + "sv-FI", + "sv-SE", + "sv", + "gsw", + "gsw-LI", + "gsw-CH", + "syr", + "tl", + "tg", + "ta", + "tt", + "te", + "th", + "bo", + "ti", + "to", + "ts", + "tn", + "tr", + "tk", + "tw", + "uk", + "ur", + "uz", + "vi", + "vo", + "cy", + "wo", + "xh", + "yi", + "yo", + "zu" + ] + } + }, + "browserTimeZones": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/La_Rioja", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Fort_Nelson", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Monticello", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Colombo", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Hebron", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ulaanbaatar", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/Perth", + "Australia/Sydney", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis" + ] + } + } + } + } + ] + } + }, + "theme": { + "type": "object", + "properties": { + "primaryColor": { + "type": "string" + }, + "fontColor": { + "type": "string" + }, + "privacyPolicy": { + "type": "string" + }, + "prompt": { + "type": "number" + } + } + }, + "syncGroups": { + "type": "string" + } + } + }, + "prompts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "content" + ], + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + } + } + } + }, + "prompt-partials": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "content" + ], + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + } + } + } + }, + "prompt-groups": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "description", + "prompts" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "prompts": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "agents": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "instructions", + "agentId", + "codeInterpreterEnabled", + "retrievalEnabled", + "large-language-model" + ], + "properties": { + "name": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "agentId": { + "type": "string" + }, + "codeInterpreterEnabled": { + "type": "boolean" + }, + "retrievalEnabled": { + "type": "boolean" + }, + "large-language-model": { + "type": "object", + "required": [ + "name", + "client" + ], + "properties": { + "name": { + "type": "string" + }, + "client": { + "type": "string", + "enum": [ + "openai", + "claude", + "llama" + ] + } + } + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "prompt": { + "type": "string" + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "teams": { + "type": "array", + "items": { + "type": "string" + } + }, + "agent-functions": { + "type": "array", + "items": { + "type": "string" + } + }, + "agent-files": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "agent-functions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "description", + "parameters" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "parameters": { + "type": "string" + } + } + } + }, + "agent-files": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "fileId", + "size", + "purpose" + ], + "properties": { + "name": { + "type": "string" + }, + "fileId": { + "type": "string" + }, + "size": { + "type": "number" + }, + "purpose": { + "type": "string", + "enum": [ + "ASSISTANTS", + "FINE_TUNE", + "FINE_TUNE_RESULTS", + "ASSISTANTS_OUTPUT" + ] + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + } + } + } + ] + } + }, + "privacy-center": { + "type": "object", + "properties": { + "isDisabled": { + "type": "boolean" + }, + "showPrivacyRequestButton": { + "type": "boolean" + }, + "showDataPractices": { + "type": "boolean" + }, + "showPolicies": { + "type": "boolean" + }, + "showTrackingTechnologies": { + "type": "boolean" + }, + "showCookies": { + "type": "boolean" + }, + "showDataFlows": { + "type": "boolean" + }, + "showConsentManager": { + "type": "boolean" + }, + "showManageYourPrivacy": { + "type": "boolean" + }, + "showMarketingPreferences": { + "type": "boolean" + }, + "locales": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "en", + "ar", + "fr", + "es", + "de", + "it", + "ja", + "ru", + "ar-AE", + "fr-FR", + "de-DE", + "de-AT", + "de-CH", + "it-IT", + "it-CH", + "af-ZA", + "bg-BG", + "zh-CN", + "hr-HR", + "cs-CZ", + "da-DK", + "en-GB", + "en-CA", + "en-AE", + "fi-FI", + "el-GR", + "hi-IN", + "hu-HU", + "id-ID", + "ja-JP", + "ko-KR", + "lt-LT", + "ms-MY", + "mr-IN", + "nb-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + "ru-RU", + "sr-Latn-RS", + "sv-SE", + "ta-IN", + "th-TH", + "tr-TR", + "uk-UA", + "vi-VN", + "zu-ZA", + "en-US", + "en-AU", + "fr-BE", + "fr-CA", + "fr-CH", + "en-IE", + "nl-NL", + "nl-BE", + "es-ES", + "es-419", + "zh-HK", + "he-IL", + "en-NZ", + "et-EE", + "is-IS", + "lv-LV", + "mt-MT", + "sk-SK", + "sl-SL" + ] + } + }, + "defaultLocale": { + "type": "string", + "enum": [ + "en", + "ar", + "fr", + "es", + "de", + "it", + "ja", + "ru", + "ar-AE", + "fr-FR", + "de-DE", + "de-AT", + "de-CH", + "it-IT", + "it-CH", + "af-ZA", + "bg-BG", + "zh-CN", + "hr-HR", + "cs-CZ", + "da-DK", + "en-GB", + "en-CA", + "en-AE", + "fi-FI", + "el-GR", + "hi-IN", + "hu-HU", + "id-ID", + "ja-JP", + "ko-KR", + "lt-LT", + "ms-MY", + "mr-IN", + "nb-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + "ru-RU", + "sr-Latn-RS", + "sv-SE", + "ta-IN", + "th-TH", + "tr-TR", + "uk-UA", + "vi-VN", + "zu-ZA", + "en-US", + "en-AU", + "fr-BE", + "fr-CA", + "fr-CH", + "en-IE", + "nl-NL", + "nl-BE", + "es-ES", + "es-419", + "zh-HK", + "he-IL", + "en-NZ", + "et-EE", + "is-IS", + "lv-LV", + "mt-MT", + "sk-SK", + "sl-SL" + ] + }, + "preferBrowserDefaultLocale": { + "type": "boolean" + }, + "supportEmail": { + "type": "string" + }, + "replyToEmail": { + "type": "string" + }, + "useNoReplyEmailAddress": { + "type": "boolean" + }, + "useCustomEmailDomain": { + "type": "boolean" + }, + "transformAccessReportJsonToCsv": { + "type": "boolean" + }, + "theme": { + "type": "object", + "properties": { + "colors": { + "type": "object", + "properties": { + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "sidebarNavBg": { + "type": "string" + }, + "heroBg": { + "type": "string" + }, + "widgetBg": { + "type": "string" + }, + "textOnBg": { + "type": "string" + }, + "textLightOnBg": { + "type": "string" + }, + "textOnPrimary": { + "type": "string" + }, + "textOnSidebar": { + "type": "string" + }, + "accentOnSidebar": { + "type": "string" + }, + "textOnHero": { + "type": "string" + }, + "textOnAboutTranscend": { + "type": "string" + }, + "highlight": { + "type": "string" + }, + "tableOutline": { + "type": "string" + }, + "bgAccent": { + "type": "string" + }, + "error": { + "type": "string" + } + } + }, + "componentStyles": { + "type": "object", + "properties": { + "h1": { + "type": "string" + }, + "h2": { + "type": "string" + }, + "h3": { + "type": "string" + }, + "body": { + "type": "string" + }, + "sideMenuText": { + "type": "string" + }, + "primaryButton": { + "type": "string" + }, + "secondaryButton": { + "type": "string" + }, + "emailButton": { + "type": "string" + }, + "emailContainer": { + "type": "string" + }, + "hero": { + "type": "string" + }, + "heroBackground": { + "type": "string" + }, + "takeControlConfirmation": { + "type": "string" + } + } + }, + "textStyles": { + "type": "object", + "properties": { + "fontFamilyHeader": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "assets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + } + } + } + ] + }, + "fontFamilyBody": { + "allOf": [ + { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "assets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + } + } + } + ] + } + } + } + } + } + } + }, + "policies": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "effectiveOn": { + "type": "string" + }, + "disableEffectiveOn": { + "type": "boolean" + }, + "content": { + "type": "string" + }, + "disabledLocales": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "en", + "ar", + "fr", + "es", + "de", + "it", + "ja", + "ru", + "ar-AE", + "fr-FR", + "de-DE", + "de-AT", + "de-CH", + "it-IT", + "it-CH", + "af-ZA", + "bg-BG", + "zh-CN", + "hr-HR", + "cs-CZ", + "da-DK", + "en-GB", + "en-CA", + "en-AE", + "fi-FI", + "el-GR", + "hi-IN", + "hu-HU", + "id-ID", + "ja-JP", + "ko-KR", + "lt-LT", + "ms-MY", + "mr-IN", + "nb-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + "ru-RU", + "sr-Latn-RS", + "sv-SE", + "ta-IN", + "th-TH", + "tr-TR", + "uk-UA", + "vi-VN", + "zu-ZA", + "en-US", + "en-AU", + "fr-BE", + "fr-CA", + "fr-CH", + "en-IE", + "nl-NL", + "nl-BE", + "es-ES", + "es-419", + "zh-HK", + "he-IL", + "en-NZ", + "et-EE", + "is-IS", + "lv-LV", + "mt-MT", + "sk-SK", + "sl-SL" + ] + } + } + } + } + ] + } + }, + "messages": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "targetReactIntlId": { + "type": "string" + }, + "defaultMessage": { + "type": "string" + }, + "translations": { + "type": "object", + "properties": { + "en": { + "type": "string" + }, + "ar": { + "type": "string" + }, + "fr": { + "type": "string" + }, + "es": { + "type": "string" + }, + "de": { + "type": "string" + }, + "it": { + "type": "string" + }, + "ja": { + "type": "string" + }, + "ru": { + "type": "string" + }, + "ar-AE": { + "type": "string" + }, + "fr-FR": { + "type": "string" + }, + "de-DE": { + "type": "string" + }, + "de-AT": { + "type": "string" + }, + "de-CH": { + "type": "string" + }, + "it-IT": { + "type": "string" + }, + "it-CH": { + "type": "string" + }, + "af-ZA": { + "type": "string" + }, + "bg-BG": { + "type": "string" + }, + "zh-CN": { + "type": "string" + }, + "hr-HR": { + "type": "string" + }, + "cs-CZ": { + "type": "string" + }, + "da-DK": { + "type": "string" + }, + "en-GB": { + "type": "string" + }, + "en-CA": { + "type": "string" + }, + "en-AE": { + "type": "string" + }, + "fi-FI": { + "type": "string" + }, + "el-GR": { + "type": "string" + }, + "hi-IN": { + "type": "string" + }, + "hu-HU": { + "type": "string" + }, + "id-ID": { + "type": "string" + }, + "ja-JP": { + "type": "string" + }, + "ko-KR": { + "type": "string" + }, + "lt-LT": { + "type": "string" + }, + "ms-MY": { + "type": "string" + }, + "mr-IN": { + "type": "string" + }, + "nb-NO": { + "type": "string" + }, + "pl-PL": { + "type": "string" + }, + "pt-BR": { + "type": "string" + }, + "pt-PT": { + "type": "string" + }, + "ro-RO": { + "type": "string" + }, + "ru-RU": { + "type": "string" + }, + "sr-Latn-RS": { + "type": "string" + }, + "sv-SE": { + "type": "string" + }, + "ta-IN": { + "type": "string" + }, + "th-TH": { + "type": "string" + }, + "tr-TR": { + "type": "string" + }, + "uk-UA": { + "type": "string" + }, + "vi-VN": { + "type": "string" + }, + "zu-ZA": { + "type": "string" + }, + "en-US": { + "type": "string" + }, + "en-AU": { + "type": "string" + }, + "fr-BE": { + "type": "string" + }, + "fr-CA": { + "type": "string" + }, + "fr-CH": { + "type": "string" + }, + "en-IE": { + "type": "string" + }, + "nl-NL": { + "type": "string" + }, + "nl-BE": { + "type": "string" + }, + "es-ES": { + "type": "string" + }, + "es-419": { + "type": "string" + }, + "zh-HK": { + "type": "string" + }, + "he-IL": { + "type": "string" + }, + "en-NZ": { + "type": "string" + }, + "et-EE": { + "type": "string" + }, + "is-IS": { + "type": "string" + }, + "lv-LV": { + "type": "string" + }, + "mt-MT": { + "type": "string" + }, + "sk-SK": { + "type": "string" + }, + "sl-SL": { + "type": "string" + } + } + } + } + } + ] + } + }, + "partitions": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "partition": { + "type": "string" + } + } + } + ] + } + }, + "assessment-templates": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "sections": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "questions" + ], + "properties": { + "title": { + "type": "string" + }, + "questions": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "type" + ], + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_ANSWER_TEXT", + "SHORT_ANSWER_TEXT", + "SINGLE_SELECT", + "MULTI_SELECT", + "FILE", + "DESCRIPTION" + ] + } + } + }, + { + "type": "object", + "properties": { + "sub-type": { + "type": "string", + "enum": [ + "NONE", + "CUSTOM", + "USER", + "TEAM", + "DATA_SUB_CATEGORY", + "PROCESSING_PURPOSE_SUB_CATEGORY", + "PROCESSING_ACTIVITY", + "VENDOR", + "REGION", + "BUSINESS_ENTITY", + "SAA_S_CATEGORY", + "DATA_PROCESSING_AGREEMENT_STATUS", + "DATA_PROTECTION_IMPACT_ASSESSMENT_STATUS", + "DEPRECATION_STATE", + "IDENTIFIER", + "DATA_SILO", + "RECOMMENDED_FOR_CONSENT", + "RECOMMENDED_FOR_PRIVACY", + "SUBJECT", + "RETENTION_TYPE", + "CONTROLLERSHIP", + "HAS_PERSONAL_DATA", + "ATTRIBUTE_KEY" + ] + }, + "placeholder": { + "type": "string" + }, + "description": { + "type": "string" + }, + "is-required": { + "type": "boolean" + }, + "reference-id": { + "type": "string" + }, + "display-logic": { + "allOf": [ + { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "SHOW", + "SKIP" + ] + } + } + }, + { + "type": "object", + "properties": { + "rule": { + "allOf": [ + { + "type": "object", + "required": [ + "depends-on-question-reference-id", + "comparison-operator" + ], + "properties": { + "depends-on-question-reference-id": { + "type": "string" + }, + "comparison-operator": { + "type": "string", + "enum": [ + "IS_EQUAL_TO", + "IS_NOT_EQUAL_TO", + "IS_ONE_OF", + "IS_NOT_ONE_OF", + "CONTAINS", + "IS_SHOWN", + "IS_NOT_SHOWN" + ] + } + } + }, + { + "type": "object", + "properties": { + "comparison-operands": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + }, + "nested-rule": {} + } + } + ] + }, + "risk-logic": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "comparison-operands", + "comparison-operator" + ], + "properties": { + "comparison-operands": { + "type": "array", + "items": { + "type": "string" + } + }, + "comparison-operator": { + "type": "string", + "enum": [ + "IS_EQUAL_TO", + "IS_NOT_EQUAL_TO", + "IS_ONE_OF", + "IS_NOT_ONE_OF", + "CONTAINS", + "IS_SHOWN", + "IS_NOT_SHOWN" + ] + } + } + }, + { + "type": "object", + "properties": { + "risk-level": { + "type": "string" + }, + "risk-matrix-column": { + "type": "string" + }, + "risk-matrix-row": { + "type": "string" + } + } + } + ] + } + }, + "risk-categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "risk-framework": { + "type": "string" + }, + "answer-options": { + "type": "array", + "items": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + } + }, + "selected-answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed-mime-types": { + "type": "array", + "items": { + "type": "string" + } + }, + "allow-select-other": { + "type": "boolean" + }, + "sync-model": { + "type": "string", + "enum": [ + "dataSilo", + "subDataPoint", + "vendor", + "dataSubCategory", + "processingPurposeSubCategory", + "businessEntity", + "processingActivity" + ] + }, + "sync-column": { + "type": "string", + "enum": [ + "TITLE", + "SLUG", + "DESCRIPTION", + "HEADQUARTER_COUNTRY", + "CONNECTION_STATE", + "CONNECT_START_TIME", + "CONTACT_EMAIL", + "CONTACT_NAME", + "COUNTRY", + "COUNTRY_SUB_DIVISION", + "CREDENTIALS_REFRESH_SCHEDULED_AT", + "DATA_PROCESSING_AGREEMENT_LINK", + "DATA_PROCESSING_AGREEMENT_STATUS", + "DATA_RETENTION_NOTE", + "DEFAULT_ACCESS_REQUEST_VISIBILITY", + "DELETED_AT", + "DEPRECATION_STATE", + "EMAIL_SENDING_LOCK", + "EXPIRED_AT", + "EXTERNAL_ID", + "HAS_PERSONAL_DATA", + "HEADERS", + "IS_LIVE", + "LAST_CONNECTED_AT", + "LAST_ENABLED_AT", + "LAST_LOOKUP_PROCESS_CREATION_TIME", + "MANUAL_WORK_RETRY_FREQUENCY", + "MANUAL_WORK_RETRY_START_AT", + "NOTES", + "NOTIFY_EMAIL_ADDRESS", + "NOTIFY_WEBHOOK_URL", + "OUTER_TYPE", + "PLAINTEXT_CONTEXT", + "PROMPT_A_VENDOR_EMAIL_COMPLETION_LINK_TYPE", + "PROMPT_A_VENDOR_EMAIL_INCLUDE_IDENTIFIERS_ATTACHMENT", + "PROMPT_A_VENDOR_EMAIL_SCHEDULED_AT", + "PROMPT_A_VENDOR_EMAIL_SEND_FREQUENCY", + "PROMPT_A_VENDOR_EMAIL_SEND_TYPE", + "PROMPT_A_VENDOR_EMAIL_START_AT", + "RECOMMENDED_FOR_CONSENT", + "RECOMMENDED_FOR_PRIVACY", + "RECONNECT_FORM_ITEMS", + "SAAS_CONTEXT", + "SECRET_HEADERS", + "SUBDOMAIN", + "TYPE", + "URL", + "WEBSITE_URL", + "TRANSFER_REGIONS", + "CONTROLLERSHIPS", + "NAME", + "DATA_POINT_ID", + "ENCRYPTED_SAMPLES_S3_KEY", + "ACCESS_REQUESTED_VISIBILITY_ENABLED", + "ENSURE_REQUEST_REDACTION_ENABLED", + "LAST_CLASSIFIED_AT", + "LAST_RUN_AT", + "ERROR", + "ERROR_COUNT", + "CONTEXT", + "CONTENT_CLASSIFICATION_STATUS", + "NON_NULL_CHECK_COMPLETED", + "CONTROLLERSHIP", + "RETENTION_TYPE", + "RETENTION_PERIOD", + "SCAN_RUN_ID", + "CONTACT_PHONE", + "ADDRESS", + "HEADQUARTER_SUB_DIVISION", + "PRIVACY_POLICY_URL", + "CATEGORY", + "IS_DEFAULT", + "REGEX", + "PURPOSE", + "DATA_PROTECTION_OFFICER_EMAIL", + "DATA_PROTECTION_OFFICER_NAME", + "DATA_TYPE", + "ENCRYPTION", + "IS_PRIMARY_KEY", + "CUSTOM_SILO_CONNECTION_STRATEGY", + "SECURITY_MEASURE_DETAILS", + "STORAGE_REGIONS", + "DATA_PROTECTION_IMPACT_ASSESSMENT_LINK", + "DATA_PROTECTION_IMPACT_ASSESSMENT_STATUS" + ] + }, + "attribute-key": { + "type": "string" + }, + "require-risk-evaluation": { + "type": "boolean" + }, + "require-risk-matrix-evaluation": { + "type": "boolean" + } + } + } + ] + } + } + } + }, + { + "type": "object", + "properties": { + "assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "external-assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "is-reviewed": { + "type": "boolean" + } + } + } + ] + } + }, + "description": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "PUBLISHED" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "DATA_INVENTORY", + "IMPORT" + ] + }, + "creator": { + "type": "string" + }, + "locked": { + "type": "boolean" + }, + "parent-id": { + "type": "string" + }, + "archived": { + "type": "boolean" + }, + "created-at": { + "type": "string" + }, + "attribute-keys": { + "type": "array", + "items": { + "type": "string" + } + }, + "retention-schedule": { + "type": "object", + "required": [ + "type", + "duration-days", + "operand" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "REQUESTS_CREATED_AT", + "ASSESSMENT_FORM_COMPLETED_AT" + ] + }, + "duration-days": { + "type": "number" + }, + "operand": { + "type": "string", + "enum": [ + "FULL_DELETE", + "PARTIAL_DELETE", + "NONE" + ] + } + } + }, + "templates": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "assessments": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "group" + ], + "properties": { + "title": { + "type": "string" + }, + "group": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "sections": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "questions" + ], + "properties": { + "title": { + "type": "string" + }, + "questions": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title", + "type" + ], + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "LONG_ANSWER_TEXT", + "SHORT_ANSWER_TEXT", + "SINGLE_SELECT", + "MULTI_SELECT", + "FILE", + "DESCRIPTION" + ] + } + } + }, + { + "type": "object", + "properties": { + "sub-type": { + "type": "string", + "enum": [ + "NONE", + "CUSTOM", + "USER", + "TEAM", + "DATA_SUB_CATEGORY", + "PROCESSING_PURPOSE_SUB_CATEGORY", + "PROCESSING_ACTIVITY", + "VENDOR", + "REGION", + "BUSINESS_ENTITY", + "SAA_S_CATEGORY", + "DATA_PROCESSING_AGREEMENT_STATUS", + "DATA_PROTECTION_IMPACT_ASSESSMENT_STATUS", + "DEPRECATION_STATE", + "IDENTIFIER", + "DATA_SILO", + "RECOMMENDED_FOR_CONSENT", + "RECOMMENDED_FOR_PRIVACY", + "SUBJECT", + "RETENTION_TYPE", + "CONTROLLERSHIP", + "HAS_PERSONAL_DATA", + "ATTRIBUTE_KEY" + ] + }, + "placeholder": { + "type": "string" + }, + "description": { + "type": "string" + }, + "is-required": { + "type": "boolean" + }, + "reference-id": { + "type": "string" + }, + "display-logic": { + "allOf": [ + { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "SHOW", + "SKIP" + ] + } + } + }, + { + "type": "object", + "properties": { + "rule": { + "allOf": [ + { + "type": "object", + "required": [ + "depends-on-question-reference-id", + "comparison-operator" + ], + "properties": { + "depends-on-question-reference-id": { + "type": "string" + }, + "comparison-operator": { + "type": "string", + "enum": [ + "IS_EQUAL_TO", + "IS_NOT_EQUAL_TO", + "IS_ONE_OF", + "IS_NOT_ONE_OF", + "CONTAINS", + "IS_SHOWN", + "IS_NOT_SHOWN" + ] + } + } + }, + { + "type": "object", + "properties": { + "comparison-operands": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + }, + "nested-rule": {} + } + } + ] + }, + "risk-logic": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "comparison-operands", + "comparison-operator" + ], + "properties": { + "comparison-operands": { + "type": "array", + "items": { + "type": "string" + } + }, + "comparison-operator": { + "type": "string", + "enum": [ + "IS_EQUAL_TO", + "IS_NOT_EQUAL_TO", + "IS_ONE_OF", + "IS_NOT_ONE_OF", + "CONTAINS", + "IS_SHOWN", + "IS_NOT_SHOWN" + ] + } + } + }, + { + "type": "object", + "properties": { + "risk-level": { + "type": "string" + }, + "risk-matrix-column": { + "type": "string" + }, + "risk-matrix-row": { + "type": "string" + } + } + } + ] + } + }, + "risk-categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "risk-framework": { + "type": "string" + }, + "answer-options": { + "type": "array", + "items": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + } + }, + "selected-answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed-mime-types": { + "type": "array", + "items": { + "type": "string" + } + }, + "allow-select-other": { + "type": "boolean" + }, + "sync-model": { + "type": "string", + "enum": [ + "dataSilo", + "subDataPoint", + "vendor", + "dataSubCategory", + "processingPurposeSubCategory", + "businessEntity", + "processingActivity" + ] + }, + "sync-column": { + "type": "string", + "enum": [ + "TITLE", + "SLUG", + "DESCRIPTION", + "HEADQUARTER_COUNTRY", + "CONNECTION_STATE", + "CONNECT_START_TIME", + "CONTACT_EMAIL", + "CONTACT_NAME", + "COUNTRY", + "COUNTRY_SUB_DIVISION", + "CREDENTIALS_REFRESH_SCHEDULED_AT", + "DATA_PROCESSING_AGREEMENT_LINK", + "DATA_PROCESSING_AGREEMENT_STATUS", + "DATA_RETENTION_NOTE", + "DEFAULT_ACCESS_REQUEST_VISIBILITY", + "DELETED_AT", + "DEPRECATION_STATE", + "EMAIL_SENDING_LOCK", + "EXPIRED_AT", + "EXTERNAL_ID", + "HAS_PERSONAL_DATA", + "HEADERS", + "IS_LIVE", + "LAST_CONNECTED_AT", + "LAST_ENABLED_AT", + "LAST_LOOKUP_PROCESS_CREATION_TIME", + "MANUAL_WORK_RETRY_FREQUENCY", + "MANUAL_WORK_RETRY_START_AT", + "NOTES", + "NOTIFY_EMAIL_ADDRESS", + "NOTIFY_WEBHOOK_URL", + "OUTER_TYPE", + "PLAINTEXT_CONTEXT", + "PROMPT_A_VENDOR_EMAIL_COMPLETION_LINK_TYPE", + "PROMPT_A_VENDOR_EMAIL_INCLUDE_IDENTIFIERS_ATTACHMENT", + "PROMPT_A_VENDOR_EMAIL_SCHEDULED_AT", + "PROMPT_A_VENDOR_EMAIL_SEND_FREQUENCY", + "PROMPT_A_VENDOR_EMAIL_SEND_TYPE", + "PROMPT_A_VENDOR_EMAIL_START_AT", + "RECOMMENDED_FOR_CONSENT", + "RECOMMENDED_FOR_PRIVACY", + "RECONNECT_FORM_ITEMS", + "SAAS_CONTEXT", + "SECRET_HEADERS", + "SUBDOMAIN", + "TYPE", + "URL", + "WEBSITE_URL", + "TRANSFER_REGIONS", + "CONTROLLERSHIPS", + "NAME", + "DATA_POINT_ID", + "ENCRYPTED_SAMPLES_S3_KEY", + "ACCESS_REQUESTED_VISIBILITY_ENABLED", + "ENSURE_REQUEST_REDACTION_ENABLED", + "LAST_CLASSIFIED_AT", + "LAST_RUN_AT", + "ERROR", + "ERROR_COUNT", + "CONTEXT", + "CONTENT_CLASSIFICATION_STATUS", + "NON_NULL_CHECK_COMPLETED", + "CONTROLLERSHIP", + "RETENTION_TYPE", + "RETENTION_PERIOD", + "SCAN_RUN_ID", + "CONTACT_PHONE", + "ADDRESS", + "HEADQUARTER_SUB_DIVISION", + "PRIVACY_POLICY_URL", + "CATEGORY", + "IS_DEFAULT", + "REGEX", + "PURPOSE", + "DATA_PROTECTION_OFFICER_EMAIL", + "DATA_PROTECTION_OFFICER_NAME", + "DATA_TYPE", + "ENCRYPTION", + "IS_PRIMARY_KEY", + "CUSTOM_SILO_CONNECTION_STRATEGY", + "SECURITY_MEASURE_DETAILS", + "STORAGE_REGIONS", + "DATA_PROTECTION_IMPACT_ASSESSMENT_LINK", + "DATA_PROTECTION_IMPACT_ASSESSMENT_STATUS" + ] + }, + "attribute-key": { + "type": "string" + }, + "require-risk-evaluation": { + "type": "boolean" + }, + "require-risk-matrix-evaluation": { + "type": "boolean" + } + } + } + ] + } + } + } + }, + { + "type": "object", + "properties": { + "assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "external-assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string" + }, + "is-reviewed": { + "type": "boolean" + } + } + } + ] + } + }, + "creator": { + "type": "string" + }, + "description": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "SHARED", + "IN_PROGRESS", + "IN_REVIEW", + "CHANGES_REQUESTED", + "REJECTED", + "APPROVED" + ] + }, + "assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "external-assignees": { + "type": "array", + "items": { + "type": "string" + } + }, + "reviewers": { + "type": "array", + "items": { + "type": "string" + } + }, + "locked": { + "type": "boolean" + }, + "archived": { + "type": "boolean" + }, + "external": { + "type": "boolean" + }, + "title-is-internal": { + "type": "boolean" + }, + "due-date": { + "type": "string" + }, + "created-at": { + "type": "string" + }, + "assigned-at": { + "type": "string" + }, + "submitted-at": { + "type": "string" + }, + "approved-at": { + "type": "string" + }, + "rejected-at": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "type" + ], + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "actionItem", + "airgapCookie", + "airgapDataFlow", + "assessmentForm", + "assessmentGroup", + "auditorRun", + "auditorSchedule", + "businessEntity", + "dataSubCategory", + "dataPoint", + "dataPointLevel", + "dataSilo", + "enricher", + "identifier", + "legalHold", + "legalMatter", + "processingActivity", + "processingPurposeSubCategory", + "prompt", + "promptGroup", + "promptRun", + "request", + "scannedObject", + "scannedObjectPath", + "subject", + "subDataPoint", + "vendor" + ] + } + } + } + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "type" + ], + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "actionItem", + "airgapCookie", + "airgapDataFlow", + "assessmentForm", + "assessmentGroup", + "auditorRun", + "auditorSchedule", + "businessEntity", + "dataSubCategory", + "dataPoint", + "dataPointLevel", + "dataSilo", + "enricher", + "identifier", + "legalHold", + "legalMatter", + "processingActivity", + "processingPurposeSubCategory", + "prompt", + "promptGroup", + "promptRun", + "request", + "scannedObject", + "scannedObjectPath", + "subject", + "subDataPoint", + "vendor" + ] + } + } + } + }, + "retention-schedule": { + "type": "object", + "required": [ + "type", + "duration-days", + "operand" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "REQUESTS_CREATED_AT", + "ASSESSMENT_FORM_COMPLETED_AT" + ] + }, + "duration-days": { + "type": "number" + }, + "operand": { + "type": "string", + "enum": [ + "FULL_DELETE", + "PARTIAL_DELETE", + "NONE" + ] + } + } + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + } + }, + "processing-activities": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "securityMeasureDetails": { + "type": "string" + }, + "controllerships": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "CONTROLLER", + "PROCESSOR", + "JOINT_CONTROLLER" + ] + } + }, + "storageRegions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "countrySubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + } + } + }, + "transferRegions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string", + "enum": [ + "EU", + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BV", + "BR", + "IO", + "VG", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "BQ", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "KP", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "KR", + "SS", + "ES", + "LK", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UM", + "VI", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "UZ", + "VU", + "VA", + "VE", + "VN", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "countrySubDivision": { + "type": "string", + "enum": [ + "AD-02", + "AD-03", + "AD-04", + "AD-05", + "AD-06", + "AD-07", + "AD-08", + "AE-AJ", + "AE-AZ", + "AE-DU", + "AE-FU", + "AE-RK", + "AE-SH", + "AE-UQ", + "AF-BAL", + "AF-BAM", + "AF-BDG", + "AF-BDS", + "AF-BGL", + "AF-DAY", + "AF-FRA", + "AF-FYB", + "AF-GHA", + "AF-GHO", + "AF-HEL", + "AF-HER", + "AF-JOW", + "AF-KAB", + "AF-KAN", + "AF-KAP", + "AF-KDZ", + "AF-KHO", + "AF-KNR", + "AF-LAG", + "AF-LOG", + "AF-NAN", + "AF-NIM", + "AF-NUR", + "AF-PAN", + "AF-PAR", + "AF-PIA", + "AF-PKA", + "AF-SAM", + "AF-SAR", + "AF-TAK", + "AF-URU", + "AF-WAR", + "AF-ZAB", + "AG-03", + "AG-04", + "AG-05", + "AG-06", + "AG-07", + "AG-08", + "AG-10", + "AG-11", + "AL-01", + "AL-02", + "AL-03", + "AL-04", + "AL-05", + "AL-06", + "AL-07", + "AL-08", + "AL-09", + "AL-10", + "AL-11", + "AL-12", + "AM-AG", + "AM-AR", + "AM-AV", + "AM-ER", + "AM-GR", + "AM-KT", + "AM-LO", + "AM-SH", + "AM-SU", + "AM-TV", + "AM-VD", + "AO-BGO", + "AO-BGU", + "AO-BIE", + "AO-CAB", + "AO-CCU", + "AO-CNN", + "AO-CNO", + "AO-CUS", + "AO-HUA", + "AO-HUI", + "AO-LNO", + "AO-LSU", + "AO-LUA", + "AO-MAL", + "AO-MOX", + "AO-NAM", + "AO-UIG", + "AO-ZAI", + "AR-A", + "AR-B", + "AR-C", + "AR-D", + "AR-E", + "AR-F", + "AR-G", + "AR-H", + "AR-J", + "AR-K", + "AR-L", + "AR-M", + "AR-N", + "AR-P", + "AR-Q", + "AR-R", + "AR-S", + "AR-T", + "AR-U", + "AR-V", + "AR-W", + "AR-X", + "AR-Y", + "AR-Z", + "AT-1", + "AT-2", + "AT-3", + "AT-4", + "AT-5", + "AT-6", + "AT-7", + "AT-8", + "AT-9", + "AU-ACT", + "AU-NSW", + "AU-NT", + "AU-QLD", + "AU-SA", + "AU-TAS", + "AU-VIC", + "AU-WA", + "AZ-ABS", + "AZ-AGA", + "AZ-AGC", + "AZ-AGM", + "AZ-AGS", + "AZ-AGU", + "AZ-AST", + "AZ-BA", + "AZ-BAB", + "AZ-BAL", + "AZ-BAR", + "AZ-BEY", + "AZ-BIL", + "AZ-CAB", + "AZ-CAL", + "AZ-CUL", + "AZ-DAS", + "AZ-FUZ", + "AZ-GA", + "AZ-GAD", + "AZ-GOR", + "AZ-GOY", + "AZ-GYG", + "AZ-HAC", + "AZ-IMI", + "AZ-ISM", + "AZ-KAL", + "AZ-KAN", + "AZ-KUR", + "AZ-LA", + "AZ-LAC", + "AZ-LAN", + "AZ-LER", + "AZ-MAS", + "AZ-MI", + "AZ-NA", + "AZ-NEF", + "AZ-NV", + "AZ-NX", + "AZ-OGU", + "AZ-ORD", + "AZ-QAB", + "AZ-QAX", + "AZ-QAZ", + "AZ-QBA", + "AZ-QBI", + "AZ-QOB", + "AZ-QUS", + "AZ-SA", + "AZ-SAB", + "AZ-SAD", + "AZ-SAH", + "AZ-SAK", + "AZ-SAL", + "AZ-SAR", + "AZ-SAT", + "AZ-SBN", + "AZ-SIY", + "AZ-SKR", + "AZ-SM", + "AZ-SMI", + "AZ-SMX", + "AZ-SR", + "AZ-SUS", + "AZ-TAR", + "AZ-TOV", + "AZ-UCA", + "AZ-XA", + "AZ-XAC", + "AZ-XCI", + "AZ-XIZ", + "AZ-XVD", + "AZ-YAR", + "AZ-YE", + "AZ-YEV", + "AZ-ZAN", + "AZ-ZAQ", + "AZ-ZAR", + "BA-BIH", + "BA-BRC", + "BA-SRP", + "BB-01", + "BB-02", + "BB-03", + "BB-04", + "BB-05", + "BB-06", + "BB-07", + "BB-08", + "BB-09", + "BB-10", + "BB-11", + "BD-01", + "BD-02", + "BD-03", + "BD-04", + "BD-05", + "BD-06", + "BD-07", + "BD-08", + "BD-09", + "BD-10", + "BD-11", + "BD-12", + "BD-13", + "BD-14", + "BD-15", + "BD-16", + "BD-17", + "BD-18", + "BD-19", + "BD-20", + "BD-21", + "BD-22", + "BD-23", + "BD-24", + "BD-25", + "BD-26", + "BD-27", + "BD-28", + "BD-29", + "BD-30", + "BD-31", + "BD-32", + "BD-33", + "BD-34", + "BD-35", + "BD-36", + "BD-37", + "BD-38", + "BD-39", + "BD-40", + "BD-41", + "BD-42", + "BD-43", + "BD-44", + "BD-45", + "BD-46", + "BD-47", + "BD-48", + "BD-49", + "BD-50", + "BD-51", + "BD-52", + "BD-53", + "BD-54", + "BD-55", + "BD-56", + "BD-57", + "BD-58", + "BD-59", + "BD-60", + "BD-61", + "BD-62", + "BD-63", + "BD-64", + "BD-A", + "BD-B", + "BD-C", + "BD-D", + "BD-E", + "BD-F", + "BD-G", + "BD-H", + "BE-BRU", + "BE-VAN", + "BE-VBR", + "BE-VLG", + "BE-VLI", + "BE-VOV", + "BE-VWV", + "BE-WAL", + "BE-WBR", + "BE-WHT", + "BE-WLG", + "BE-WLX", + "BE-WNA", + "BF-01", + "BF-02", + "BF-03", + "BF-04", + "BF-05", + "BF-06", + "BF-07", + "BF-08", + "BF-09", + "BF-10", + "BF-11", + "BF-12", + "BF-13", + "BF-BAL", + "BF-BAM", + "BF-BAN", + "BF-BAZ", + "BF-BGR", + "BF-BLG", + "BF-BLK", + "BF-COM", + "BF-GAN", + "BF-GNA", + "BF-GOU", + "BF-HOU", + "BF-IOB", + "BF-KAD", + "BF-KEN", + "BF-KMD", + "BF-KMP", + "BF-KOP", + "BF-KOS", + "BF-KOT", + "BF-KOW", + "BF-LER", + "BF-LOR", + "BF-MOU", + "BF-NAM", + "BF-NAO", + "BF-NAY", + "BF-NOU", + "BF-OUB", + "BF-OUD", + "BF-PAS", + "BF-PON", + "BF-SEN", + "BF-SIS", + "BF-SMT", + "BF-SNG", + "BF-SOM", + "BF-SOR", + "BF-TAP", + "BF-TUI", + "BF-YAG", + "BF-YAT", + "BF-ZIR", + "BF-ZON", + "BF-ZOU", + "BG-01", + "BG-02", + "BG-03", + "BG-04", + "BG-05", + "BG-06", + "BG-07", + "BG-08", + "BG-09", + "BG-10", + "BG-11", + "BG-12", + "BG-13", + "BG-14", + "BG-15", + "BG-16", + "BG-17", + "BG-18", + "BG-19", + "BG-20", + "BG-21", + "BG-22", + "BG-23", + "BG-24", + "BG-25", + "BG-26", + "BG-27", + "BG-28", + "BH-13", + "BH-14", + "BH-15", + "BH-17", + "BI-BB", + "BI-BL", + "BI-BM", + "BI-BR", + "BI-CA", + "BI-CI", + "BI-GI", + "BI-KI", + "BI-KR", + "BI-KY", + "BI-MA", + "BI-MU", + "BI-MW", + "BI-MY", + "BI-NG", + "BI-RM", + "BI-RT", + "BI-RY", + "BJ-AK", + "BJ-AL", + "BJ-AQ", + "BJ-BO", + "BJ-CO", + "BJ-DO", + "BJ-KO", + "BJ-LI", + "BJ-MO", + "BJ-OU", + "BJ-PL", + "BJ-ZO", + "BN-BE", + "BN-BM", + "BN-TE", + "BN-TU", + "BO-B", + "BO-C", + "BO-H", + "BO-L", + "BO-N", + "BO-O", + "BO-P", + "BO-S", + "BO-T", + "BQ-BO", + "BQ-SA", + "BQ-SE", + "BR-AC", + "BR-AL", + "BR-AM", + "BR-AP", + "BR-BA", + "BR-CE", + "BR-DF", + "BR-ES", + "BR-GO", + "BR-MA", + "BR-MG", + "BR-MS", + "BR-MT", + "BR-PA", + "BR-PB", + "BR-PE", + "BR-PI", + "BR-PR", + "BR-RJ", + "BR-RN", + "BR-RO", + "BR-RR", + "BR-RS", + "BR-SC", + "BR-SE", + "BR-SP", + "BR-TO", + "BS-AK", + "BS-BI", + "BS-BP", + "BS-BY", + "BS-CE", + "BS-CI", + "BS-CK", + "BS-CO", + "BS-CS", + "BS-EG", + "BS-EX", + "BS-FP", + "BS-GC", + "BS-HI", + "BS-HT", + "BS-IN", + "BS-LI", + "BS-MC", + "BS-MG", + "BS-MI", + "BS-NE", + "BS-NO", + "BS-NP", + "BS-NS", + "BS-RC", + "BS-RI", + "BS-SA", + "BS-SE", + "BS-SO", + "BS-SS", + "BS-SW", + "BS-WG", + "BT-11", + "BT-12", + "BT-13", + "BT-14", + "BT-15", + "BT-21", + "BT-22", + "BT-23", + "BT-24", + "BT-31", + "BT-32", + "BT-33", + "BT-34", + "BT-41", + "BT-42", + "BT-43", + "BT-44", + "BT-45", + "BT-GA", + "BT-TY", + "BW-CE", + "BW-CH", + "BW-FR", + "BW-GA", + "BW-GH", + "BW-JW", + "BW-KG", + "BW-KL", + "BW-KW", + "BW-LO", + "BW-NE", + "BW-NW", + "BW-SE", + "BW-SO", + "BW-SP", + "BW-ST", + "BY-BR", + "BY-HM", + "BY-HO", + "BY-HR", + "BY-MA", + "BY-MI", + "BY-VI", + "BZ-BZ", + "BZ-CY", + "BZ-CZL", + "BZ-OW", + "BZ-SC", + "BZ-TOL", + "CA-AB", + "CA-BC", + "CA-MB", + "CA-NB", + "CA-NL", + "CA-NS", + "CA-NT", + "CA-NU", + "CA-ON", + "CA-PE", + "CA-QC", + "CA-SK", + "CA-YT", + "CD-BC", + "CD-BU", + "CD-EQ", + "CD-HK", + "CD-HL", + "CD-HU", + "CD-IT", + "CD-KC", + "CD-KE", + "CD-KG", + "CD-KL", + "CD-KN", + "CD-KS", + "CD-LO", + "CD-LU", + "CD-MA", + "CD-MN", + "CD-MO", + "CD-NK", + "CD-NU", + "CD-SA", + "CD-SK", + "CD-SU", + "CD-TA", + "CD-TO", + "CD-TU", + "CF-AC", + "CF-BB", + "CF-BGF", + "CF-BK", + "CF-HK", + "CF-HM", + "CF-HS", + "CF-KB", + "CF-KG", + "CF-LB", + "CF-MB", + "CF-MP", + "CF-NM", + "CF-OP", + "CF-SE", + "CF-UK", + "CF-VK", + "CG-11", + "CG-12", + "CG-13", + "CG-14", + "CG-15", + "CG-16", + "CG-2", + "CG-5", + "CG-7", + "CG-8", + "CG-9", + "CG-BZV", + "CH-AG", + "CH-AI", + "CH-AR", + "CH-BE", + "CH-BL", + "CH-BS", + "CH-FR", + "CH-GE", + "CH-GL", + "CH-GR", + "CH-JU", + "CH-LU", + "CH-NE", + "CH-NW", + "CH-OW", + "CH-SG", + "CH-SH", + "CH-SO", + "CH-SZ", + "CH-TG", + "CH-TI", + "CH-UR", + "CH-VD", + "CH-VS", + "CH-ZG", + "CH-ZH", + "CI-AB", + "CI-BS", + "CI-CM", + "CI-DN", + "CI-GD", + "CI-LC", + "CI-LG", + "CI-MG", + "CI-SM", + "CI-SV", + "CI-VB", + "CI-WR", + "CI-YM", + "CI-ZZ", + "CL-AI", + "CL-AN", + "CL-AP", + "CL-AR", + "CL-AT", + "CL-BI", + "CL-CO", + "CL-LI", + "CL-LL", + "CL-LR", + "CL-MA", + "CL-ML", + "CL-NB", + "CL-RM", + "CL-TA", + "CL-VS", + "CM-AD", + "CM-CE", + "CM-EN", + "CM-ES", + "CM-LT", + "CM-NO", + "CM-NW", + "CM-OU", + "CM-SU", + "CM-SW", + "CN-AH", + "CN-BJ", + "CN-CQ", + "CN-FJ", + "CN-GD", + "CN-GS", + "CN-GX", + "CN-GZ", + "CN-HA", + "CN-HB", + "CN-HE", + "CN-HI", + "CN-HK", + "CN-HL", + "CN-HN", + "CN-JL", + "CN-JS", + "CN-JX", + "CN-LN", + "CN-MO", + "CN-NM", + "CN-NX", + "CN-QH", + "CN-SC", + "CN-SD", + "CN-SH", + "CN-SN", + "CN-SX", + "CN-TJ", + "CN-TW", + "CN-XJ", + "CN-XZ", + "CN-YN", + "CN-ZJ", + "CO-AMA", + "CO-ANT", + "CO-ARA", + "CO-ATL", + "CO-BOL", + "CO-BOY", + "CO-CAL", + "CO-CAQ", + "CO-CAS", + "CO-CAU", + "CO-CES", + "CO-CHO", + "CO-COR", + "CO-CUN", + "CO-DC", + "CO-GUA", + "CO-GUV", + "CO-HUI", + "CO-LAG", + "CO-MAG", + "CO-MET", + "CO-NAR", + "CO-NSA", + "CO-PUT", + "CO-QUI", + "CO-RIS", + "CO-SAN", + "CO-SAP", + "CO-SUC", + "CO-TOL", + "CO-VAC", + "CO-VAU", + "CO-VID", + "CR-A", + "CR-C", + "CR-G", + "CR-H", + "CR-L", + "CR-P", + "CR-SJ", + "CU-01", + "CU-03", + "CU-04", + "CU-05", + "CU-06", + "CU-07", + "CU-08", + "CU-09", + "CU-10", + "CU-11", + "CU-12", + "CU-13", + "CU-14", + "CU-15", + "CU-16", + "CU-99", + "CV-B", + "CV-BR", + "CV-BV", + "CV-CA", + "CV-CF", + "CV-CR", + "CV-MA", + "CV-MO", + "CV-PA", + "CV-PN", + "CV-PR", + "CV-RB", + "CV-RG", + "CV-RS", + "CV-S", + "CV-SD", + "CV-SF", + "CV-SL", + "CV-SM", + "CV-SO", + "CV-SS", + "CV-SV", + "CV-TA", + "CV-TS", + "CY-01", + "CY-02", + "CY-03", + "CY-04", + "CY-05", + "CY-06", + "CZ-10", + "CZ-20", + "CZ-201", + "CZ-202", + "CZ-203", + "CZ-204", + "CZ-205", + "CZ-206", + "CZ-207", + "CZ-208", + "CZ-209", + "CZ-20A", + "CZ-20B", + "CZ-20C", + "CZ-31", + "CZ-311", + "CZ-312", + "CZ-313", + "CZ-314", + "CZ-315", + "CZ-316", + "CZ-317", + "CZ-32", + "CZ-321", + "CZ-322", + "CZ-323", + "CZ-324", + "CZ-325", + "CZ-326", + "CZ-327", + "CZ-41", + "CZ-411", + "CZ-412", + "CZ-413", + "CZ-42", + "CZ-421", + "CZ-422", + "CZ-423", + "CZ-424", + "CZ-425", + "CZ-426", + "CZ-427", + "CZ-51", + "CZ-511", + "CZ-512", + "CZ-513", + "CZ-514", + "CZ-52", + "CZ-521", + "CZ-522", + "CZ-523", + "CZ-524", + "CZ-525", + "CZ-53", + "CZ-531", + "CZ-532", + "CZ-533", + "CZ-534", + "CZ-63", + "CZ-631", + "CZ-632", + "CZ-633", + "CZ-634", + "CZ-635", + "CZ-64", + "CZ-641", + "CZ-642", + "CZ-643", + "CZ-644", + "CZ-645", + "CZ-646", + "CZ-647", + "CZ-71", + "CZ-711", + "CZ-712", + "CZ-713", + "CZ-714", + "CZ-715", + "CZ-72", + "CZ-721", + "CZ-722", + "CZ-723", + "CZ-724", + "CZ-80", + "CZ-801", + "CZ-802", + "CZ-803", + "CZ-804", + "CZ-805", + "CZ-806", + "DE-BB", + "DE-BE", + "DE-BW", + "DE-BY", + "DE-HB", + "DE-HE", + "DE-HH", + "DE-MV", + "DE-NI", + "DE-NW", + "DE-RP", + "DE-SH", + "DE-SL", + "DE-SN", + "DE-ST", + "DE-TH", + "DJ-AR", + "DJ-AS", + "DJ-DI", + "DJ-DJ", + "DJ-OB", + "DJ-TA", + "DK-81", + "DK-82", + "DK-83", + "DK-84", + "DK-85", + "DM-02", + "DM-03", + "DM-04", + "DM-05", + "DM-06", + "DM-07", + "DM-08", + "DM-09", + "DM-10", + "DM-11", + "DO-01", + "DO-02", + "DO-03", + "DO-04", + "DO-05", + "DO-06", + "DO-07", + "DO-08", + "DO-09", + "DO-10", + "DO-11", + "DO-12", + "DO-13", + "DO-14", + "DO-15", + "DO-16", + "DO-17", + "DO-18", + "DO-19", + "DO-20", + "DO-21", + "DO-22", + "DO-23", + "DO-24", + "DO-25", + "DO-26", + "DO-27", + "DO-28", + "DO-29", + "DO-30", + "DO-31", + "DO-32", + "DO-33", + "DO-34", + "DO-35", + "DO-36", + "DO-37", + "DO-38", + "DO-39", + "DO-40", + "DO-41", + "DO-42", + "DZ-01", + "DZ-02", + "DZ-03", + "DZ-04", + "DZ-05", + "DZ-06", + "DZ-07", + "DZ-08", + "DZ-09", + "DZ-10", + "DZ-11", + "DZ-12", + "DZ-13", + "DZ-14", + "DZ-15", + "DZ-16", + "DZ-17", + "DZ-18", + "DZ-19", + "DZ-20", + "DZ-21", + "DZ-22", + "DZ-23", + "DZ-24", + "DZ-25", + "DZ-26", + "DZ-27", + "DZ-28", + "DZ-29", + "DZ-30", + "DZ-31", + "DZ-32", + "DZ-33", + "DZ-34", + "DZ-35", + "DZ-36", + "DZ-37", + "DZ-38", + "DZ-39", + "DZ-40", + "DZ-41", + "DZ-42", + "DZ-43", + "DZ-44", + "DZ-45", + "DZ-46", + "DZ-47", + "DZ-48", + "EC-A", + "EC-B", + "EC-C", + "EC-D", + "EC-E", + "EC-F", + "EC-G", + "EC-H", + "EC-I", + "EC-L", + "EC-M", + "EC-N", + "EC-O", + "EC-P", + "EC-R", + "EC-S", + "EC-SD", + "EC-SE", + "EC-T", + "EC-U", + "EC-W", + "EC-X", + "EC-Y", + "EC-Z", + "EE-130", + "EE-141", + "EE-142", + "EE-171", + "EE-184", + "EE-191", + "EE-198", + "EE-205", + "EE-214", + "EE-245", + "EE-247", + "EE-251", + "EE-255", + "EE-272", + "EE-283", + "EE-284", + "EE-291", + "EE-293", + "EE-296", + "EE-303", + "EE-305", + "EE-317", + "EE-321", + "EE-338", + "EE-353", + "EE-37", + "EE-39", + "EE-424", + "EE-430", + "EE-431", + "EE-432", + "EE-441", + "EE-442", + "EE-446", + "EE-45", + "EE-478", + "EE-480", + "EE-486", + "EE-50", + "EE-503", + "EE-511", + "EE-514", + "EE-52", + "EE-528", + "EE-557", + "EE-56", + "EE-567", + "EE-586", + "EE-60", + "EE-615", + "EE-618", + "EE-622", + "EE-624", + "EE-638", + "EE-64", + "EE-651", + "EE-653", + "EE-661", + "EE-663", + "EE-668", + "EE-68", + "EE-689", + "EE-698", + "EE-708", + "EE-71", + "EE-712", + "EE-714", + "EE-719", + "EE-726", + "EE-732", + "EE-735", + "EE-74", + "EE-784", + "EE-79", + "EE-792", + "EE-793", + "EE-796", + "EE-803", + "EE-809", + "EE-81", + "EE-824", + "EE-834", + "EE-84", + "EE-855", + "EE-87", + "EE-890", + "EE-897", + "EE-899", + "EE-901", + "EE-903", + "EE-907", + "EE-917", + "EE-919", + "EE-928", + "EG-ALX", + "EG-ASN", + "EG-AST", + "EG-BA", + "EG-BH", + "EG-BNS", + "EG-C", + "EG-DK", + "EG-DT", + "EG-FYM", + "EG-GH", + "EG-GZ", + "EG-IS", + "EG-JS", + "EG-KB", + "EG-KFS", + "EG-KN", + "EG-LX", + "EG-MN", + "EG-MNF", + "EG-MT", + "EG-PTS", + "EG-SHG", + "EG-SHR", + "EG-SIN", + "EG-SUZ", + "EG-WAD", + "ER-AN", + "ER-DK", + "ER-DU", + "ER-GB", + "ER-MA", + "ER-SK", + "ES-A", + "ES-AB", + "ES-AL", + "ES-AN", + "ES-AR", + "ES-AS", + "ES-AV", + "ES-B", + "ES-BA", + "ES-BI", + "ES-BU", + "ES-C", + "ES-CA", + "ES-CB", + "ES-CC", + "ES-CE", + "ES-CL", + "ES-CM", + "ES-CN", + "ES-CO", + "ES-CR", + "ES-CS", + "ES-CT", + "ES-CU", + "ES-EX", + "ES-GA", + "ES-GC", + "ES-GI", + "ES-GR", + "ES-GU", + "ES-H", + "ES-HU", + "ES-IB", + "ES-J", + "ES-L", + "ES-LE", + "ES-LO", + "ES-LU", + "ES-M", + "ES-MA", + "ES-MC", + "ES-MD", + "ES-ML", + "ES-MU", + "ES-NA", + "ES-NC", + "ES-O", + "ES-OR", + "ES-P", + "ES-PM", + "ES-PO", + "ES-PV", + "ES-RI", + "ES-S", + "ES-SA", + "ES-SE", + "ES-SG", + "ES-SO", + "ES-SS", + "ES-T", + "ES-TE", + "ES-TF", + "ES-TO", + "ES-V", + "ES-VA", + "ES-VC", + "ES-VI", + "ES-Z", + "ES-ZA", + "ET-AA", + "ET-AF", + "ET-AM", + "ET-BE", + "ET-DD", + "ET-GA", + "ET-HA", + "ET-OR", + "ET-SN", + "ET-SO", + "ET-TI", + "FI-01", + "FI-02", + "FI-03", + "FI-04", + "FI-05", + "FI-06", + "FI-07", + "FI-08", + "FI-09", + "FI-10", + "FI-11", + "FI-12", + "FI-13", + "FI-14", + "FI-15", + "FI-16", + "FI-17", + "FI-18", + "FI-19", + "FJ-01", + "FJ-02", + "FJ-03", + "FJ-04", + "FJ-05", + "FJ-06", + "FJ-07", + "FJ-08", + "FJ-09", + "FJ-10", + "FJ-11", + "FJ-12", + "FJ-13", + "FJ-14", + "FJ-C", + "FJ-E", + "FJ-N", + "FJ-R", + "FJ-W", + "FM-KSA", + "FM-PNI", + "FM-TRK", + "FM-YAP", + "FR-01", + "FR-02", + "FR-03", + "FR-04", + "FR-05", + "FR-06", + "FR-07", + "FR-08", + "FR-09", + "FR-10", + "FR-11", + "FR-12", + "FR-13", + "FR-14", + "FR-15", + "FR-16", + "FR-17", + "FR-18", + "FR-19", + "FR-20R", + "FR-21", + "FR-22", + "FR-23", + "FR-24", + "FR-25", + "FR-26", + "FR-27", + "FR-28", + "FR-29", + "FR-2A", + "FR-2B", + "FR-30", + "FR-31", + "FR-32", + "FR-33", + "FR-34", + "FR-35", + "FR-36", + "FR-37", + "FR-38", + "FR-39", + "FR-40", + "FR-41", + "FR-42", + "FR-43", + "FR-44", + "FR-45", + "FR-46", + "FR-47", + "FR-48", + "FR-49", + "FR-50", + "FR-51", + "FR-52", + "FR-53", + "FR-54", + "FR-55", + "FR-56", + "FR-57", + "FR-58", + "FR-59", + "FR-60", + "FR-61", + "FR-62", + "FR-63", + "FR-64", + "FR-65", + "FR-66", + "FR-67", + "FR-68", + "FR-69", + "FR-70", + "FR-71", + "FR-72", + "FR-73", + "FR-74", + "FR-75", + "FR-76", + "FR-77", + "FR-78", + "FR-79", + "FR-80", + "FR-81", + "FR-82", + "FR-83", + "FR-84", + "FR-85", + "FR-86", + "FR-87", + "FR-88", + "FR-89", + "FR-90", + "FR-91", + "FR-92", + "FR-93", + "FR-94", + "FR-95", + "FR-971", + "FR-972", + "FR-973", + "FR-974", + "FR-976", + "FR-ARA", + "FR-BFC", + "FR-BL", + "FR-BRE", + "FR-CP", + "FR-CVL", + "FR-GES", + "FR-GF", + "FR-GP", + "FR-HDF", + "FR-IDF", + "FR-MF", + "FR-MQ", + "FR-NAQ", + "FR-NC", + "FR-NOR", + "FR-OCC", + "FR-PAC", + "FR-PDL", + "FR-PF", + "FR-PM", + "FR-RE", + "FR-TF", + "FR-WF", + "FR-YT", + "GA-1", + "GA-2", + "GA-3", + "GA-4", + "GA-5", + "GA-6", + "GA-7", + "GA-8", + "GA-9", + "GB-ABC", + "GB-ABD", + "GB-ABE", + "GB-AGB", + "GB-AGY", + "GB-AND", + "GB-ANN", + "GB-ANS", + "GB-BAS", + "GB-BBD", + "GB-BCP", + "GB-BDF", + "GB-BDG", + "GB-BEN", + "GB-BEX", + "GB-BFS", + "GB-BGE", + "GB-BGW", + "GB-BIR", + "GB-BKM", + "GB-BNE", + "GB-BNH", + "GB-BNS", + "GB-BOL", + "GB-BPL", + "GB-BRC", + "GB-BRD", + "GB-BRY", + "GB-BST", + "GB-BUR", + "GB-CAM", + "GB-CAY", + "GB-CBF", + "GB-CCG", + "GB-CGN", + "GB-CHE", + "GB-CHW", + "GB-CLD", + "GB-CLK", + "GB-CMA", + "GB-CMD", + "GB-CMN", + "GB-CON", + "GB-COV", + "GB-CRF", + "GB-CRY", + "GB-CWY", + "GB-DAL", + "GB-DBY", + "GB-DEN", + "GB-DER", + "GB-DEV", + "GB-DGY", + "GB-DNC", + "GB-DND", + "GB-DOR", + "GB-DRS", + "GB-DUD", + "GB-DUR", + "GB-EAL", + "GB-EAW", + "GB-EAY", + "GB-EDH", + "GB-EDU", + "GB-ELN", + "GB-ELS", + "GB-ENF", + "GB-ENG", + "GB-ERW", + "GB-ERY", + "GB-ESS", + "GB-ESX", + "GB-FAL", + "GB-FIF", + "GB-FLN", + "GB-FMO", + "GB-GAT", + "GB-GBN", + "GB-GLG", + "GB-GLS", + "GB-GRE", + "GB-GWN", + "GB-HAL", + "GB-HAM", + "GB-HAV", + "GB-HCK", + "GB-HEF", + "GB-HIL", + "GB-HLD", + "GB-HMF", + "GB-HNS", + "GB-HPL", + "GB-HRT", + "GB-HRW", + "GB-HRY", + "GB-IOS", + "GB-IOW", + "GB-ISL", + "GB-IVC", + "GB-KEC", + "GB-KEN", + "GB-KHL", + "GB-KIR", + "GB-KTT", + "GB-KWL", + "GB-LAN", + "GB-LBC", + "GB-LBH", + "GB-LCE", + "GB-LDS", + "GB-LEC", + "GB-LEW", + "GB-LIN", + "GB-LIV", + "GB-LND", + "GB-LUT", + "GB-MAN", + "GB-MDB", + "GB-MDW", + "GB-MEA", + "GB-MIK", + "GB-MLN", + "GB-MON", + "GB-MRT", + "GB-MRY", + "GB-MTY", + "GB-MUL", + "GB-NAY", + "GB-NBL", + "GB-NEL", + "GB-NET", + "GB-NFK", + "GB-NGM", + "GB-NIR", + "GB-NLK", + "GB-NLN", + "GB-NMD", + "GB-NSM", + "GB-NTH", + "GB-NTL", + "GB-NTT", + "GB-NTY", + "GB-NWM", + "GB-NWP", + "GB-NYK", + "GB-OLD", + "GB-ORK", + "GB-OXF", + "GB-PEM", + "GB-PKN", + "GB-PLY", + "GB-POR", + "GB-POW", + "GB-PTE", + "GB-RCC", + "GB-RCH", + "GB-RCT", + "GB-RDB", + "GB-RDG", + "GB-RFW", + "GB-RIC", + "GB-ROT", + "GB-RUT", + "GB-SAW", + "GB-SAY", + "GB-SCB", + "GB-SCT", + "GB-SFK", + "GB-SFT", + "GB-SGC", + "GB-SHF", + "GB-SHN", + "GB-SHR", + "GB-SKP", + "GB-SLF", + "GB-SLG", + "GB-SLK", + "GB-SND", + "GB-SOL", + "GB-SOM", + "GB-SOS", + "GB-SRY", + "GB-STE", + "GB-STG", + "GB-STH", + "GB-STN", + "GB-STS", + "GB-STT", + "GB-STY", + "GB-SWA", + "GB-SWD", + "GB-SWK", + "GB-TAM", + "GB-TFW", + "GB-THR", + "GB-TOB", + "GB-TOF", + "GB-TRF", + "GB-TWH", + "GB-UKM", + "GB-VGL", + "GB-WAR", + "GB-WBK", + "GB-WDU", + "GB-WFT", + "GB-WGN", + "GB-WIL", + "GB-WKF", + "GB-WLL", + "GB-WLN", + "GB-WLS", + "GB-WLV", + "GB-WND", + "GB-WNM", + "GB-WOK", + "GB-WOR", + "GB-WRL", + "GB-WRT", + "GB-WRX", + "GB-WSM", + "GB-WSX", + "GB-YOR", + "GB-ZET", + "GD-01", + "GD-02", + "GD-03", + "GD-04", + "GD-05", + "GD-06", + "GD-10", + "GE-AB", + "GE-AJ", + "GE-GU", + "GE-IM", + "GE-KA", + "GE-KK", + "GE-MM", + "GE-RL", + "GE-SJ", + "GE-SK", + "GE-SZ", + "GE-TB", + "GH-AA", + "GH-AF", + "GH-AH", + "GH-BA", + "GH-BE", + "GH-BO", + "GH-CP", + "GH-EP", + "GH-NE", + "GH-NP", + "GH-OT", + "GH-SV", + "GH-TV", + "GH-UE", + "GH-UW", + "GH-WN", + "GH-WP", + "GL-AV", + "GL-KU", + "GL-QE", + "GL-QT", + "GL-SM", + "GM-B", + "GM-L", + "GM-M", + "GM-N", + "GM-U", + "GM-W", + "GN-B", + "GN-BE", + "GN-BF", + "GN-BK", + "GN-C", + "GN-CO", + "GN-D", + "GN-DB", + "GN-DI", + "GN-DL", + "GN-DU", + "GN-F", + "GN-FA", + "GN-FO", + "GN-FR", + "GN-GA", + "GN-GU", + "GN-K", + "GN-KA", + "GN-KB", + "GN-KD", + "GN-KE", + "GN-KN", + "GN-KO", + "GN-KS", + "GN-L", + "GN-LA", + "GN-LE", + "GN-LO", + "GN-M", + "GN-MC", + "GN-MD", + "GN-ML", + "GN-MM", + "GN-N", + "GN-NZ", + "GN-PI", + "GN-SI", + "GN-TE", + "GN-TO", + "GN-YO", + "GQ-AN", + "GQ-BN", + "GQ-BS", + "GQ-C", + "GQ-CS", + "GQ-DJ", + "GQ-I", + "GQ-KN", + "GQ-LI", + "GQ-WN", + "GR-69", + "GR-A", + "GR-B", + "GR-C", + "GR-D", + "GR-E", + "GR-F", + "GR-G", + "GR-H", + "GR-I", + "GR-J", + "GR-K", + "GR-L", + "GR-M", + "GT-AV", + "GT-BV", + "GT-CM", + "GT-CQ", + "GT-ES", + "GT-GU", + "GT-HU", + "GT-IZ", + "GT-JA", + "GT-JU", + "GT-PE", + "GT-PR", + "GT-QC", + "GT-QZ", + "GT-RE", + "GT-SA", + "GT-SM", + "GT-SO", + "GT-SR", + "GT-SU", + "GT-TO", + "GT-ZA", + "GW-BA", + "GW-BL", + "GW-BM", + "GW-BS", + "GW-CA", + "GW-GA", + "GW-L", + "GW-N", + "GW-OI", + "GW-QU", + "GW-S", + "GW-TO", + "GY-BA", + "GY-CU", + "GY-DE", + "GY-EB", + "GY-ES", + "GY-MA", + "GY-PM", + "GY-PT", + "GY-UD", + "GY-UT", + "HN-AT", + "HN-CH", + "HN-CL", + "HN-CM", + "HN-CP", + "HN-CR", + "HN-EP", + "HN-FM", + "HN-GD", + "HN-IB", + "HN-IN", + "HN-LE", + "HN-LP", + "HN-OC", + "HN-OL", + "HN-SB", + "HN-VA", + "HN-YO", + "HR-01", + "HR-02", + "HR-03", + "HR-04", + "HR-05", + "HR-06", + "HR-07", + "HR-08", + "HR-09", + "HR-10", + "HR-11", + "HR-12", + "HR-13", + "HR-14", + "HR-15", + "HR-16", + "HR-17", + "HR-18", + "HR-19", + "HR-20", + "HR-21", + "HT-AR", + "HT-CE", + "HT-GA", + "HT-ND", + "HT-NE", + "HT-NI", + "HT-NO", + "HT-OU", + "HT-SD", + "HT-SE", + "HU-BA", + "HU-BC", + "HU-BE", + "HU-BK", + "HU-BU", + "HU-BZ", + "HU-CS", + "HU-DE", + "HU-DU", + "HU-EG", + "HU-ER", + "HU-FE", + "HU-GS", + "HU-GY", + "HU-HB", + "HU-HE", + "HU-HV", + "HU-JN", + "HU-KE", + "HU-KM", + "HU-KV", + "HU-MI", + "HU-NK", + "HU-NO", + "HU-NY", + "HU-PE", + "HU-PS", + "HU-SD", + "HU-SF", + "HU-SH", + "HU-SK", + "HU-SN", + "HU-SO", + "HU-SS", + "HU-ST", + "HU-SZ", + "HU-TB", + "HU-TO", + "HU-VA", + "HU-VE", + "HU-VM", + "HU-ZA", + "HU-ZE", + "ID-AC", + "ID-BA", + "ID-BB", + "ID-BE", + "ID-BT", + "ID-GO", + "ID-JA", + "ID-JB", + "ID-JI", + "ID-JK", + "ID-JT", + "ID-JW", + "ID-KA", + "ID-KB", + "ID-KI", + "ID-KR", + "ID-KS", + "ID-KT", + "ID-KU", + "ID-LA", + "ID-MA", + "ID-ML", + "ID-MU", + "ID-NB", + "ID-NT", + "ID-NU", + "ID-PA", + "ID-PB", + "ID-PP", + "ID-RI", + "ID-SA", + "ID-SB", + "ID-SG", + "ID-SL", + "ID-SM", + "ID-SN", + "ID-SR", + "ID-SS", + "ID-ST", + "ID-SU", + "ID-YO", + "IE-C", + "IE-CE", + "IE-CN", + "IE-CO", + "IE-CW", + "IE-D", + "IE-DL", + "IE-G", + "IE-KE", + "IE-KK", + "IE-KY", + "IE-L", + "IE-LD", + "IE-LH", + "IE-LK", + "IE-LM", + "IE-LS", + "IE-M", + "IE-MH", + "IE-MN", + "IE-MO", + "IE-OY", + "IE-RN", + "IE-SO", + "IE-TA", + "IE-U", + "IE-WD", + "IE-WH", + "IE-WW", + "IE-WX", + "IL-D", + "IL-HA", + "IL-JM", + "IL-M", + "IL-TA", + "IL-Z", + "IN-AN", + "IN-AP", + "IN-AR", + "IN-AS", + "IN-BR", + "IN-CH", + "IN-CT", + "IN-DH", + "IN-DL", + "IN-GA", + "IN-GJ", + "IN-HP", + "IN-HR", + "IN-JH", + "IN-JK", + "IN-KA", + "IN-KL", + "IN-LA", + "IN-LD", + "IN-MH", + "IN-ML", + "IN-MN", + "IN-MP", + "IN-MZ", + "IN-NL", + "IN-OR", + "IN-PB", + "IN-PY", + "IN-RJ", + "IN-SK", + "IN-TG", + "IN-TN", + "IN-TR", + "IN-UP", + "IN-UT", + "IN-WB", + "IQ-AN", + "IQ-AR", + "IQ-BA", + "IQ-BB", + "IQ-BG", + "IQ-DA", + "IQ-DI", + "IQ-DQ", + "IQ-HA", + "IQ-KA", + "IQ-KI", + "IQ-MA", + "IQ-MU", + "IQ-NA", + "IQ-NI", + "IQ-QA", + "IQ-SD", + "IQ-SU", + "IQ-WA", + "IR-00", + "IR-01", + "IR-02", + "IR-03", + "IR-04", + "IR-05", + "IR-06", + "IR-07", + "IR-08", + "IR-09", + "IR-10", + "IR-11", + "IR-12", + "IR-13", + "IR-14", + "IR-15", + "IR-16", + "IR-17", + "IR-18", + "IR-19", + "IR-20", + "IR-21", + "IR-22", + "IR-23", + "IR-24", + "IR-25", + "IR-26", + "IR-27", + "IR-28", + "IR-29", + "IR-30", + "IS-1", + "IS-2", + "IS-3", + "IS-4", + "IS-5", + "IS-6", + "IS-7", + "IS-8", + "IS-AKH", + "IS-AKN", + "IS-AKU", + "IS-ARN", + "IS-ASA", + "IS-BFJ", + "IS-BLA", + "IS-BLO", + "IS-BOG", + "IS-BOL", + "IS-DAB", + "IS-DAV", + "IS-DJU", + "IS-EOM", + "IS-EYF", + "IS-FJD", + "IS-FJL", + "IS-FLA", + "IS-FLD", + "IS-FLR", + "IS-GAR", + "IS-GOG", + "IS-GRN", + "IS-GRU", + "IS-GRY", + "IS-HAF", + "IS-HEL", + "IS-HRG", + "IS-HRU", + "IS-HUT", + "IS-HUV", + "IS-HVA", + "IS-HVE", + "IS-ISA", + "IS-KAL", + "IS-KJO", + "IS-KOP", + "IS-LAN", + "IS-MOS", + "IS-MYR", + "IS-NOR", + "IS-RGE", + "IS-RGY", + "IS-RHH", + "IS-RKN", + "IS-RKV", + "IS-SBH", + "IS-SBT", + "IS-SDN", + "IS-SDV", + "IS-SEL", + "IS-SEY", + "IS-SFA", + "IS-SHF", + "IS-SKF", + "IS-SKG", + "IS-SKO", + "IS-SKU", + "IS-SNF", + "IS-SOG", + "IS-SOL", + "IS-SSF", + "IS-SSS", + "IS-STR", + "IS-STY", + "IS-SVG", + "IS-TAL", + "IS-THG", + "IS-TJO", + "IS-VEM", + "IS-VER", + "IS-VOP", + "IT-21", + "IT-23", + "IT-25", + "IT-32", + "IT-34", + "IT-36", + "IT-42", + "IT-45", + "IT-52", + "IT-55", + "IT-57", + "IT-62", + "IT-65", + "IT-67", + "IT-72", + "IT-75", + "IT-77", + "IT-78", + "IT-82", + "IT-88", + "IT-AG", + "IT-AL", + "IT-AN", + "IT-AP", + "IT-AQ", + "IT-AR", + "IT-AT", + "IT-AV", + "IT-BA", + "IT-BG", + "IT-BI", + "IT-BL", + "IT-BN", + "IT-BO", + "IT-BR", + "IT-BS", + "IT-BT", + "IT-BZ", + "IT-CA", + "IT-CB", + "IT-CE", + "IT-CH", + "IT-CL", + "IT-CN", + "IT-CO", + "IT-CR", + "IT-CS", + "IT-CT", + "IT-CZ", + "IT-EN", + "IT-FC", + "IT-FE", + "IT-FG", + "IT-FI", + "IT-FM", + "IT-FR", + "IT-GE", + "IT-GO", + "IT-GR", + "IT-IM", + "IT-IS", + "IT-KR", + "IT-LC", + "IT-LE", + "IT-LI", + "IT-LO", + "IT-LT", + "IT-LU", + "IT-MB", + "IT-MC", + "IT-ME", + "IT-MI", + "IT-MN", + "IT-MO", + "IT-MS", + "IT-MT", + "IT-NA", + "IT-NO", + "IT-NU", + "IT-OR", + "IT-PA", + "IT-PC", + "IT-PD", + "IT-PE", + "IT-PG", + "IT-PI", + "IT-PN", + "IT-PO", + "IT-PR", + "IT-PT", + "IT-PU", + "IT-PV", + "IT-PZ", + "IT-RA", + "IT-RC", + "IT-RE", + "IT-RG", + "IT-RI", + "IT-RM", + "IT-RN", + "IT-RO", + "IT-SA", + "IT-SI", + "IT-SO", + "IT-SP", + "IT-SR", + "IT-SS", + "IT-SU", + "IT-SV", + "IT-TA", + "IT-TE", + "IT-TN", + "IT-TO", + "IT-TP", + "IT-TR", + "IT-TS", + "IT-TV", + "IT-UD", + "IT-VA", + "IT-VB", + "IT-VC", + "IT-VE", + "IT-VI", + "IT-VR", + "IT-VT", + "IT-VV", + "JM-01", + "JM-02", + "JM-03", + "JM-04", + "JM-05", + "JM-06", + "JM-07", + "JM-08", + "JM-09", + "JM-10", + "JM-11", + "JM-12", + "JM-13", + "JM-14", + "JO-AJ", + "JO-AM", + "JO-AQ", + "JO-AT", + "JO-AZ", + "JO-BA", + "JO-IR", + "JO-JA", + "JO-KA", + "JO-MA", + "JO-MD", + "JO-MN", + "JP-01", + "JP-02", + "JP-03", + "JP-04", + "JP-05", + "JP-06", + "JP-07", + "JP-08", + "JP-09", + "JP-10", + "JP-11", + "JP-12", + "JP-13", + "JP-14", + "JP-15", + "JP-16", + "JP-17", + "JP-18", + "JP-19", + "JP-20", + "JP-21", + "JP-22", + "JP-23", + "JP-24", + "JP-25", + "JP-26", + "JP-27", + "JP-28", + "JP-29", + "JP-30", + "JP-31", + "JP-32", + "JP-33", + "JP-34", + "JP-35", + "JP-36", + "JP-37", + "JP-38", + "JP-39", + "JP-40", + "JP-41", + "JP-42", + "JP-43", + "JP-44", + "JP-45", + "JP-46", + "JP-47", + "KE-01", + "KE-02", + "KE-03", + "KE-04", + "KE-05", + "KE-06", + "KE-07", + "KE-08", + "KE-09", + "KE-10", + "KE-11", + "KE-12", + "KE-13", + "KE-14", + "KE-15", + "KE-16", + "KE-17", + "KE-18", + "KE-19", + "KE-20", + "KE-21", + "KE-22", + "KE-23", + "KE-24", + "KE-25", + "KE-26", + "KE-27", + "KE-28", + "KE-29", + "KE-30", + "KE-31", + "KE-32", + "KE-33", + "KE-34", + "KE-35", + "KE-36", + "KE-37", + "KE-38", + "KE-39", + "KE-40", + "KE-41", + "KE-42", + "KE-43", + "KE-44", + "KE-45", + "KE-46", + "KE-47", + "KG-B", + "KG-C", + "KG-GB", + "KG-GO", + "KG-J", + "KG-N", + "KG-O", + "KG-T", + "KG-Y", + "KH-1", + "KH-10", + "KH-11", + "KH-12", + "KH-13", + "KH-14", + "KH-15", + "KH-16", + "KH-17", + "KH-18", + "KH-19", + "KH-2", + "KH-20", + "KH-21", + "KH-22", + "KH-23", + "KH-24", + "KH-25", + "KH-3", + "KH-4", + "KH-5", + "KH-6", + "KH-7", + "KH-8", + "KH-9", + "KI-G", + "KI-L", + "KI-P", + "KM-A", + "KM-G", + "KM-M", + "KN-01", + "KN-02", + "KN-03", + "KN-04", + "KN-05", + "KN-06", + "KN-07", + "KN-08", + "KN-09", + "KN-10", + "KN-11", + "KN-12", + "KN-13", + "KN-15", + "KN-K", + "KN-N", + "KP-01", + "KP-02", + "KP-03", + "KP-04", + "KP-05", + "KP-06", + "KP-07", + "KP-08", + "KP-09", + "KP-10", + "KP-13", + "KP-14", + "KR-11", + "KR-26", + "KR-27", + "KR-28", + "KR-29", + "KR-30", + "KR-31", + "KR-41", + "KR-42", + "KR-43", + "KR-44", + "KR-45", + "KR-46", + "KR-47", + "KR-48", + "KR-49", + "KR-50", + "KW-AH", + "KW-FA", + "KW-HA", + "KW-JA", + "KW-KU", + "KW-MU", + "KZ-AKM", + "KZ-AKT", + "KZ-ALA", + "KZ-ALM", + "KZ-AST", + "KZ-ATY", + "KZ-KAR", + "KZ-KUS", + "KZ-KZY", + "KZ-MAN", + "KZ-PAV", + "KZ-SEV", + "KZ-SHY", + "KZ-VOS", + "KZ-YUZ", + "KZ-ZAP", + "KZ-ZHA", + "LA-AT", + "LA-BK", + "LA-BL", + "LA-CH", + "LA-HO", + "LA-KH", + "LA-LM", + "LA-LP", + "LA-OU", + "LA-PH", + "LA-SL", + "LA-SV", + "LA-VI", + "LA-VT", + "LA-XA", + "LA-XE", + "LA-XI", + "LA-XS", + "LB-AK", + "LB-AS", + "LB-BA", + "LB-BH", + "LB-BI", + "LB-JA", + "LB-JL", + "LB-NA", + "LC-01", + "LC-02", + "LC-03", + "LC-05", + "LC-06", + "LC-07", + "LC-08", + "LC-10", + "LC-11", + "LC-12", + "LI-01", + "LI-02", + "LI-03", + "LI-04", + "LI-05", + "LI-06", + "LI-07", + "LI-08", + "LI-09", + "LI-10", + "LI-11", + "LK-1", + "LK-11", + "LK-12", + "LK-13", + "LK-2", + "LK-21", + "LK-22", + "LK-23", + "LK-3", + "LK-31", + "LK-32", + "LK-33", + "LK-4", + "LK-41", + "LK-42", + "LK-43", + "LK-44", + "LK-45", + "LK-5", + "LK-51", + "LK-52", + "LK-53", + "LK-6", + "LK-61", + "LK-62", + "LK-7", + "LK-71", + "LK-72", + "LK-8", + "LK-81", + "LK-82", + "LK-9", + "LK-91", + "LK-92", + "LR-BG", + "LR-BM", + "LR-CM", + "LR-GB", + "LR-GG", + "LR-GK", + "LR-GP", + "LR-LO", + "LR-MG", + "LR-MO", + "LR-MY", + "LR-NI", + "LR-RG", + "LR-RI", + "LR-SI", + "LS-A", + "LS-B", + "LS-C", + "LS-D", + "LS-E", + "LS-F", + "LS-G", + "LS-H", + "LS-J", + "LS-K", + "LT-01", + "LT-02", + "LT-03", + "LT-04", + "LT-05", + "LT-06", + "LT-07", + "LT-08", + "LT-09", + "LT-10", + "LT-11", + "LT-12", + "LT-13", + "LT-14", + "LT-15", + "LT-16", + "LT-17", + "LT-18", + "LT-19", + "LT-20", + "LT-21", + "LT-22", + "LT-23", + "LT-24", + "LT-25", + "LT-26", + "LT-27", + "LT-28", + "LT-29", + "LT-30", + "LT-31", + "LT-32", + "LT-33", + "LT-34", + "LT-35", + "LT-36", + "LT-37", + "LT-38", + "LT-39", + "LT-40", + "LT-41", + "LT-42", + "LT-43", + "LT-44", + "LT-45", + "LT-46", + "LT-47", + "LT-48", + "LT-49", + "LT-50", + "LT-51", + "LT-52", + "LT-53", + "LT-54", + "LT-55", + "LT-56", + "LT-57", + "LT-58", + "LT-59", + "LT-60", + "LT-AL", + "LT-KL", + "LT-KU", + "LT-MR", + "LT-PN", + "LT-SA", + "LT-TA", + "LT-TE", + "LT-UT", + "LT-VL", + "LU-CA", + "LU-CL", + "LU-DI", + "LU-EC", + "LU-ES", + "LU-GR", + "LU-LU", + "LU-ME", + "LU-RD", + "LU-RM", + "LU-VD", + "LU-WI", + "LV-001", + "LV-002", + "LV-003", + "LV-004", + "LV-005", + "LV-006", + "LV-007", + "LV-008", + "LV-009", + "LV-010", + "LV-011", + "LV-012", + "LV-013", + "LV-014", + "LV-015", + "LV-016", + "LV-017", + "LV-018", + "LV-019", + "LV-020", + "LV-021", + "LV-022", + "LV-023", + "LV-024", + "LV-025", + "LV-026", + "LV-027", + "LV-028", + "LV-029", + "LV-030", + "LV-031", + "LV-032", + "LV-033", + "LV-034", + "LV-035", + "LV-036", + "LV-037", + "LV-038", + "LV-039", + "LV-040", + "LV-041", + "LV-042", + "LV-043", + "LV-044", + "LV-045", + "LV-046", + "LV-047", + "LV-048", + "LV-049", + "LV-050", + "LV-051", + "LV-052", + "LV-053", + "LV-054", + "LV-055", + "LV-056", + "LV-057", + "LV-058", + "LV-059", + "LV-060", + "LV-061", + "LV-062", + "LV-063", + "LV-064", + "LV-065", + "LV-066", + "LV-067", + "LV-068", + "LV-069", + "LV-070", + "LV-071", + "LV-072", + "LV-073", + "LV-074", + "LV-075", + "LV-076", + "LV-077", + "LV-078", + "LV-079", + "LV-080", + "LV-081", + "LV-082", + "LV-083", + "LV-084", + "LV-085", + "LV-086", + "LV-087", + "LV-088", + "LV-089", + "LV-090", + "LV-091", + "LV-092", + "LV-093", + "LV-094", + "LV-095", + "LV-096", + "LV-097", + "LV-098", + "LV-099", + "LV-100", + "LV-101", + "LV-102", + "LV-103", + "LV-104", + "LV-105", + "LV-106", + "LV-107", + "LV-108", + "LV-109", + "LV-110", + "LV-DGV", + "LV-JEL", + "LV-JKB", + "LV-JUR", + "LV-LPX", + "LV-REZ", + "LV-RIX", + "LV-VEN", + "LV-VMR", + "LY-BA", + "LY-BU", + "LY-DR", + "LY-GT", + "LY-JA", + "LY-JG", + "LY-JI", + "LY-JU", + "LY-KF", + "LY-MB", + "LY-MI", + "LY-MJ", + "LY-MQ", + "LY-NL", + "LY-NQ", + "LY-SB", + "LY-SR", + "LY-TB", + "LY-WA", + "LY-WD", + "LY-WS", + "LY-ZA", + "MA-01", + "MA-02", + "MA-03", + "MA-04", + "MA-05", + "MA-06", + "MA-07", + "MA-08", + "MA-09", + "MA-10", + "MA-11", + "MA-12", + "MA-AGD", + "MA-AOU", + "MA-ASZ", + "MA-AZI", + "MA-BEM", + "MA-BER", + "MA-BES", + "MA-BOD", + "MA-BOM", + "MA-BRR", + "MA-CAS", + "MA-CHE", + "MA-CHI", + "MA-CHT", + "MA-DRI", + "MA-ERR", + "MA-ESI", + "MA-ESM", + "MA-FAH", + "MA-FES", + "MA-FIG", + "MA-FQH", + "MA-GUE", + "MA-GUF", + "MA-HAJ", + "MA-HAO", + "MA-HOC", + "MA-IFR", + "MA-INE", + "MA-JDI", + "MA-JRA", + "MA-KEN", + "MA-KES", + "MA-KHE", + "MA-KHN", + "MA-KHO", + "MA-LAA", + "MA-LAR", + "MA-MAR", + "MA-MDF", + "MA-MED", + "MA-MEK", + "MA-MID", + "MA-MOH", + "MA-MOU", + "MA-NAD", + "MA-NOU", + "MA-OUA", + "MA-OUD", + "MA-OUJ", + "MA-OUZ", + "MA-RAB", + "MA-REH", + "MA-SAF", + "MA-SAL", + "MA-SEF", + "MA-SET", + "MA-SIB", + "MA-SIF", + "MA-SIK", + "MA-SIL", + "MA-SKH", + "MA-TAF", + "MA-TAI", + "MA-TAO", + "MA-TAR", + "MA-TAT", + "MA-TAZ", + "MA-TET", + "MA-TIN", + "MA-TIZ", + "MA-TNG", + "MA-TNT", + "MA-YUS", + "MA-ZAG", + "MC-CL", + "MC-CO", + "MC-FO", + "MC-GA", + "MC-JE", + "MC-LA", + "MC-MA", + "MC-MC", + "MC-MG", + "MC-MO", + "MC-MU", + "MC-PH", + "MC-SD", + "MC-SO", + "MC-SP", + "MC-SR", + "MC-VR", + "MD-AN", + "MD-BA", + "MD-BD", + "MD-BR", + "MD-BS", + "MD-CA", + "MD-CL", + "MD-CM", + "MD-CR", + "MD-CS", + "MD-CT", + "MD-CU", + "MD-DO", + "MD-DR", + "MD-DU", + "MD-ED", + "MD-FA", + "MD-FL", + "MD-GA", + "MD-GL", + "MD-HI", + "MD-IA", + "MD-LE", + "MD-NI", + "MD-OC", + "MD-OR", + "MD-RE", + "MD-RI", + "MD-SD", + "MD-SI", + "MD-SN", + "MD-SO", + "MD-ST", + "MD-SV", + "MD-TA", + "MD-TE", + "MD-UN", + "ME-01", + "ME-02", + "ME-03", + "ME-04", + "ME-05", + "ME-06", + "ME-07", + "ME-08", + "ME-09", + "ME-10", + "ME-11", + "ME-12", + "ME-13", + "ME-14", + "ME-15", + "ME-16", + "ME-17", + "ME-18", + "ME-19", + "ME-20", + "ME-21", + "ME-22", + "ME-23", + "ME-24", + "MG-A", + "MG-D", + "MG-F", + "MG-M", + "MG-T", + "MG-U", + "MH-ALK", + "MH-ALL", + "MH-ARN", + "MH-AUR", + "MH-EBO", + "MH-ENI", + "MH-JAB", + "MH-JAL", + "MH-KIL", + "MH-KWA", + "MH-L", + "MH-LAE", + "MH-LIB", + "MH-LIK", + "MH-MAJ", + "MH-MAL", + "MH-MEJ", + "MH-MIL", + "MH-NMK", + "MH-NMU", + "MH-RON", + "MH-T", + "MH-UJA", + "MH-UTI", + "MH-WTH", + "MH-WTJ", + "MK-101", + "MK-102", + "MK-103", + "MK-104", + "MK-105", + "MK-106", + "MK-107", + "MK-108", + "MK-109", + "MK-201", + "MK-202", + "MK-203", + "MK-204", + "MK-205", + "MK-206", + "MK-207", + "MK-208", + "MK-209", + "MK-210", + "MK-211", + "MK-301", + "MK-303", + "MK-304", + "MK-307", + "MK-308", + "MK-310", + "MK-311", + "MK-312", + "MK-313", + "MK-401", + "MK-402", + "MK-403", + "MK-404", + "MK-405", + "MK-406", + "MK-407", + "MK-408", + "MK-409", + "MK-410", + "MK-501", + "MK-502", + "MK-503", + "MK-504", + "MK-505", + "MK-506", + "MK-507", + "MK-508", + "MK-509", + "MK-601", + "MK-602", + "MK-603", + "MK-604", + "MK-605", + "MK-606", + "MK-607", + "MK-608", + "MK-609", + "MK-701", + "MK-702", + "MK-703", + "MK-704", + "MK-705", + "MK-706", + "MK-801", + "MK-802", + "MK-803", + "MK-804", + "MK-805", + "MK-806", + "MK-807", + "MK-808", + "MK-809", + "MK-810", + "MK-811", + "MK-812", + "MK-813", + "MK-814", + "MK-815", + "MK-816", + "MK-817", + "ML-1", + "ML-10", + "ML-2", + "ML-3", + "ML-4", + "ML-5", + "ML-6", + "ML-7", + "ML-8", + "ML-9", + "ML-BKO", + "MM-01", + "MM-02", + "MM-03", + "MM-04", + "MM-05", + "MM-06", + "MM-07", + "MM-11", + "MM-12", + "MM-13", + "MM-14", + "MM-15", + "MM-16", + "MM-17", + "MM-18", + "MN-035", + "MN-037", + "MN-039", + "MN-041", + "MN-043", + "MN-046", + "MN-047", + "MN-049", + "MN-051", + "MN-053", + "MN-055", + "MN-057", + "MN-059", + "MN-061", + "MN-063", + "MN-064", + "MN-065", + "MN-067", + "MN-069", + "MN-071", + "MN-073", + "MN-1", + "MR-01", + "MR-02", + "MR-03", + "MR-04", + "MR-05", + "MR-06", + "MR-07", + "MR-08", + "MR-09", + "MR-10", + "MR-11", + "MR-12", + "MR-13", + "MR-14", + "MR-15", + "MT-01", + "MT-02", + "MT-03", + "MT-04", + "MT-05", + "MT-06", + "MT-07", + "MT-08", + "MT-09", + "MT-10", + "MT-11", + "MT-12", + "MT-13", + "MT-14", + "MT-15", + "MT-16", + "MT-17", + "MT-18", + "MT-19", + "MT-20", + "MT-21", + "MT-22", + "MT-23", + "MT-24", + "MT-25", + "MT-26", + "MT-27", + "MT-28", + "MT-29", + "MT-30", + "MT-31", + "MT-32", + "MT-33", + "MT-34", + "MT-35", + "MT-36", + "MT-37", + "MT-38", + "MT-39", + "MT-40", + "MT-41", + "MT-42", + "MT-43", + "MT-44", + "MT-45", + "MT-46", + "MT-47", + "MT-48", + "MT-49", + "MT-50", + "MT-51", + "MT-52", + "MT-53", + "MT-54", + "MT-55", + "MT-56", + "MT-57", + "MT-58", + "MT-59", + "MT-60", + "MT-61", + "MT-62", + "MT-63", + "MT-64", + "MT-65", + "MT-66", + "MT-67", + "MT-68", + "MU-AG", + "MU-BL", + "MU-CC", + "MU-FL", + "MU-GP", + "MU-MO", + "MU-PA", + "MU-PL", + "MU-PW", + "MU-RO", + "MU-RR", + "MU-SA", + "MV-00", + "MV-01", + "MV-02", + "MV-03", + "MV-04", + "MV-05", + "MV-07", + "MV-08", + "MV-12", + "MV-13", + "MV-14", + "MV-17", + "MV-20", + "MV-23", + "MV-24", + "MV-25", + "MV-26", + "MV-27", + "MV-28", + "MV-29", + "MV-MLE", + "MW-BA", + "MW-BL", + "MW-C", + "MW-CK", + "MW-CR", + "MW-CT", + "MW-DE", + "MW-DO", + "MW-KR", + "MW-KS", + "MW-LI", + "MW-LK", + "MW-MC", + "MW-MG", + "MW-MH", + "MW-MU", + "MW-MW", + "MW-MZ", + "MW-N", + "MW-NB", + "MW-NE", + "MW-NI", + "MW-NK", + "MW-NS", + "MW-NU", + "MW-PH", + "MW-RU", + "MW-S", + "MW-SA", + "MW-TH", + "MW-ZO", + "MX-AGU", + "MX-BCN", + "MX-BCS", + "MX-CAM", + "MX-CHH", + "MX-CHP", + "MX-CMX", + "MX-COA", + "MX-COL", + "MX-DUR", + "MX-GRO", + "MX-GUA", + "MX-HID", + "MX-JAL", + "MX-MEX", + "MX-MIC", + "MX-MOR", + "MX-NAY", + "MX-NLE", + "MX-OAX", + "MX-PUE", + "MX-QUE", + "MX-ROO", + "MX-SIN", + "MX-SLP", + "MX-SON", + "MX-TAB", + "MX-TAM", + "MX-TLA", + "MX-VER", + "MX-YUC", + "MX-ZAC", + "MY-01", + "MY-02", + "MY-03", + "MY-04", + "MY-05", + "MY-06", + "MY-07", + "MY-08", + "MY-09", + "MY-10", + "MY-11", + "MY-12", + "MY-13", + "MY-14", + "MY-15", + "MY-16", + "MZ-A", + "MZ-B", + "MZ-G", + "MZ-I", + "MZ-L", + "MZ-MPM", + "MZ-N", + "MZ-P", + "MZ-Q", + "MZ-S", + "MZ-T", + "NA-CA", + "NA-ER", + "NA-HA", + "NA-KA", + "NA-KE", + "NA-KH", + "NA-KU", + "NA-KW", + "NA-OD", + "NA-OH", + "NA-ON", + "NA-OS", + "NA-OT", + "NA-OW", + "NE-1", + "NE-2", + "NE-3", + "NE-4", + "NE-5", + "NE-6", + "NE-7", + "NE-8", + "NG-AB", + "NG-AD", + "NG-AK", + "NG-AN", + "NG-BA", + "NG-BE", + "NG-BO", + "NG-BY", + "NG-CR", + "NG-DE", + "NG-EB", + "NG-ED", + "NG-EK", + "NG-EN", + "NG-FC", + "NG-GO", + "NG-IM", + "NG-JI", + "NG-KD", + "NG-KE", + "NG-KN", + "NG-KO", + "NG-KT", + "NG-KW", + "NG-LA", + "NG-NA", + "NG-NI", + "NG-OG", + "NG-ON", + "NG-OS", + "NG-OY", + "NG-PL", + "NG-RI", + "NG-SO", + "NG-TA", + "NG-YO", + "NG-ZA", + "NI-AN", + "NI-AS", + "NI-BO", + "NI-CA", + "NI-CI", + "NI-CO", + "NI-ES", + "NI-GR", + "NI-JI", + "NI-LE", + "NI-MD", + "NI-MN", + "NI-MS", + "NI-MT", + "NI-NS", + "NI-RI", + "NI-SJ", + "NL-AW", + "NL-BQ1", + "NL-BQ2", + "NL-BQ3", + "NL-CW", + "NL-DR", + "NL-FL", + "NL-FR", + "NL-GE", + "NL-GR", + "NL-LI", + "NL-NB", + "NL-NH", + "NL-OV", + "NL-SX", + "NL-UT", + "NL-ZE", + "NL-ZH", + "NO-03", + "NO-11", + "NO-15", + "NO-18", + "NO-21", + "NO-22", + "NO-30", + "NO-34", + "NO-38", + "NO-42", + "NO-46", + "NO-50", + "NO-54", + "NP-1", + "NP-2", + "NP-3", + "NP-4", + "NP-5", + "NP-BA", + "NP-BH", + "NP-DH", + "NP-GA", + "NP-JA", + "NP-KA", + "NP-KO", + "NP-LU", + "NP-MA", + "NP-ME", + "NP-NA", + "NP-P1", + "NP-P2", + "NP-P3", + "NP-P4", + "NP-P5", + "NP-P6", + "NP-P7", + "NP-RA", + "NP-SA", + "NP-SE", + "NR-01", + "NR-02", + "NR-03", + "NR-04", + "NR-05", + "NR-06", + "NR-07", + "NR-08", + "NR-09", + "NR-10", + "NR-11", + "NR-12", + "NR-13", + "NR-14", + "NZ-AUK", + "NZ-BOP", + "NZ-CAN", + "NZ-CIT", + "NZ-GIS", + "NZ-HKB", + "NZ-MBH", + "NZ-MWT", + "NZ-NSN", + "NZ-NTL", + "NZ-OTA", + "NZ-STL", + "NZ-TAS", + "NZ-TKI", + "NZ-WGN", + "NZ-WKO", + "NZ-WTC", + "OM-BJ", + "OM-BS", + "OM-BU", + "OM-DA", + "OM-MA", + "OM-MU", + "OM-SJ", + "OM-SS", + "OM-WU", + "OM-ZA", + "OM-ZU", + "PA-1", + "PA-10", + "PA-2", + "PA-3", + "PA-4", + "PA-5", + "PA-6", + "PA-7", + "PA-8", + "PA-9", + "PA-EM", + "PA-KY", + "PA-NB", + "PE-AMA", + "PE-ANC", + "PE-APU", + "PE-ARE", + "PE-AYA", + "PE-CAJ", + "PE-CAL", + "PE-CUS", + "PE-HUC", + "PE-HUV", + "PE-ICA", + "PE-JUN", + "PE-LAL", + "PE-LAM", + "PE-LIM", + "PE-LMA", + "PE-LOR", + "PE-MDD", + "PE-MOQ", + "PE-PAS", + "PE-PIU", + "PE-PUN", + "PE-SAM", + "PE-TAC", + "PE-TUM", + "PE-UCA", + "PG-CPK", + "PG-CPM", + "PG-EBR", + "PG-EHG", + "PG-EPW", + "PG-ESW", + "PG-GPK", + "PG-HLA", + "PG-JWK", + "PG-MBA", + "PG-MPL", + "PG-MPM", + "PG-MRL", + "PG-NCD", + "PG-NIK", + "PG-NPP", + "PG-NSB", + "PG-SAN", + "PG-SHM", + "PG-WBK", + "PG-WHM", + "PG-WPD", + "PH-00", + "PH-01", + "PH-02", + "PH-03", + "PH-05", + "PH-06", + "PH-07", + "PH-08", + "PH-09", + "PH-10", + "PH-11", + "PH-12", + "PH-13", + "PH-14", + "PH-15", + "PH-40", + "PH-41", + "PH-ABR", + "PH-AGN", + "PH-AGS", + "PH-AKL", + "PH-ALB", + "PH-ANT", + "PH-APA", + "PH-AUR", + "PH-BAN", + "PH-BAS", + "PH-BEN", + "PH-BIL", + "PH-BOH", + "PH-BTG", + "PH-BTN", + "PH-BUK", + "PH-BUL", + "PH-CAG", + "PH-CAM", + "PH-CAN", + "PH-CAP", + "PH-CAS", + "PH-CAT", + "PH-CAV", + "PH-CEB", + "PH-COM", + "PH-DAO", + "PH-DAS", + "PH-DAV", + "PH-DIN", + "PH-DVO", + "PH-EAS", + "PH-GUI", + "PH-IFU", + "PH-ILI", + "PH-ILN", + "PH-ILS", + "PH-ISA", + "PH-KAL", + "PH-LAG", + "PH-LAN", + "PH-LAS", + "PH-LEY", + "PH-LUN", + "PH-MAD", + "PH-MAG", + "PH-MAS", + "PH-MDC", + "PH-MDR", + "PH-MOU", + "PH-MSC", + "PH-MSR", + "PH-NCO", + "PH-NEC", + "PH-NER", + "PH-NSA", + "PH-NUE", + "PH-NUV", + "PH-PAM", + "PH-PAN", + "PH-PLW", + "PH-QUE", + "PH-QUI", + "PH-RIZ", + "PH-ROM", + "PH-SAR", + "PH-SCO", + "PH-SIG", + "PH-SLE", + "PH-SLU", + "PH-SOR", + "PH-SUK", + "PH-SUN", + "PH-SUR", + "PH-TAR", + "PH-TAW", + "PH-WSA", + "PH-ZAN", + "PH-ZAS", + "PH-ZMB", + "PH-ZSI", + "PK-BA", + "PK-GB", + "PK-IS", + "PK-JK", + "PK-KP", + "PK-PB", + "PK-SD", + "PK-TA", + "PL-02", + "PL-04", + "PL-06", + "PL-08", + "PL-10", + "PL-12", + "PL-14", + "PL-16", + "PL-18", + "PL-20", + "PL-22", + "PL-24", + "PL-26", + "PL-28", + "PL-30", + "PL-32", + "PS-BTH", + "PS-DEB", + "PS-GZA", + "PS-HBN", + "PS-JEM", + "PS-JEN", + "PS-JRH", + "PS-KYS", + "PS-NBS", + "PS-NGZ", + "PS-QQA", + "PS-RBH", + "PS-RFH", + "PS-SLT", + "PS-TBS", + "PS-TKM", + "PT-01", + "PT-02", + "PT-03", + "PT-04", + "PT-05", + "PT-06", + "PT-07", + "PT-08", + "PT-09", + "PT-10", + "PT-11", + "PT-12", + "PT-13", + "PT-14", + "PT-15", + "PT-16", + "PT-17", + "PT-18", + "PT-20", + "PT-30", + "PW-002", + "PW-004", + "PW-010", + "PW-050", + "PW-100", + "PW-150", + "PW-212", + "PW-214", + "PW-218", + "PW-222", + "PW-224", + "PW-226", + "PW-227", + "PW-228", + "PW-350", + "PW-370", + "PY-1", + "PY-10", + "PY-11", + "PY-12", + "PY-13", + "PY-14", + "PY-15", + "PY-16", + "PY-19", + "PY-2", + "PY-3", + "PY-4", + "PY-5", + "PY-6", + "PY-7", + "PY-8", + "PY-9", + "PY-ASU", + "QA-DA", + "QA-KH", + "QA-MS", + "QA-RA", + "QA-SH", + "QA-US", + "QA-WA", + "QA-ZA", + "RO-AB", + "RO-AG", + "RO-AR", + "RO-B", + "RO-BC", + "RO-BH", + "RO-BN", + "RO-BR", + "RO-BT", + "RO-BV", + "RO-BZ", + "RO-CJ", + "RO-CL", + "RO-CS", + "RO-CT", + "RO-CV", + "RO-DB", + "RO-DJ", + "RO-GJ", + "RO-GL", + "RO-GR", + "RO-HD", + "RO-HR", + "RO-IF", + "RO-IL", + "RO-IS", + "RO-MH", + "RO-MM", + "RO-MS", + "RO-NT", + "RO-OT", + "RO-PH", + "RO-SB", + "RO-SJ", + "RO-SM", + "RO-SV", + "RO-TL", + "RO-TM", + "RO-TR", + "RO-VL", + "RO-VN", + "RO-VS", + "RS-00", + "RS-01", + "RS-02", + "RS-03", + "RS-04", + "RS-05", + "RS-06", + "RS-07", + "RS-08", + "RS-09", + "RS-10", + "RS-11", + "RS-12", + "RS-13", + "RS-14", + "RS-15", + "RS-16", + "RS-17", + "RS-18", + "RS-19", + "RS-20", + "RS-21", + "RS-22", + "RS-23", + "RS-24", + "RS-25", + "RS-26", + "RS-27", + "RS-28", + "RS-29", + "RS-KM", + "RS-VO", + "RU-AD", + "RU-AL", + "RU-ALT", + "RU-AMU", + "RU-ARK", + "RU-AST", + "RU-BA", + "RU-BEL", + "RU-BRY", + "RU-BU", + "RU-CE", + "RU-CHE", + "RU-CHU", + "RU-CU", + "RU-DA", + "RU-IN", + "RU-IRK", + "RU-IVA", + "RU-KAM", + "RU-KB", + "RU-KC", + "RU-KDA", + "RU-KEM", + "RU-KGD", + "RU-KGN", + "RU-KHA", + "RU-KHM", + "RU-KIR", + "RU-KK", + "RU-KL", + "RU-KLU", + "RU-KO", + "RU-KOS", + "RU-KR", + "RU-KRS", + "RU-KYA", + "RU-LEN", + "RU-LIP", + "RU-MAG", + "RU-ME", + "RU-MO", + "RU-MOS", + "RU-MOW", + "RU-MUR", + "RU-NEN", + "RU-NGR", + "RU-NIZ", + "RU-NVS", + "RU-OMS", + "RU-ORE", + "RU-ORL", + "RU-PER", + "RU-PNZ", + "RU-PRI", + "RU-PSK", + "RU-ROS", + "RU-RYA", + "RU-SA", + "RU-SAK", + "RU-SAM", + "RU-SAR", + "RU-SE", + "RU-SMO", + "RU-SPE", + "RU-STA", + "RU-SVE", + "RU-TA", + "RU-TAM", + "RU-TOM", + "RU-TUL", + "RU-TVE", + "RU-TY", + "RU-TYU", + "RU-UD", + "RU-ULY", + "RU-VGG", + "RU-VLA", + "RU-VLG", + "RU-VOR", + "RU-YAN", + "RU-YAR", + "RU-YEV", + "RU-ZAB", + "RW-01", + "RW-02", + "RW-03", + "RW-04", + "RW-05", + "SA-01", + "SA-02", + "SA-03", + "SA-04", + "SA-05", + "SA-06", + "SA-07", + "SA-08", + "SA-09", + "SA-10", + "SA-11", + "SA-12", + "SA-14", + "SB-CE", + "SB-CH", + "SB-CT", + "SB-GU", + "SB-IS", + "SB-MK", + "SB-ML", + "SB-RB", + "SB-TE", + "SB-WE", + "SC-01", + "SC-02", + "SC-03", + "SC-04", + "SC-05", + "SC-06", + "SC-07", + "SC-08", + "SC-09", + "SC-10", + "SC-11", + "SC-12", + "SC-13", + "SC-14", + "SC-15", + "SC-16", + "SC-17", + "SC-18", + "SC-19", + "SC-20", + "SC-21", + "SC-22", + "SC-23", + "SC-24", + "SC-25", + "SC-26", + "SC-27", + "SD-DC", + "SD-DE", + "SD-DN", + "SD-DS", + "SD-DW", + "SD-GD", + "SD-GK", + "SD-GZ", + "SD-KA", + "SD-KH", + "SD-KN", + "SD-KS", + "SD-NB", + "SD-NO", + "SD-NR", + "SD-NW", + "SD-RS", + "SD-SI", + "SE-AB", + "SE-AC", + "SE-BD", + "SE-C", + "SE-D", + "SE-E", + "SE-F", + "SE-G", + "SE-H", + "SE-I", + "SE-K", + "SE-M", + "SE-N", + "SE-O", + "SE-S", + "SE-T", + "SE-U", + "SE-W", + "SE-X", + "SE-Y", + "SE-Z", + "SG-01", + "SG-02", + "SG-03", + "SG-04", + "SG-05", + "SH-AC", + "SH-HL", + "SH-TA", + "SI-001", + "SI-002", + "SI-003", + "SI-004", + "SI-005", + "SI-006", + "SI-007", + "SI-008", + "SI-009", + "SI-010", + "SI-011", + "SI-012", + "SI-013", + "SI-014", + "SI-015", + "SI-016", + "SI-017", + "SI-018", + "SI-019", + "SI-020", + "SI-021", + "SI-022", + "SI-023", + "SI-024", + "SI-025", + "SI-026", + "SI-027", + "SI-028", + "SI-029", + "SI-030", + "SI-031", + "SI-032", + "SI-033", + "SI-034", + "SI-035", + "SI-036", + "SI-037", + "SI-038", + "SI-039", + "SI-040", + "SI-041", + "SI-042", + "SI-043", + "SI-044", + "SI-045", + "SI-046", + "SI-047", + "SI-048", + "SI-049", + "SI-050", + "SI-051", + "SI-052", + "SI-053", + "SI-054", + "SI-055", + "SI-056", + "SI-057", + "SI-058", + "SI-059", + "SI-060", + "SI-061", + "SI-062", + "SI-063", + "SI-064", + "SI-065", + "SI-066", + "SI-067", + "SI-068", + "SI-069", + "SI-070", + "SI-071", + "SI-072", + "SI-073", + "SI-074", + "SI-075", + "SI-076", + "SI-077", + "SI-078", + "SI-079", + "SI-080", + "SI-081", + "SI-082", + "SI-083", + "SI-084", + "SI-085", + "SI-086", + "SI-087", + "SI-088", + "SI-089", + "SI-090", + "SI-091", + "SI-092", + "SI-093", + "SI-094", + "SI-095", + "SI-096", + "SI-097", + "SI-098", + "SI-099", + "SI-100", + "SI-101", + "SI-102", + "SI-103", + "SI-104", + "SI-105", + "SI-106", + "SI-107", + "SI-108", + "SI-109", + "SI-110", + "SI-111", + "SI-112", + "SI-113", + "SI-114", + "SI-115", + "SI-116", + "SI-117", + "SI-118", + "SI-119", + "SI-120", + "SI-121", + "SI-122", + "SI-123", + "SI-124", + "SI-125", + "SI-126", + "SI-127", + "SI-128", + "SI-129", + "SI-130", + "SI-131", + "SI-132", + "SI-133", + "SI-134", + "SI-135", + "SI-136", + "SI-137", + "SI-138", + "SI-139", + "SI-140", + "SI-141", + "SI-142", + "SI-143", + "SI-144", + "SI-146", + "SI-147", + "SI-148", + "SI-149", + "SI-150", + "SI-151", + "SI-152", + "SI-153", + "SI-154", + "SI-155", + "SI-156", + "SI-157", + "SI-158", + "SI-159", + "SI-160", + "SI-161", + "SI-162", + "SI-163", + "SI-164", + "SI-165", + "SI-166", + "SI-167", + "SI-168", + "SI-169", + "SI-170", + "SI-171", + "SI-172", + "SI-173", + "SI-174", + "SI-175", + "SI-176", + "SI-177", + "SI-178", + "SI-179", + "SI-180", + "SI-181", + "SI-182", + "SI-183", + "SI-184", + "SI-185", + "SI-186", + "SI-187", + "SI-188", + "SI-189", + "SI-190", + "SI-191", + "SI-192", + "SI-193", + "SI-194", + "SI-195", + "SI-196", + "SI-197", + "SI-198", + "SI-199", + "SI-200", + "SI-201", + "SI-202", + "SI-203", + "SI-204", + "SI-205", + "SI-206", + "SI-207", + "SI-208", + "SI-209", + "SI-210", + "SI-211", + "SI-212", + "SI-213", + "SK-BC", + "SK-BL", + "SK-KI", + "SK-NI", + "SK-PV", + "SK-TA", + "SK-TC", + "SK-ZI", + "SL-E", + "SL-N", + "SL-NW", + "SL-S", + "SL-W", + "SM-01", + "SM-02", + "SM-03", + "SM-04", + "SM-05", + "SM-06", + "SM-07", + "SM-08", + "SM-09", + "SN-DB", + "SN-DK", + "SN-FK", + "SN-KA", + "SN-KD", + "SN-KE", + "SN-KL", + "SN-LG", + "SN-MT", + "SN-SE", + "SN-SL", + "SN-TC", + "SN-TH", + "SN-ZG", + "SO-AW", + "SO-BK", + "SO-BN", + "SO-BR", + "SO-BY", + "SO-GA", + "SO-GE", + "SO-HI", + "SO-JD", + "SO-JH", + "SO-MU", + "SO-NU", + "SO-SA", + "SO-SD", + "SO-SH", + "SO-SO", + "SO-TO", + "SO-WO", + "SR-BR", + "SR-CM", + "SR-CR", + "SR-MA", + "SR-NI", + "SR-PM", + "SR-PR", + "SR-SA", + "SR-SI", + "SR-WA", + "SS-BN", + "SS-BW", + "SS-EC", + "SS-EE", + "SS-EW", + "SS-JG", + "SS-LK", + "SS-NU", + "SS-UY", + "SS-WR", + "ST-01", + "ST-02", + "ST-03", + "ST-04", + "ST-05", + "ST-06", + "ST-P", + "SV-AH", + "SV-CA", + "SV-CH", + "SV-CU", + "SV-LI", + "SV-MO", + "SV-PA", + "SV-SA", + "SV-SM", + "SV-SO", + "SV-SS", + "SV-SV", + "SV-UN", + "SV-US", + "SY-DI", + "SY-DR", + "SY-DY", + "SY-HA", + "SY-HI", + "SY-HL", + "SY-HM", + "SY-ID", + "SY-LA", + "SY-QU", + "SY-RA", + "SY-RD", + "SY-SU", + "SY-TA", + "SZ-HH", + "SZ-LU", + "SZ-MA", + "SZ-SH", + "TD-BA", + "TD-BG", + "TD-BO", + "TD-CB", + "TD-EE", + "TD-EO", + "TD-GR", + "TD-HL", + "TD-KA", + "TD-LC", + "TD-LO", + "TD-LR", + "TD-MA", + "TD-MC", + "TD-ME", + "TD-MO", + "TD-ND", + "TD-OD", + "TD-SA", + "TD-SI", + "TD-TA", + "TD-TI", + "TD-WF", + "TG-C", + "TG-K", + "TG-M", + "TG-P", + "TG-S", + "TH-10", + "TH-11", + "TH-12", + "TH-13", + "TH-14", + "TH-15", + "TH-16", + "TH-17", + "TH-18", + "TH-19", + "TH-20", + "TH-21", + "TH-22", + "TH-23", + "TH-24", + "TH-25", + "TH-26", + "TH-27", + "TH-30", + "TH-31", + "TH-32", + "TH-33", + "TH-34", + "TH-35", + "TH-36", + "TH-37", + "TH-38", + "TH-39", + "TH-40", + "TH-41", + "TH-42", + "TH-43", + "TH-44", + "TH-45", + "TH-46", + "TH-47", + "TH-48", + "TH-49", + "TH-50", + "TH-51", + "TH-52", + "TH-53", + "TH-54", + "TH-55", + "TH-56", + "TH-57", + "TH-58", + "TH-60", + "TH-61", + "TH-62", + "TH-63", + "TH-64", + "TH-65", + "TH-66", + "TH-67", + "TH-70", + "TH-71", + "TH-72", + "TH-73", + "TH-74", + "TH-75", + "TH-76", + "TH-77", + "TH-80", + "TH-81", + "TH-82", + "TH-83", + "TH-84", + "TH-85", + "TH-86", + "TH-90", + "TH-91", + "TH-92", + "TH-93", + "TH-94", + "TH-95", + "TH-96", + "TH-S", + "TJ-DU", + "TJ-GB", + "TJ-KT", + "TJ-RA", + "TJ-SU", + "TL-AL", + "TL-AN", + "TL-BA", + "TL-BO", + "TL-CO", + "TL-DI", + "TL-ER", + "TL-LA", + "TL-LI", + "TL-MF", + "TL-MT", + "TL-OE", + "TL-VI", + "TM-A", + "TM-B", + "TM-D", + "TM-L", + "TM-M", + "TM-S", + "TN-11", + "TN-12", + "TN-13", + "TN-14", + "TN-21", + "TN-22", + "TN-23", + "TN-31", + "TN-32", + "TN-33", + "TN-34", + "TN-41", + "TN-42", + "TN-43", + "TN-51", + "TN-52", + "TN-53", + "TN-61", + "TN-71", + "TN-72", + "TN-73", + "TN-81", + "TN-82", + "TN-83", + "TO-01", + "TO-02", + "TO-03", + "TO-04", + "TO-05", + "TR-01", + "TR-02", + "TR-03", + "TR-04", + "TR-05", + "TR-06", + "TR-07", + "TR-08", + "TR-09", + "TR-10", + "TR-11", + "TR-12", + "TR-13", + "TR-14", + "TR-15", + "TR-16", + "TR-17", + "TR-18", + "TR-19", + "TR-20", + "TR-21", + "TR-22", + "TR-23", + "TR-24", + "TR-25", + "TR-26", + "TR-27", + "TR-28", + "TR-29", + "TR-30", + "TR-31", + "TR-32", + "TR-33", + "TR-34", + "TR-35", + "TR-36", + "TR-37", + "TR-38", + "TR-39", + "TR-40", + "TR-41", + "TR-42", + "TR-43", + "TR-44", + "TR-45", + "TR-46", + "TR-47", + "TR-48", + "TR-49", + "TR-50", + "TR-51", + "TR-52", + "TR-53", + "TR-54", + "TR-55", + "TR-56", + "TR-57", + "TR-58", + "TR-59", + "TR-60", + "TR-61", + "TR-62", + "TR-63", + "TR-64", + "TR-65", + "TR-66", + "TR-67", + "TR-68", + "TR-69", + "TR-70", + "TR-71", + "TR-72", + "TR-73", + "TR-74", + "TR-75", + "TR-76", + "TR-77", + "TR-78", + "TR-79", + "TR-80", + "TR-81", + "TT-ARI", + "TT-CHA", + "TT-CTT", + "TT-DMN", + "TT-MRC", + "TT-PED", + "TT-POS", + "TT-PRT", + "TT-PTF", + "TT-SFO", + "TT-SGE", + "TT-SIP", + "TT-SJL", + "TT-TOB", + "TT-TUP", + "TV-FUN", + "TV-NIT", + "TV-NKF", + "TV-NKL", + "TV-NMA", + "TV-NMG", + "TV-NUI", + "TV-VAI", + "TW-CHA", + "TW-CYI", + "TW-CYQ", + "TW-HSQ", + "TW-HSZ", + "TW-HUA", + "TW-ILA", + "TW-KEE", + "TW-KHH", + "TW-KIN", + "TW-LIE", + "TW-MIA", + "TW-NAN", + "TW-NWT", + "TW-PEN", + "TW-PIF", + "TW-TAO", + "TW-TNN", + "TW-TPE", + "TW-TTT", + "TW-TXG", + "TW-YUN", + "TZ-01", + "TZ-02", + "TZ-03", + "TZ-04", + "TZ-05", + "TZ-06", + "TZ-07", + "TZ-08", + "TZ-09", + "TZ-10", + "TZ-11", + "TZ-12", + "TZ-13", + "TZ-14", + "TZ-15", + "TZ-16", + "TZ-17", + "TZ-18", + "TZ-19", + "TZ-20", + "TZ-21", + "TZ-22", + "TZ-23", + "TZ-24", + "TZ-25", + "TZ-26", + "TZ-27", + "TZ-28", + "TZ-29", + "TZ-30", + "TZ-31", + "UA-05", + "UA-07", + "UA-09", + "UA-12", + "UA-14", + "UA-18", + "UA-21", + "UA-23", + "UA-26", + "UA-30", + "UA-32", + "UA-35", + "UA-40", + "UA-43", + "UA-46", + "UA-48", + "UA-51", + "UA-53", + "UA-56", + "UA-59", + "UA-61", + "UA-63", + "UA-65", + "UA-68", + "UA-71", + "UA-74", + "UA-77", + "UG-101", + "UG-102", + "UG-103", + "UG-104", + "UG-105", + "UG-106", + "UG-107", + "UG-108", + "UG-109", + "UG-110", + "UG-111", + "UG-112", + "UG-113", + "UG-114", + "UG-115", + "UG-116", + "UG-117", + "UG-118", + "UG-119", + "UG-120", + "UG-121", + "UG-122", + "UG-123", + "UG-124", + "UG-125", + "UG-126", + "UG-201", + "UG-202", + "UG-203", + "UG-204", + "UG-205", + "UG-206", + "UG-207", + "UG-208", + "UG-209", + "UG-210", + "UG-211", + "UG-212", + "UG-213", + "UG-214", + "UG-215", + "UG-216", + "UG-217", + "UG-218", + "UG-219", + "UG-220", + "UG-221", + "UG-222", + "UG-223", + "UG-224", + "UG-225", + "UG-226", + "UG-227", + "UG-228", + "UG-229", + "UG-230", + "UG-231", + "UG-232", + "UG-233", + "UG-234", + "UG-235", + "UG-236", + "UG-237", + "UG-301", + "UG-302", + "UG-303", + "UG-304", + "UG-305", + "UG-306", + "UG-307", + "UG-308", + "UG-309", + "UG-310", + "UG-311", + "UG-312", + "UG-313", + "UG-314", + "UG-315", + "UG-316", + "UG-317", + "UG-318", + "UG-319", + "UG-320", + "UG-321", + "UG-322", + "UG-323", + "UG-324", + "UG-325", + "UG-326", + "UG-327", + "UG-328", + "UG-329", + "UG-330", + "UG-331", + "UG-332", + "UG-333", + "UG-334", + "UG-335", + "UG-336", + "UG-337", + "UG-401", + "UG-402", + "UG-403", + "UG-404", + "UG-405", + "UG-406", + "UG-407", + "UG-408", + "UG-409", + "UG-410", + "UG-411", + "UG-412", + "UG-413", + "UG-414", + "UG-415", + "UG-416", + "UG-417", + "UG-418", + "UG-419", + "UG-420", + "UG-421", + "UG-422", + "UG-423", + "UG-424", + "UG-425", + "UG-426", + "UG-427", + "UG-428", + "UG-429", + "UG-430", + "UG-431", + "UG-432", + "UG-433", + "UG-434", + "UG-435", + "UG-C", + "UG-E", + "UG-N", + "UG-W", + "UM-67", + "UM-71", + "UM-76", + "UM-79", + "UM-81", + "UM-84", + "UM-86", + "UM-89", + "UM-95", + "US-AK", + "US-AL", + "US-AR", + "US-AS", + "US-AZ", + "US-CA", + "US-CO", + "US-CT", + "US-DC", + "US-DE", + "US-FL", + "US-GA", + "US-GU", + "US-HI", + "US-IA", + "US-ID", + "US-IL", + "US-IN", + "US-KS", + "US-KY", + "US-LA", + "US-MA", + "US-MD", + "US-ME", + "US-MI", + "US-MN", + "US-MO", + "US-MP", + "US-MS", + "US-MT", + "US-NC", + "US-ND", + "US-NE", + "US-NH", + "US-NJ", + "US-NM", + "US-NV", + "US-NY", + "US-OH", + "US-OK", + "US-OR", + "US-PA", + "US-PR", + "US-RI", + "US-SC", + "US-SD", + "US-TN", + "US-TX", + "US-UM", + "US-UT", + "US-VA", + "US-VI", + "US-VT", + "US-WA", + "US-WI", + "US-WV", + "US-WY", + "UY-AR", + "UY-CA", + "UY-CL", + "UY-CO", + "UY-DU", + "UY-FD", + "UY-FS", + "UY-LA", + "UY-MA", + "UY-MO", + "UY-PA", + "UY-RN", + "UY-RO", + "UY-RV", + "UY-SA", + "UY-SJ", + "UY-SO", + "UY-TA", + "UY-TT", + "UZ-AN", + "UZ-BU", + "UZ-FA", + "UZ-JI", + "UZ-NG", + "UZ-NW", + "UZ-QA", + "UZ-QR", + "UZ-SA", + "UZ-SI", + "UZ-SU", + "UZ-TK", + "UZ-TO", + "UZ-XO", + "VC-01", + "VC-02", + "VC-03", + "VC-04", + "VC-05", + "VC-06", + "VE-A", + "VE-B", + "VE-C", + "VE-D", + "VE-E", + "VE-F", + "VE-G", + "VE-H", + "VE-I", + "VE-J", + "VE-K", + "VE-L", + "VE-M", + "VE-N", + "VE-O", + "VE-P", + "VE-R", + "VE-S", + "VE-T", + "VE-U", + "VE-V", + "VE-W", + "VE-X", + "VE-Y", + "VE-Z", + "VN-01", + "VN-02", + "VN-03", + "VN-04", + "VN-05", + "VN-06", + "VN-07", + "VN-09", + "VN-13", + "VN-14", + "VN-18", + "VN-20", + "VN-21", + "VN-22", + "VN-23", + "VN-24", + "VN-25", + "VN-26", + "VN-27", + "VN-28", + "VN-29", + "VN-30", + "VN-31", + "VN-32", + "VN-33", + "VN-34", + "VN-35", + "VN-36", + "VN-37", + "VN-39", + "VN-40", + "VN-41", + "VN-43", + "VN-44", + "VN-45", + "VN-46", + "VN-47", + "VN-49", + "VN-50", + "VN-51", + "VN-52", + "VN-53", + "VN-54", + "VN-55", + "VN-56", + "VN-57", + "VN-58", + "VN-59", + "VN-61", + "VN-63", + "VN-66", + "VN-67", + "VN-68", + "VN-69", + "VN-70", + "VN-71", + "VN-72", + "VN-73", + "VN-CT", + "VN-DN", + "VN-HN", + "VN-HP", + "VN-SG", + "VU-MAP", + "VU-PAM", + "VU-SAM", + "VU-SEE", + "VU-TAE", + "VU-TOB", + "WF-AL", + "WF-SG", + "WF-UV", + "WS-AA", + "WS-AL", + "WS-AT", + "WS-FA", + "WS-GE", + "WS-GI", + "WS-PA", + "WS-SA", + "WS-TU", + "WS-VF", + "WS-VS", + "YE-AB", + "YE-AD", + "YE-AM", + "YE-BA", + "YE-DA", + "YE-DH", + "YE-HD", + "YE-HJ", + "YE-HU", + "YE-IB", + "YE-JA", + "YE-LA", + "YE-MA", + "YE-MR", + "YE-MW", + "YE-RA", + "YE-SA", + "YE-SD", + "YE-SH", + "YE-SN", + "YE-SU", + "YE-TA", + "ZA-EC", + "ZA-FS", + "ZA-GP", + "ZA-KZN", + "ZA-LP", + "ZA-MP", + "ZA-NC", + "ZA-NW", + "ZA-WC", + "ZM-01", + "ZM-02", + "ZM-03", + "ZM-04", + "ZM-05", + "ZM-06", + "ZM-07", + "ZM-08", + "ZM-09", + "ZM-10", + "ZW-BU", + "ZW-HA", + "ZW-MA", + "ZW-MC", + "ZW-ME", + "ZW-MI", + "ZW-MN", + "ZW-MS", + "ZW-MV", + "ZW-MW" + ] + } + } + } + }, + "retentionType": { + "type": "string", + "enum": [ + "STATED_PERIOD", + "LIMITED", + "INDEFINITE", + "OTHER", + "UNSPECIFIED" + ] + }, + "retentionPeriod": { + "type": "number" + }, + "dataProtectionImpactAssessmentLink": { + "type": "string" + }, + "dataProtectionImpactAssessmentStatus": { + "type": "string", + "enum": [ + "LINK", + "NOT_REQUIRED", + "MISSING" + ] + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "values" + ], + "properties": { + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "dataSiloTitles": { + "type": "array", + "items": { + "type": "string" + } + }, + "dataSubjectTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "teamNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "ownerEmails": { + "type": "array", + "items": { + "type": "string" + } + }, + "processingSubPurposes": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "purpose" + ], + "properties": { + "purpose": { + "type": "string", + "enum": [ + "ESSENTIAL", + "ADDITIONAL_FUNCTIONALITY", + "ADVERTISING", + "MARKETING", + "ANALYTICS", + "PERSONALIZATION", + "OPERATION_SECURITY", + "LEGAL", + "TRANSFER", + "SALE", + "HR", + "OTHER", + "UNSPECIFIED" + ] + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + } + }, + "dataSubCategories": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "category" + ], + "properties": { + "category": { + "type": "string", + "enum": [ + "FINANCIAL", + "HEALTH", + "CONTACT", + "LOCATION", + "DEMOGRAPHIC", + "ID", + "ONLINE_ACTIVITY", + "USER_PROFILE", + "SOCIAL_MEDIA", + "CONNECTION", + "TRACKING", + "DEVICE", + "SURVEY", + "OTHER", + "UNSPECIFIED", + "NOT_PERSONAL_DATA", + "INTEGRATION_IDENTIFIER" + ] + } + } + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + ] + } + }, + "saaSCategories": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "purposes": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "trackingType", + "title", + "name" + ], + "properties": { + "trackingType": { + "type": "string" + }, + "title": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "is-active": { + "type": "boolean" + }, + "configurable": { + "type": "boolean" + }, + "display-order": { + "type": "number" + }, + "show-in-privacy-center": { + "type": "boolean" + }, + "show-in-consent-manager": { + "type": "boolean" + }, + "preference-topics": { + "type": "array", + "items": { + "allOf": [ + { + "type": "object", + "required": [ + "type", + "title", + "description" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "SELECT", + "MULTI_SELECT", + "BOOLEAN" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "default-configuration": { + "type": "string" + }, + "show-in-privacy-center": { + "type": "boolean" + }, + "options": { + "type": "array", + "items": { + "type": "object", + "required": [ + "title", + "slug" + ], + "properties": { + "title": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + } + } + } + } + ] + } + }, + "auth-level": { + "type": "string", + "enum": [ + "REQUIRED", + "OPTIONAL", + "ALLOW_OPT_OUT" + ] + }, + "opt-out-signals": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GPC", + "DNT" + ] + } + }, + "default-consent": { + "type": "string", + "enum": [ + "off", + "Auto" + ] + } + } + } + ] + } + } + } +} From 80243c1a3f3eab57d31d08f5230610e674986249 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 12:57:22 -0700 Subject: [PATCH 39/72] filter slashes --- .vscode/settings.json | 1 + node/corepack/v1/pnpm/10.12.4/.corepack | 1 + node/corepack/v1/pnpm/10.12.4/LICENSE | 22 ++ node/corepack/v1/pnpm/10.12.4/README.md | 239 ++++++++++++++++++ node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs | 27 ++ node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs | 5 + node/corepack/v1/pnpm/10.12.4/package.json | 189 ++++++++++++++ src/commands/admin/chunk-csv/command.ts | 10 +- src/commands/admin/chunk-csv/impl.ts | 47 ++-- .../upload-preferences/buildTaskOptions.ts | 3 + .../consent/upload-preferences/command.ts | 10 +- .../consent/upload-preferences/impl.ts | 1 + .../receipts/receiptsState.ts | 21 +- .../consent/upload-preferences/runChild.ts | 3 +- .../consent/upload-preferences/schemaState.ts | 8 +- .../transform/transformCsv.ts | 56 ++-- .../upload/buildInteractiveUploadPlan.ts | 13 +- .../interactivePreferenceUploaderFromPlan.ts | 47 +++- src/constants.ts | 4 +- src/lib/preference-management/codecs.ts | 19 +- .../getPreferenceUpdatesFromRow.ts | 2 +- .../getPreferencesForIdentifiers.ts | 198 +++++++++++---- .../parsePreferenceIdentifiersFromCsv.ts | 45 +++- .../parsePreferenceManagementCsv.ts | 60 +++-- 24 files changed, 885 insertions(+), 146 deletions(-) create mode 100644 node/corepack/v1/pnpm/10.12.4/.corepack create mode 100644 node/corepack/v1/pnpm/10.12.4/LICENSE create mode 100644 node/corepack/v1/pnpm/10.12.4/README.md create mode 100755 node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs create mode 100755 node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs create mode 100644 node/corepack/v1/pnpm/10.12.4/package.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 71259a7b..ceba0cbe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,6 +65,7 @@ "respawned", "respawning", "respawns", + "resumability", "retryable", "sombra", "subdatapoint", diff --git a/node/corepack/v1/pnpm/10.12.4/.corepack b/node/corepack/v1/pnpm/10.12.4/.corepack new file mode 100644 index 00000000..c57f053d --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/.corepack @@ -0,0 +1 @@ +{"locator":{"name":"pnpm","reference":"10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"},"bin":{"pnpm":"./bin/pnpm.cjs","pnpx":"./bin/pnpx.cjs"},"hash":"sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"} \ No newline at end of file diff --git a/node/corepack/v1/pnpm/10.12.4/LICENSE b/node/corepack/v1/pnpm/10.12.4/LICENSE new file mode 100644 index 00000000..a4c8771c --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors +Copyright (c) 2016-2025 Zoltan Kochan and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node/corepack/v1/pnpm/10.12.4/README.md b/node/corepack/v1/pnpm/10.12.4/README.md new file mode 100644 index 00000000..ce020b20 --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/README.md @@ -0,0 +1,239 @@ + + + +## Table of Contents + +- [Platinum Sponsors](#platinum-sponsors) +- [Gold Sponsors](#gold-sponsors) +- [Silver Sponsors](#silver-sponsors) +- [Background](#background) +- [Installation](#installation) +- [Usage](#usage) +- [Benchmark](#benchmark) +- [Support](#support) +- [License](#license) + + + +[简体中文](https://pnpm.io/zh/) | +[日本語](https://pnpm.io/ja/) | +[한국어](https://pnpm.io/ko/) | +[Italiano](https://pnpm.io/it/) | +[Português Brasileiro](https://pnpm.io/pt/) + + + + + pnpm + + +Fast, disk space efficient package manager: + +- **Fast.** Up to 2x faster than the alternatives (see [benchmark](#benchmark)). +- **Efficient.** Files inside `node_modules` are linked from a single content-addressable storage. +- **[Great for monorepos](https://pnpm.io/workspaces).** +- **Strict.** A package can access only dependencies that are specified in its `package.json`. +- **Deterministic.** Has a lockfile called `pnpm-lock.yaml`. +- **Works as a Node.js version manager.** See [pnpm env use](https://pnpm.io/cli/env). +- **Works everywhere.** Supports Windows, Linux, and macOS. +- **Battle-tested.** Used in production by teams of [all sizes](https://pnpm.io/users) since 2016. +- [See the full feature comparison with npm and Yarn](https://pnpm.io/feature-comparison). + +To quote the [Rush](https://rushjs.io/) team: + +> Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we’ve found it to be very fast and reliable. + +[![npm version](https://img.shields.io/npm/v/pnpm.svg?label=latest)](https://github.com/pnpm/pnpm/releases/latest) +[![Join the chat at Discord](https://img.shields.io/discord/731599538665553971.svg)](https://r.pnpm.io/chat) +[![OpenCollective](https://opencollective.com/pnpm/backers/badge.svg)](https://opencollective.com/pnpm) +[![OpenCollective](https://opencollective.com/pnpm/sponsors/badge.svg)](https://opencollective.com/pnpm) +[![X Follow](https://img.shields.io/twitter/follow/pnpmjs.svg?style=social&label=Follow)](https://x.com/intent/follow?screen_name=pnpmjs®ion=follow_link) +[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) + +## Platinum Sponsors + + + + + + + + +
+ Bit + + Bit +
+ +## Gold Sponsors + + + + + + + + + + + + + +
+ + + + + Discord + + + + + + + + CodeRabbit + + + + + + + + Workleap + + +
+ + + + + Stackblitz + + + + + Vite + +
+ +## Silver Sponsors + + + + + + + + + + + + + + + + + +
+ + + + + u|screen + + + + + Leniolabs_ + + + + + + + Depot + + +
+ + + + + devowl.io + + + + + + + + Cerbos + + + + + Vite + +
+ + + + + OOMOL Studio + + +
+ +Support this project by [becoming a sponsor](https://opencollective.com/pnpm#sponsor). + +## Background + +pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. +When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. +With pnpm, lodash will be stored in a content-addressable storage, so: + +1. If you depend on different versions of lodash, only the files that differ are added to the store. + If lodash has 100 files, and a new version has a change only in one of those files, + `pnpm update` will only add 1 new file to the storage. +1. All the files are saved in a single place on the disk. When packages are installed, their files are linked + from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write). + +As a result, you save gigabytes of space on your disk and you have a lot faster installations! +If you'd like more details about the unique `node_modules` structure that pnpm creates and +why it works fine with the Node.js ecosystem, read this small article: [Flat node_modules is not the only way](https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way). + +💖 Like this project? Let people know with a [tweet](https://r.pnpm.io/tweet) + +## Installation + +For installation options [visit our website](https://pnpm.io/installation). + +## Usage + +Just use pnpm in place of npm/Yarn. E.g., install dependencies via: + +``` +pnpm install +``` + +For more advanced usage, read [pnpm CLI](https://pnpm.io/pnpm-cli) on our website, or run `pnpm help`. + +## Benchmark + +pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks [here](https://r.pnpm.io/benchmarks). + +Benchmarks on an app with lots of dependencies: + +![](https://pnpm.io/img/benchmarks/alotta-files.svg) + +## Support + +- [Frequently Asked Questions](https://pnpm.io/faq) +- [Chat](https://r.pnpm.io/chat) +- [X](https://x.com/pnpmjs) +- [Bluesky](https://bsky.app/profile/pnpm.io) + +## License + +[MIT](https://github.com/pnpm/pnpm/blob/main/LICENSE) diff --git a/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs b/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs new file mode 100755 index 00000000..defbe3c4 --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node +const [major, minor] = process.version.slice(1).split('.'); +const COMPATIBILITY_PAGE = `Visit https://r.pnpm.io/comp to see the list of past pnpm versions with respective Node.js version support.`; + +// We don't use the semver library here because: +// 1. it is already bundled to dist/pnpm.cjs, so we would load it twice +// 2. we want this file to support potentially older Node.js versions than what semver supports +if (major < 18 || (major == 18 && minor < 12)) { + console.error(`ERROR: This version of pnpm requires at least Node.js v18.12 +The current version of Node.js is ${process.version} +${COMPATIBILITY_PAGE}`); + process.exit(1); +} + +// We need to load v8-compile-cache.js separately in order to have effect +try { + // Use node.js 22 new API for better performance. + if (!require('module')?.enableCompileCache?.()) require('v8-compile-cache'); +} catch { + // We don't have/need to care about v8-compile-cache failed +} + +global['pnpm__startedAt'] = Date.now(); +require('../dist/pnpm.cjs'); + +// if you want to debug at your local env, you can use this +// require('../lib/pnpm') diff --git a/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs b/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs new file mode 100755 index 00000000..097e7fce --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +process.argv = [...process.argv.slice(0, 2), 'dlx', ...process.argv.slice(2)]; + +require('./pnpm.cjs'); diff --git a/node/corepack/v1/pnpm/10.12.4/package.json b/node/corepack/v1/pnpm/10.12.4/package.json new file mode 100644 index 00000000..8cbaba19 --- /dev/null +++ b/node/corepack/v1/pnpm/10.12.4/package.json @@ -0,0 +1,189 @@ +{ + "name": "pnpm", + "version": "10.12.4", + "description": "Fast, disk space efficient package manager", + "keywords": [ + "pnpm", + "pnpm10", + "dependencies", + "dependency manager", + "efficient", + "fast", + "hardlinks", + "install", + "installer", + "link", + "lockfile", + "modules", + "monorepo", + "multi-package", + "npm", + "package manager", + "package.json", + "packages", + "prune", + "rapid", + "remove", + "shrinkwrap", + "symlinks", + "uninstall", + "workspace" + ], + "license": "MIT", + "funding": "https://opencollective.com/pnpm", + "repository": { + "type": "git", + "url": "git+https://github.com/pnpm/pnpm.git", + "directory": "pnpm" + }, + "homepage": "https://pnpm.io", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "main": "bin/pnpm.cjs", + "exports": { + ".": "./package.json" + }, + "files": [ + "dist", + "bin" + ], + "bin": { + "pnpm": "bin/pnpm.cjs", + "pnpx": "bin/pnpx.cjs" + }, + "directories": { + "test": "test" + }, + "unpkg": "dist/pnpm.cjs", + "__dependencies": { + "v8-compile-cache": "2.4.0" + }, + "__optionalDependencies": { + "node-gyp": "^11.1.0" + }, + "__devDependencies": { + "@pnpm/assert-project": "workspace:*", + "@pnpm/byline": "catalog:", + "@pnpm/cache.commands": "workspace:*", + "@pnpm/cli-meta": "workspace:*", + "@pnpm/cli-utils": "workspace:*", + "@pnpm/client": "workspace:*", + "@pnpm/command": "workspace:*", + "@pnpm/common-cli-options-help": "workspace:*", + "@pnpm/config": "workspace:*", + "@pnpm/constants": "workspace:*", + "@pnpm/core-loggers": "workspace:*", + "@pnpm/crypto.hash": "workspace:*", + "@pnpm/default-reporter": "workspace:*", + "@pnpm/dependency-path": "workspace:*", + "@pnpm/env.path": "workspace:*", + "@pnpm/error": "workspace:*", + "@pnpm/exec.build-commands": "workspace:*", + "@pnpm/filter-workspace-packages": "workspace:*", + "@pnpm/find-workspace-dir": "workspace:*", + "@pnpm/lockfile.types": "workspace:*", + "@pnpm/logger": "workspace:*", + "@pnpm/modules-yaml": "workspace:*", + "@pnpm/nopt": "catalog:", + "@pnpm/parse-cli-args": "workspace:*", + "@pnpm/plugin-commands-audit": "workspace:*", + "@pnpm/plugin-commands-completion": "workspace:*", + "@pnpm/plugin-commands-config": "workspace:*", + "@pnpm/plugin-commands-deploy": "workspace:*", + "@pnpm/plugin-commands-doctor": "workspace:*", + "@pnpm/plugin-commands-env": "workspace:*", + "@pnpm/plugin-commands-init": "workspace:*", + "@pnpm/plugin-commands-installation": "workspace:*", + "@pnpm/plugin-commands-licenses": "workspace:*", + "@pnpm/plugin-commands-listing": "workspace:*", + "@pnpm/plugin-commands-outdated": "workspace:*", + "@pnpm/plugin-commands-patching": "workspace:*", + "@pnpm/plugin-commands-publishing": "workspace:*", + "@pnpm/plugin-commands-rebuild": "workspace:*", + "@pnpm/plugin-commands-script-runners": "workspace:*", + "@pnpm/plugin-commands-server": "workspace:*", + "@pnpm/plugin-commands-setup": "workspace:*", + "@pnpm/plugin-commands-store": "workspace:*", + "@pnpm/plugin-commands-store-inspecting": "workspace:*", + "@pnpm/prepare": "workspace:*", + "@pnpm/read-package-json": "workspace:*", + "@pnpm/read-project-manifest": "workspace:*", + "@pnpm/registry-mock": "catalog:", + "@pnpm/run-npm": "workspace:*", + "@pnpm/store.cafs": "workspace:*", + "@pnpm/tabtab": "catalog:", + "@pnpm/test-fixtures": "workspace:*", + "@pnpm/test-ipc-server": "workspace:*", + "@pnpm/tools.path": "workspace:*", + "@pnpm/tools.plugin-commands-self-updater": "workspace:*", + "@pnpm/types": "workspace:*", + "@pnpm/worker": "workspace:*", + "@pnpm/workspace.find-packages": "workspace:*", + "@pnpm/workspace.pkgs-graph": "workspace:*", + "@pnpm/workspace.read-manifest": "workspace:*", + "@pnpm/workspace.state": "workspace:*", + "@pnpm/write-project-manifest": "workspace:*", + "@types/cross-spawn": "catalog:", + "@types/is-windows": "catalog:", + "@types/pnpm__byline": "catalog:", + "@types/ramda": "catalog:", + "@types/semver": "catalog:", + "@zkochan/retry": "catalog:", + "@zkochan/rimraf": "catalog:", + "chalk": "catalog:", + "ci-info": "catalog:", + "cross-spawn": "catalog:", + "deep-require-cwd": "catalog:", + "delay": "catalog:", + "dir-is-case-sensitive": "catalog:", + "esbuild": "catalog:", + "execa": "catalog:", + "exists-link": "catalog:", + "is-windows": "catalog:", + "load-json-file": "catalog:", + "loud-rejection": "catalog:", + "normalize-newline": "catalog:", + "p-any": "catalog:", + "p-defer": "catalog:", + "path-name": "catalog:", + "pidtree": "catalog:", + "ps-list": "catalog:", + "ramda": "catalog:", + "read-yaml-file": "catalog:", + "render-help": "catalog:", + "semver": "catalog:", + "split-cmd": "catalog:", + "symlink-dir": "catalog:", + "tempy": "catalog:", + "tree-kill": "catalog:", + "write-json-file": "catalog:", + "write-pkg": "catalog:", + "write-yaml-file": "catalog:" + }, + "engines": { + "node": ">=18.12" + }, + "jest": { + "preset": "@pnpm/jest-config/with-registry" + }, + "preferGlobal": true, + "publishConfig": { + "tag": "next-10", + "executableFiles": [ + "./dist/node-gyp-bin/node-gyp", + "./dist/node-gyp-bin/node-gyp.cmd", + "./dist/node_modules/node-gyp/bin/node-gyp.js" + ] + }, + "scripts": { + "bundle": "ts-node bundle.ts", + "start": "tsc --watch", + "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", + "pretest:e2e": "rimraf node_modules/.bin/pnpm", + "_test": "jest", + "test": "pnpm run compile && pnpm run _test", + "_compile": "tsc --build", + "compile": "tsc --build && pnpm run lint --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/templates dist/templates && shx cp -r node_modules/ps-list/vendor dist/vendor && shx cp pnpmrc dist/pnpmrc" + } +} diff --git a/src/commands/admin/chunk-csv/command.ts b/src/commands/admin/chunk-csv/command.ts index 6ad3e6c6..5f4813a4 100644 --- a/src/commands/admin/chunk-csv/command.ts +++ b/src/commands/admin/chunk-csv/command.ts @@ -19,6 +19,12 @@ export const chunkCsvCommand = buildCommand({ "Directory to write chunk files (defaults to the input file's directory)", optional: true, }, + clearOutputDir: { + kind: 'parsed', + parse: Boolean, + brief: 'Clear the output directory before writing chunks', + default: 'true', + }, chunkSizeMB: { kind: 'parsed', parse: (v: string) => { @@ -29,8 +35,8 @@ export const chunkCsvCommand = buildCommand({ return n; }, brief: - 'Approximate chunk size in megabytes. Keep well under JS string size limits. Default 10MB.', - optional: true, + 'Approximate chunk size in megabytes. Keep well under JS string size limits', + default: '10', }, }, }, diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index 99a39a27..5255e35b 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -1,7 +1,8 @@ #!/usr/bin/env node import { Parser } from 'csv-parse'; -import { createReadStream } from 'node:fs'; +import { createReadStream, mkdirSync, unlinkSync } from 'node:fs'; +import { readdir } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; import { pipeline } from 'node:stream/promises'; import { Transform } from 'node:stream'; @@ -13,12 +14,10 @@ import type { LocalContext } from '../../../context'; export interface ChunkCsvCommandFlags { inputFile: string; outputDir?: string; - chunkSizeMB?: number; + clearOutputDir: boolean; + chunkSizeMB: number; } -/** Size of each chunk in bytes (need to stay WELL under JS string size limit of 512MB) */ -const DEFAULT_CHUNK_SIZE_BYTES = 10 * 1024 * 1024; - /** * Format memory usage for logging * @@ -47,13 +46,9 @@ function formatMemoryUsage(memoryData: NodeJS.MemoryUsage): string { */ export async function chunkCsvImpl( this: LocalContext, - { - flags, - }: { - flags: ChunkCsvCommandFlags; - }, + flags: ChunkCsvCommandFlags, ): Promise { - const { inputFile, outputDir, chunkSizeMB } = flags; + const { inputFile, outputDir, chunkSizeMB, clearOutputDir } = flags; // Ensure inputFile is provided if (!inputFile) { @@ -65,13 +60,29 @@ export async function chunkCsvImpl( process.exit(1); } - const CHUNK_SIZE = - typeof chunkSizeMB === 'number' && Number.isFinite(chunkSizeMB) - ? Math.floor(chunkSizeMB * 1024 * 1024) - : DEFAULT_CHUNK_SIZE_BYTES; + const chunkSize = Math.floor((chunkSizeMB || chunkSizeMB) * 1024 * 1024); const baseFileName = basename(inputFile, '.csv'); const outputDirectory = outputDir || dirname(inputFile); + mkdirSync(outputDirectory, { recursive: true }); + + // clear previous files + if (clearOutputDir) { + logger.info(colors.blue(`Clearing output directory: ${outputDirectory}`)); + try { + const files = await readdir(outputDirectory); + await Promise.all( + files + .filter((file) => file.startsWith(`${baseFileName}_chunk`)) + .map((file) => unlinkSync(join(outputDirectory, file))), + ); + logger.info(colors.green('Output directory cleared.')); + } catch (error) { + logger.error(colors.red('Error clearing output directory:'), error); + process.exit(1); + } + } + let currentChunkSize = 0; let currentChunkNumber = 1; let headerRow: string[] | null = null; @@ -159,7 +170,7 @@ export async function chunkCsvImpl( } // Determine if we need to start a new chunk - if (currentChunkSize >= CHUNK_SIZE) { + if (currentChunkSize >= chunkSize) { currentChunkNumber += 1; currentChunkSize = 0; currentOutputFile = join( @@ -183,7 +194,9 @@ export async function chunkCsvImpl( const readStream = createReadStream(inputFile); try { - logger.info(colors.blue(`Starting to process ${inputFile}...`)); + logger.info( + colors.blue(`Starting to process ${inputFile}... for ${chunkSizeMB}MB`), + ); await pipeline(readStream, parser, chunker); logger.info( colors.green( diff --git a/src/commands/consent/upload-preferences/buildTaskOptions.ts b/src/commands/consent/upload-preferences/buildTaskOptions.ts index d8723eea..5819acee 100644 --- a/src/commands/consent/upload-preferences/buildTaskOptions.ts +++ b/src/commands/consent/upload-preferences/buildTaskOptions.ts @@ -13,6 +13,7 @@ export type TaskCommonOpts = Pick< | 'uploadConcurrency' | 'uploadLogInterval' | 'maxChunkSize' + | 'downloadIdentifierConcurrency' | 'rateLimitRetryDelay' | 'maxRecordsToReceipt' | 'skipWorkflowTriggers' @@ -48,6 +49,7 @@ export function buildCommonOpts( sombraAuth, partition, transcendUrl, + downloadIdentifierConcurrency, skipConflictUpdates, skipWorkflowTriggers, skipExistingRecordCheck, @@ -70,6 +72,7 @@ export function buildCommonOpts( receiptsFolder, auth, directory, + downloadIdentifierConcurrency, sombraAuth, partition, transcendUrl, diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 5d611529..3499806d 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -126,7 +126,7 @@ export const uploadPreferencesCommand = buildCommand({ brief: 'When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.' + 'The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency.', - default: '50', + default: '25', }, rateLimitRetryDelay: { kind: 'parsed', @@ -148,6 +148,14 @@ export const uploadPreferencesCommand = buildCommand({ 'Default is a good optimization for most cases.', default: '1000', }, + downloadIdentifierConcurrency: { + kind: 'parsed', + parse: numberParser, + brief: + 'When downloading identifiers for the upload - this is the number of concurrent requests to make. ' + + 'This is only used if the records are not already cached in the preference store. ', + default: '30', + }, maxRecordsToReceipt: { kind: 'parsed', parse: numberParser, diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index daa78700..f9acf545 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -73,6 +73,7 @@ export interface UploadPreferencesCommandFlags { maxChunkSize: number; rateLimitRetryDelay: number; uploadLogInterval: number; + downloadIdentifierConcurrency: number; maxRecordsToReceipt: number; allowedIdentifierNames: string[]; identifierColumns: string[]; diff --git a/src/commands/consent/upload-preferences/receipts/receiptsState.ts b/src/commands/consent/upload-preferences/receipts/receiptsState.ts index e5071a80..fe61b5fb 100644 --- a/src/commands/consent/upload-preferences/receipts/receiptsState.ts +++ b/src/commands/consent/upload-preferences/receipts/receiptsState.ts @@ -5,6 +5,7 @@ import { type PendingSafePreferenceUpdates, type PendingWithConflictPreferenceUpdates, type PreferenceUpdateMap, + type SkippedPreferenceUpdates, } from '../../../../lib/preference-management'; import { retrySamePromise, @@ -38,6 +39,10 @@ export type PreferenceReceiptsInterface = { * Set the new map of safe to upload records */ setPendingSafe(next: PendingSafePreferenceUpdates): Promise; + /** + * Set the skipped records + */ + setSkipped(next: PendingSafePreferenceUpdates): Promise; /** * Set the new map of conflict upload records */ @@ -78,8 +83,8 @@ export async function makeReceiptsState( // Retry policy: only retry on the specific JSON truncation message. const policy: RetryPolicy = { - maxAttempts: 5, - delayMs: 50, // start small and backoff + maxAttempts: 10, + delayMs: 500, // start small and backoff shouldRetry: (_status, message) => typeof message === 'string' && /Unexpected end of JSON input/i.test(message ?? ''), @@ -90,11 +95,13 @@ export async function makeReceiptsState( try { const s = await retrySamePromise( - () => + async () => { // Wrap constructor in a Promise so thrown sync errors reject properly. - Promise.resolve( + const result = await Promise.resolve( new PersistedState(filepath, RequestUploadReceipts, initial), - ), + ); + return result; + }, policy, // eslint-disable-next-line @typescript-eslint/no-unused-vars (_note) => { @@ -116,6 +123,9 @@ export async function makeReceiptsState( async setSuccessful(v: PreferenceUpdateMap) { await s.setValue(v, 'successfulUpdates'); }, + async setSkipped(v: SkippedPreferenceUpdates) { + await s.setValue(v, 'skippedUpdates'); + }, async setPending(v: PreferenceUpdateMap) { await s.setValue(v, 'pendingUpdates'); }, @@ -131,6 +141,7 @@ export async function makeReceiptsState( async resetPending() { await s.setValue({}, 'pendingUpdates'); await s.setValue({}, 'pendingSafeUpdates'); + await s.setValue({}, 'skippedUpdates'); await s.setValue({}, 'pendingConflictUpdates'); }, }; diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/runChild.ts index 50d3d73f..5feeb79c 100644 --- a/src/commands/consent/upload-preferences/runChild.ts +++ b/src/commands/consent/upload-preferences/runChild.ts @@ -98,7 +98,8 @@ export async function runChild(): Promise { partition: options.partition, receipts, schema, - uploadLogInterval: options.uploadLogInterval, + identifierDownloadLogInterval: options.uploadLogInterval * 10, + downloadIdentifierConcurrency: options.downloadIdentifierConcurrency, skipExistingRecordCheck: options.skipExistingRecordCheck, forceTriggerWorkflows: options.forceTriggerWorkflows, allowedIdentifierNames: options.allowedIdentifierNames, diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts index f178fc8d..e9e0476f 100644 --- a/src/commands/consent/upload-preferences/schemaState.ts +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -62,9 +62,13 @@ export async function makeSchemaState( try { const state = await retrySamePromise( - () => + async () => { // Wrap constructor in a Promise so thrown sync errors reject properly. - Promise.resolve(new PersistedState(filepath, FileFormatState, initial)), + const result = await Promise.resolve( + new PersistedState(filepath, FileFormatState, initial), + ); + return result; + }, policy, // eslint-disable-next-line @typescript-eslint/no-unused-vars (note) => { diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/transform/transformCsv.ts index 877aa673..0308f74a 100644 --- a/src/commands/consent/upload-preferences/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/transform/transformCsv.ts @@ -1,4 +1,7 @@ // FIXME +import colors from 'colors'; + +import { logger } from '../../../../logger'; /** * Add Transcend ID to preferences if email_id is present @@ -9,25 +12,38 @@ export function transformCsv( preferences: Record[], ): Record[] { - // Add a transcendent ID to each preference if it doesn't already exist - const disallowedEmails = (process.env.EMAIL_LIST || '') - .split(',') - .map((email) => email.trim().toLowerCase()); + const keys = Object.keys(preferences[0]); + const isUdp = + keys.includes('email_address') && + keys.includes('person_id') && + keys.includes('member_id'); + if (isUdp) { + logger.info( + colors.yellow( + 'Detected UDP format. Transforming preferences to include Transcend ID.', + ), + ); + // Add a transcendent ID to each preference if it doesn't already exist + const disallowedEmails = (process.env.EMAIL_LIST || '') + .split(',') + .map((email) => email.trim().toLowerCase()); - return preferences.map((pref) => { - const email = (pref.email_address || '').toLowerCase().trim(); - const emailAddress = - !email || disallowedEmails.includes(email) ? '' : pref.email_address; - return { - ...pref, - person_id: pref.person_id !== '-2' ? pref.person_id : '', - email_address: emailAddress, - // preference email address over transcendID - transcendID: emailAddress - ? '' - : pref.person_id && pref.person_id !== '-2' - ? pref.person_id - : pref.member_id, - }; - }); + return preferences.map((pref) => { + const email = (pref.email_address || '').toLowerCase().trim(); + const emailAddress = + !email || disallowedEmails.includes(email) ? '' : pref.email_address; + return { + ...pref, + person_id: pref.person_id !== '-2' ? pref.person_id : '', + email_address: emailAddress, + // preference email address over transcendID + transcendID: emailAddress + ? '' + : pref.person_id && pref.person_id !== '-2' + ? pref.person_id + : pref.member_id, + }; + }); + } + return preferences; } diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index 19adbf73..73be6577 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -69,7 +69,8 @@ export async function buildInteractiveUploadPreferencePlan({ skipExistingRecordCheck = false, forceTriggerWorkflows = false, allowedIdentifierNames, - uploadLogInterval = 1000, + downloadIdentifierConcurrency = 30, + identifierDownloadLogInterval = 10000, maxRecordsToReceipt = 50, identifierColumns, columnsToIgnore = [], @@ -91,6 +92,8 @@ export async function buildInteractiveUploadPreferencePlan({ skipExistingRecordCheck?: boolean; /** Force workflow triggers; requires existing consent records for all rows */ forceTriggerWorkflows?: boolean; + /** Concurrency for downloading identifiers */ + downloadIdentifierConcurrency?: number; /** Allowed identifier names configured for the org/run */ allowedIdentifierNames: string[]; /** CSV columns that correspond to identifiers */ @@ -99,8 +102,8 @@ export async function buildInteractiveUploadPreferencePlan({ columnsToIgnore?: string[]; /** Extra workflow attributes (pre-parsed Key:Value strings) */ attributes?: string[]; - /** Interval to log upload progress */ - uploadLogInterval?: number; + /** Interval to log when downloading identifiers */ + identifierDownloadLogInterval?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; }): Promise { @@ -139,8 +142,9 @@ export async function buildInteractiveUploadPreferencePlan({ forceTriggerWorkflows, orgIdentifiers: references.identifiers, allowedIdentifierNames, + downloadIdentifierConcurrency, identifierColumns, - uploadLogInterval, + identifierDownloadLogInterval, columnsToIgnore, }, schema.state, @@ -150,6 +154,7 @@ export async function buildInteractiveUploadPreferencePlan({ await receipts.setPendingSafe( limitRecords(parsed.pendingSafeUpdates, maxRecordsToReceipt), ); + await receipts.setSkipped(parsed.skippedUpdates); await receipts.setPendingConflict(parsed.pendingConflictUpdates); // Return a compact, self-contained plan for the upload stage. diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 35364f22..b7c09781 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -1,7 +1,7 @@ /* eslint-disable no-param-reassign */ import colors from 'colors'; import { map as pMap } from 'bluebird'; -import { chunk } from 'lodash-es'; +import { chunk, groupBy } from 'lodash-es'; import { logger } from '../../../../logger'; import { buildPendingUpdates } from '../transform/buildPendingUpdates'; import { uploadChunkWithSplit } from './batchUploader'; @@ -40,7 +40,7 @@ export async function interactivePreferenceUploaderFromPlan( skipConflictUpdates = false, forceTriggerWorkflows = false, uploadLogInterval = 1_000, - maxChunkSize = 50, + maxChunkSize = 25, uploadConcurrency = 20, maxRecordsToReceipt = 50, onProgress, @@ -231,7 +231,9 @@ export async function interactivePreferenceUploaderFromPlan( failing[userId] = { uploadedAt: new Date().toISOString(), update, - error: msg, + error: msg.includes('Identifier email did not pass validation') + ? 'Identifier email did not pass validation' + : msg, }; delete pendingUpdates[userId]; @@ -257,8 +259,45 @@ export async function interactivePreferenceUploaderFromPlan( } }; + const { + valid = [], + invalidAt = [], + invalidSlash = [], + } = groupBy(filtered, ([, update]) => + !update.identifiers + ? 'valid' + : update.identifiers.some( + (id) => id.name === 'email' && !id.value.includes('@'), + ) + ? 'invalidAt' + : update.identifiers.some( + (id) => id.name === 'email' && id.value.includes('/'), + ) + ? 'invalidSlash' + : 'valid', + ); + + if (invalidAt.length > 0) { + await markFailureForBatch( + invalidAt, + new Error('Invalid email format - missing @'), + ); + } + if (invalidSlash.length > 0) { + await markFailureForBatch( + invalidSlash, + new Error('Invalid email format - email contains a slash (/)'), + ); + } + + if (valid.length === 0) { + logger.warn(colors.yellow('No updates to upload after validating emails.')); + await receipts.resetPending(); + return; + } + // Kick off uploads in chunks; each chunk may be recursively split on errors - const chunks = chunk(filtered, maxChunkSize); + const chunks = chunk(valid, maxChunkSize); await pMap( chunks, async (currentChunk) => { diff --git a/src/constants.ts b/src/constants.ts index c0aaff54..150b34b7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -197,4 +197,6 @@ export const SCOPE_TITLES = Object.keys(SCOPES_BY_TITLE); * 502: Upstream/edge gateway error * 329: Reserved for custom infra (kept defensively) */ -export const RETRYABLE_BATCH_STATUSES = new Set([429, 502, 329] as const); +export const RETRYABLE_BATCH_STATUSES = new Set([ + 429, 502, 500, 504, 329, +] as const); diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index f891a271..8eccfd3f 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -52,18 +52,25 @@ export const ColumnPurposeMap = t.record(t.string, PurposeRowMapping); /** Override type */ export type ColumnPurposeMap = t.TypeOf; +export const IdentifierMetadataForPreference = t.type({ + /** The identifier name */ + name: t.string, + /** Is unique on preference store */ + isUniqueOnPreferenceStore: t.boolean, +}); + +/** Override type */ +export type IdentifierMetadataForPreference = t.TypeOf< + typeof IdentifierMetadataForPreference +>; + /** * Mapping of identifier name to the column name in the CSV file. * This is used to map each identifier name to the column in the CSV file. */ export const ColumnIdentifierMap = t.record( t.string, - t.type({ - /** The identifier name */ - name: t.string, - /** Is unique on preference store */ - isUniqueOnPreferenceStore: t.boolean, - }), + IdentifierMetadataForPreference, ); /** Override type */ diff --git a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts index d6953344..203086da 100644 --- a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts +++ b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts @@ -66,7 +66,7 @@ export function getPreferenceUpdatesFromRow({ const rawValue = row[columnName]; const rawMapping = valueMapping[rawValue]; // When mapping is undefined, it means we should omit this column - if (rawMapping === undefined) { + if (rawMapping === undefined || rawMapping === null) { return; } diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 466feec8..29e85d25 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -7,6 +7,8 @@ import { decodeCodec } from '@transcend-io/type-utils'; import * as t from 'io-ts'; import { map } from 'bluebird'; import { logger } from '../../logger'; +import { extractErrorMessage, getErrorStatus, splitInHalf } from '../helpers'; +import { RETRYABLE_BATCH_STATUSES } from '../../constants'; const PreferenceRecordsQueryResponse = t.intersection([ t.type({ @@ -38,7 +40,8 @@ export async function getPreferencesForIdentifiers( identifiers, partitionKey, skipLogging = false, - uploadLogInterval = 1000, + logInterval = 10000, + concurrency = 30, }: { /** The list of identifiers to look up */ identifiers: { @@ -52,7 +55,9 @@ export async function getPreferencesForIdentifiers( /** Whether to skip logging */ skipLogging?: boolean; /** The interval to log upload progress */ - uploadLogInterval?: number; + logInterval?: number; + /** Concurrency for fetching identifiers */ + concurrency?: number; }, ): Promise { const results: PreferenceQueryResponseItem[] = []; @@ -62,65 +67,154 @@ export async function getPreferencesForIdentifiers( const t0 = new Date().getTime(); let total = 0; - await map( - groupedIdentifiers, - async (group) => { - // Make the request with retry logic - let attempts = 0; - const maxAttempts = 3; - while (attempts < maxAttempts) { - try { - const rawResult = await sombra - .post(`v1/preferences/${partitionKey}/query`, { - json: { - filter: { - identifiers: group, - }, - limit: group.length, + + /** Progress logger respecting `logInterval` */ + const maybeLogProgress = (): void => { + if (skipLogging) return; + const shouldLog = + total % logInterval === 0 || + Math.floor((total - identifiers.length) / logInterval) < + Math.floor(total / logInterval); + if (shouldLog) { + logger.info( + colors.green( + `Fetched ${total}/${identifiers.length} user preferences from partition ${partitionKey}`, + ), + ); + } + }; + + /** + * Attempt a single POST for a given group with transient retries. + * Returns decoded nodes on success. + * Throws an error on terminal failure. + * If the error contains "did not pass validation", it throws that error up + * so the caller can choose to split. + * + * @param group - The group of identifiers to fetch + * @returns The decoded nodes from the response + */ + const postGroupWithRetries = async ( + group: { + /** Value of the identifier */ + value: string; + /** Name of the identifier */ + name: string; + }[], + ): Promise => { + let attempts = 0; + const maxAttempts = 3; + + // eslint-disable-next-line no-constant-condition + while (true) { + attempts += 1; + try { + const rawResult = await sombra + .post(`v1/preferences/${partitionKey}/query`, { + json: { + filter: { + identifiers: group, }, - }) - .json(); - - const result = decodeCodec(PreferenceRecordsQueryResponse, rawResult); - results.push(...result.nodes); - total += group.length; - const shouldLog = - !skipLogging && - (total % uploadLogInterval === 0 || - Math.floor((total - identifiers.length) / uploadLogInterval) < - Math.floor(total / uploadLogInterval)); - if (shouldLog) { - logger.info( - colors.green( - `Fetched ${total}/${identifiers.length} user preferences from partition ${partitionKey}`, - ), - ); - } - break; // Exit loop if successful - } catch (err) { - attempts += 1; - const msg = err?.response?.body || err?.message || ''; - if ( - attempts >= maxAttempts || - !MSGS.some((errorMessage) => msg.includes(errorMessage)) - ) { - throw new Error( - `Received an error from server after ${attempts} attempts: ${msg}`, - ); - } + limit: group.length, + }, + }) + .json(); + + const result = decodeCodec(PreferenceRecordsQueryResponse, rawResult); + return result.nodes; + } catch (err) { + const status = getErrorStatus(err); + const msg = extractErrorMessage(err); + + // For validation failures, bubble up (caller will split) + if (/did not pass validation/i.test(msg)) { + throw err; // handled by caller (split path) + } + + // If not a known transient or we've exhausted attempts → terminal error + const isTransient = + MSGS.some((m) => msg.includes(m)) || + // eslint-disable-next-line @typescript-eslint/no-explicit-any + RETRYABLE_BATCH_STATUSES.has(status as any); + if (!isTransient || attempts >= maxAttempts) { + throw new Error( + `Received an error from server after ${attempts} attempts: ${msg}`, + ); + } + + logger.warn( + colors.yellow( + `[RETRYING FAILED REQUEST - Attempt ${attempts}] ` + + `Failed to fetch ${group.length} user preferences from partition ${partitionKey}: ${msg}, status: ${status}`, + ), + ); + } + } + }; + /** + * Recursively process a group: + * - Try to fetch in one go. + * - If it fails with "did not pass validation", split into halves and recurse. + * - If the group becomes a singleton and still fails validation, skip it. + * In all terminal paths (success or skip), increment `total` by the + * number of identifiers accounted for and log progress. + * + * @param group - The group of identifiers to process + */ + const processGroup = async ( + group: { + /** Value of the identifier */ + value: string; + /** Name of the identifier */ + name: string; + }[], + ): Promise => { + try { + const nodes = await postGroupWithRetries(group); + results.push(...nodes); + total += group.length; + maybeLogProgress(); + } catch (err) { + const msg = extractErrorMessage(err); + + if (/did not pass validation/i.test(msg)) { + // If single, skip and count it + if (group.length === 1) { + const only = group[0]; logger.warn( colors.yellow( - `[RETRYING FAILED REQUEST - Attempt ${attempts}] ` + - `Failed to fetch ${group.length} user preferences from partition ${partitionKey}: ${msg}`, + `Skipping identifier "${only.value}" (${only.name}): ${msg}`, ), ); + total += 1; + maybeLogProgress(); + return; } + + // Otherwise, split and recurse + const [left, right] = splitInHalf(group); + logger.warn( + colors.yellow( + `Group of ${group.length} did not pass validation. Splitting into ${left.length} and ${right.length}.`, + ), + ); + await processGroup(left); + await processGroup(right); + return; } + + // Non-validation terminal error: rethrow + throw err; + } + }; + + await map( + groupedIdentifiers, + async (group) => { + await processGroup(group); }, - { - concurrency: 40, - }, + { concurrency }, ); const t1 = new Date().getTime(); diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index bbcecb01..081ae0ba 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -2,7 +2,10 @@ import { uniq, keyBy } from 'lodash-es'; import colors from 'colors'; import inquirer from 'inquirer'; -import type { FileFormatState } from './codecs'; +import type { + FileFormatState, + IdentifierMetadataForPreference, +} from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; import { mapSeries } from 'bluebird'; @@ -191,12 +194,19 @@ export function getPreferenceIdentifiersFromRow({ /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; }): PreferenceStoreIdentifier[] { - return Object.entries(columnToIdentifier) + const identifiers = Object.entries(columnToIdentifier) .filter(([col]) => !!row[col]) .map(([col, identifierMapping]) => ({ name: identifierMapping.name, value: row[col], })); + // put email first if it exists + // TODO: https://linear.app/transcend/issue/PIK-285/set-precedence-of-unique-identifiers - remove email logic + return identifiers.sort( + (a, b) => + (a.name === 'email' ? -1 : 0) - (b.name === 'email' ? -1 : 0) || + a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }), + ); } /** @@ -215,15 +225,26 @@ export function getUniquePreferenceIdentifierNamesFromRow({ row: Record; /** The current file metadata state */ columnToIdentifier: FileFormatState['columnToIdentifier']; -}): string[] { +}): (IdentifierMetadataForPreference & { + /** Column name */ + columnName: string; + /** Value of the identifier in the row */ + value: string; +})[] { // TODO: https://linear.app/transcend/issue/PIK-285/set-precedence-of-unique-identifiers - remove email logic - const columns = Object.keys(columnToIdentifier).filter( - (col) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, - ); - // if email is present move it to front of list - if (columns.includes('email')) { - columns.splice(columns.indexOf('email'), 1); - columns.unshift('email'); - } - return columns; + // sort email to the front + return Object.entries(columnToIdentifier) + .sort( + ([, a], [, b]) => + (a.name === 'email' ? -1 : 0) - (b.name === 'email' ? -1 : 0) || + a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }), + ) + .filter( + ([col]) => row[col] && columnToIdentifier[col].isUniqueOnPreferenceStore, + ) + .map(([col, identifier]) => ({ + ...identifier, + columnName: col, + value: row[col], + })); } diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 1019a729..3291dae4 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -22,6 +22,7 @@ import { parsePreferenceAndPurposeValuesFromCsv } from './parsePreferenceAndPurp import { checkIfPendingPreferenceUpdatesAreNoOp } from './checkIfPendingPreferenceUpdatesAreNoOp'; import { checkIfPendingPreferenceUpdatesCauseConflict } from './checkIfPendingPreferenceUpdatesCauseConflict'; import type { ObjByString } from '@transcend-io/type-utils'; +import type { PreferenceQueryResponseItem } from '@transcend-io/privacy-types'; /** * Parse a file into the cache @@ -45,7 +46,8 @@ export async function parsePreferenceManagementCsvWithCache( orgIdentifiers, allowedIdentifierNames, identifierColumns, - uploadLogInterval, + downloadIdentifierConcurrency, + identifierDownloadLogInterval, columnsToIgnore, }: { /** File to parse */ @@ -71,7 +73,9 @@ export async function parsePreferenceManagementCsvWithCache( /** Columns to ignore in the CSV file */ columnsToIgnore: string[]; /** The interval to log upload progress */ - uploadLogInterval: number; + identifierDownloadLogInterval: number; + /** Concurrency for downloading identifiers */ + downloadIdentifierConcurrency: number; }, schemaState: PersistedState, ): Promise<{ @@ -115,20 +119,39 @@ export async function parsePreferenceManagementCsvWithCache( getUniquePreferenceIdentifierNamesFromRow({ row: pref, columnToIdentifier: currentColumnToIdentifierMap, - }).map((col) => ({ - name: currentColumnToIdentifierMap[col].name, - value: pref[col], - })), + }), ); const existingConsentRecords = skipExistingRecordCheck ? [] : await getPreferencesForIdentifiers(sombra, { identifiers, - uploadLogInterval, + logInterval: identifierDownloadLogInterval, partitionKey, + concurrency: downloadIdentifierConcurrency, }); - const consentRecordByIdentifier = keyBy(existingConsentRecords, 'userId'); + + // Create a map of all unique identifiers to consent records + const uniqueIdentifiers = Object.values(currentColumnToIdentifierMap) + .filter((x) => x.isUniqueOnPreferenceStore) + .map((x) => x.name); + const consentRecordByUniqueIdentifiers = uniqueIdentifiers.reduce( + (acc, identifier) => { + const recordsWithIdentifier = existingConsentRecords.filter((record) => + (record.identifiers || []).some( + (id) => id.name === identifier && id.value, + ), + ); + acc[identifier] = keyBy( + recordsWithIdentifier, + (record) => + (record.identifiers || []).find((id) => id.name === identifier) + ?.value || '', + ); + return acc; + }, + {} as Record>, + ); // Clear out previous updates const pendingConflictUpdates: RequestUploadReceipts['pendingConflictUpdates'] = @@ -149,10 +172,10 @@ export async function parsePreferenceManagementCsvWithCache( ); preferences.forEach((pref, ind) => { // Get the userIds that could be the primary key of the consent record - const possiblePrimaryKeys = getUniquePreferenceIdentifierNamesFromRow({ + const uniqueIdentifiers = getUniquePreferenceIdentifierNamesFromRow({ row: pref, columnToIdentifier: currentColumnToIdentifierMap, - }).map((col) => pref[col]); + }); // determine updates for user const pendingUpdates = getPreferenceUpdatesFromRow({ @@ -163,12 +186,13 @@ export async function parsePreferenceManagementCsvWithCache( }); // Grab current state of the update - const currentConsentRecord = possiblePrimaryKeys - .map((primaryKey) => consentRecordByIdentifier[primaryKey]) - .find((record) => record); - + const primaryKeyMetadata = uniqueIdentifiers[0]; + const currentConsentRecord = + consentRecordByUniqueIdentifiers[primaryKeyMetadata.name][ + primaryKeyMetadata.value + ]; // If consent record is found use it, otherwise use the first unique identifier - let primaryKey = currentConsentRecord?.userId || possiblePrimaryKeys[0]; + let primaryKey = primaryKeyMetadata.value; // Ensure this is unique if (seenAlready[primaryKey]) { if ( @@ -202,9 +226,9 @@ export async function parsePreferenceManagementCsvWithCache( if (forceTriggerWorkflows && !currentConsentRecord) { throw new Error( - `No existing consent record found for user with ids: ${possiblePrimaryKeys.join( - ', ', - )}. + `No existing consent record found for user with ids: ${uniqueIdentifiers + .map((x) => x.value) + .join(', ')}. When 'forceTriggerWorkflows' is set all the user identifiers should contain a consent record`, ); } From 0e239ba69392059e3fda669be34db2a42098f51f Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 14:09:54 -0700 Subject: [PATCH 40/72] ipdats --- .../upload-preferences/transform/transformCsv.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/transform/transformCsv.ts index 0308f74a..dcc6eac6 100644 --- a/src/commands/consent/upload-preferences/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/transform/transformCsv.ts @@ -12,6 +12,11 @@ import { logger } from '../../../../logger'; export function transformCsv( preferences: Record[], ): Record[] { + // Add a transcendent ID to each preference if it doesn't already exist + const disallowedEmails = (process.env.EMAIL_LIST || '') + .split(',') + .map((email) => email.trim().toLowerCase()); + const keys = Object.keys(preferences[0]); const isUdp = keys.includes('email_address') && @@ -23,10 +28,6 @@ export function transformCsv( 'Detected UDP format. Transforming preferences to include Transcend ID.', ), ); - // Add a transcendent ID to each preference if it doesn't already exist - const disallowedEmails = (process.env.EMAIL_LIST || '') - .split(',') - .map((email) => email.trim().toLowerCase()); return preferences.map((pref) => { const email = (pref.email_address || '').toLowerCase().trim(); @@ -45,5 +46,7 @@ export function transformCsv( }; }); } + + // FIXME skip the emails return preferences; } From 8296a3abfde23f3df15df520eb8e0e21de0fc8b3 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 15:41:40 -0700 Subject: [PATCH 41/72] rm node corepack --- node/corepack/v1/pnpm/10.12.4/.corepack | 1 - node/corepack/v1/pnpm/10.12.4/LICENSE | 22 -- node/corepack/v1/pnpm/10.12.4/README.md | 239 --------------------- node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs | 27 --- node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs | 5 - node/corepack/v1/pnpm/10.12.4/package.json | 189 ---------------- 6 files changed, 483 deletions(-) delete mode 100644 node/corepack/v1/pnpm/10.12.4/.corepack delete mode 100644 node/corepack/v1/pnpm/10.12.4/LICENSE delete mode 100644 node/corepack/v1/pnpm/10.12.4/README.md delete mode 100755 node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs delete mode 100755 node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs delete mode 100644 node/corepack/v1/pnpm/10.12.4/package.json diff --git a/node/corepack/v1/pnpm/10.12.4/.corepack b/node/corepack/v1/pnpm/10.12.4/.corepack deleted file mode 100644 index c57f053d..00000000 --- a/node/corepack/v1/pnpm/10.12.4/.corepack +++ /dev/null @@ -1 +0,0 @@ -{"locator":{"name":"pnpm","reference":"10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"},"bin":{"pnpm":"./bin/pnpm.cjs","pnpx":"./bin/pnpx.cjs"},"hash":"sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"} \ No newline at end of file diff --git a/node/corepack/v1/pnpm/10.12.4/LICENSE b/node/corepack/v1/pnpm/10.12.4/LICENSE deleted file mode 100644 index a4c8771c..00000000 --- a/node/corepack/v1/pnpm/10.12.4/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015-2016 Rico Sta. Cruz and other contributors -Copyright (c) 2016-2025 Zoltan Kochan and other contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/node/corepack/v1/pnpm/10.12.4/README.md b/node/corepack/v1/pnpm/10.12.4/README.md deleted file mode 100644 index ce020b20..00000000 --- a/node/corepack/v1/pnpm/10.12.4/README.md +++ /dev/null @@ -1,239 +0,0 @@ - - - -## Table of Contents - -- [Platinum Sponsors](#platinum-sponsors) -- [Gold Sponsors](#gold-sponsors) -- [Silver Sponsors](#silver-sponsors) -- [Background](#background) -- [Installation](#installation) -- [Usage](#usage) -- [Benchmark](#benchmark) -- [Support](#support) -- [License](#license) - - - -[简体中文](https://pnpm.io/zh/) | -[日本語](https://pnpm.io/ja/) | -[한국어](https://pnpm.io/ko/) | -[Italiano](https://pnpm.io/it/) | -[Português Brasileiro](https://pnpm.io/pt/) - - - - - pnpm - - -Fast, disk space efficient package manager: - -- **Fast.** Up to 2x faster than the alternatives (see [benchmark](#benchmark)). -- **Efficient.** Files inside `node_modules` are linked from a single content-addressable storage. -- **[Great for monorepos](https://pnpm.io/workspaces).** -- **Strict.** A package can access only dependencies that are specified in its `package.json`. -- **Deterministic.** Has a lockfile called `pnpm-lock.yaml`. -- **Works as a Node.js version manager.** See [pnpm env use](https://pnpm.io/cli/env). -- **Works everywhere.** Supports Windows, Linux, and macOS. -- **Battle-tested.** Used in production by teams of [all sizes](https://pnpm.io/users) since 2016. -- [See the full feature comparison with npm and Yarn](https://pnpm.io/feature-comparison). - -To quote the [Rush](https://rushjs.io/) team: - -> Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and we’ve found it to be very fast and reliable. - -[![npm version](https://img.shields.io/npm/v/pnpm.svg?label=latest)](https://github.com/pnpm/pnpm/releases/latest) -[![Join the chat at Discord](https://img.shields.io/discord/731599538665553971.svg)](https://r.pnpm.io/chat) -[![OpenCollective](https://opencollective.com/pnpm/backers/badge.svg)](https://opencollective.com/pnpm) -[![OpenCollective](https://opencollective.com/pnpm/sponsors/badge.svg)](https://opencollective.com/pnpm) -[![X Follow](https://img.shields.io/twitter/follow/pnpmjs.svg?style=social&label=Follow)](https://x.com/intent/follow?screen_name=pnpmjs®ion=follow_link) -[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) - -## Platinum Sponsors - - - - - - - - -
- Bit - - Bit -
- -## Gold Sponsors - - - - - - - - - - - - - -
- - - - - Discord - - - - - - - - CodeRabbit - - - - - - - - Workleap - - -
- - - - - Stackblitz - - - - - Vite - -
- -## Silver Sponsors - - - - - - - - - - - - - - - - - -
- - - - - u|screen - - - - - Leniolabs_ - - - - - - - Depot - - -
- - - - - devowl.io - - - - - - - - Cerbos - - - - - Vite - -
- - - - - OOMOL Studio - - -
- -Support this project by [becoming a sponsor](https://opencollective.com/pnpm#sponsor). - -## Background - -pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. -When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. -With pnpm, lodash will be stored in a content-addressable storage, so: - -1. If you depend on different versions of lodash, only the files that differ are added to the store. - If lodash has 100 files, and a new version has a change only in one of those files, - `pnpm update` will only add 1 new file to the storage. -1. All the files are saved in a single place on the disk. When packages are installed, their files are linked - from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write). - -As a result, you save gigabytes of space on your disk and you have a lot faster installations! -If you'd like more details about the unique `node_modules` structure that pnpm creates and -why it works fine with the Node.js ecosystem, read this small article: [Flat node_modules is not the only way](https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way). - -💖 Like this project? Let people know with a [tweet](https://r.pnpm.io/tweet) - -## Installation - -For installation options [visit our website](https://pnpm.io/installation). - -## Usage - -Just use pnpm in place of npm/Yarn. E.g., install dependencies via: - -``` -pnpm install -``` - -For more advanced usage, read [pnpm CLI](https://pnpm.io/pnpm-cli) on our website, or run `pnpm help`. - -## Benchmark - -pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks [here](https://r.pnpm.io/benchmarks). - -Benchmarks on an app with lots of dependencies: - -![](https://pnpm.io/img/benchmarks/alotta-files.svg) - -## Support - -- [Frequently Asked Questions](https://pnpm.io/faq) -- [Chat](https://r.pnpm.io/chat) -- [X](https://x.com/pnpmjs) -- [Bluesky](https://bsky.app/profile/pnpm.io) - -## License - -[MIT](https://github.com/pnpm/pnpm/blob/main/LICENSE) diff --git a/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs b/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs deleted file mode 100755 index defbe3c4..00000000 --- a/node/corepack/v1/pnpm/10.12.4/bin/pnpm.cjs +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node -const [major, minor] = process.version.slice(1).split('.'); -const COMPATIBILITY_PAGE = `Visit https://r.pnpm.io/comp to see the list of past pnpm versions with respective Node.js version support.`; - -// We don't use the semver library here because: -// 1. it is already bundled to dist/pnpm.cjs, so we would load it twice -// 2. we want this file to support potentially older Node.js versions than what semver supports -if (major < 18 || (major == 18 && minor < 12)) { - console.error(`ERROR: This version of pnpm requires at least Node.js v18.12 -The current version of Node.js is ${process.version} -${COMPATIBILITY_PAGE}`); - process.exit(1); -} - -// We need to load v8-compile-cache.js separately in order to have effect -try { - // Use node.js 22 new API for better performance. - if (!require('module')?.enableCompileCache?.()) require('v8-compile-cache'); -} catch { - // We don't have/need to care about v8-compile-cache failed -} - -global['pnpm__startedAt'] = Date.now(); -require('../dist/pnpm.cjs'); - -// if you want to debug at your local env, you can use this -// require('../lib/pnpm') diff --git a/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs b/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs deleted file mode 100755 index 097e7fce..00000000 --- a/node/corepack/v1/pnpm/10.12.4/bin/pnpx.cjs +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -process.argv = [...process.argv.slice(0, 2), 'dlx', ...process.argv.slice(2)]; - -require('./pnpm.cjs'); diff --git a/node/corepack/v1/pnpm/10.12.4/package.json b/node/corepack/v1/pnpm/10.12.4/package.json deleted file mode 100644 index 8cbaba19..00000000 --- a/node/corepack/v1/pnpm/10.12.4/package.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "name": "pnpm", - "version": "10.12.4", - "description": "Fast, disk space efficient package manager", - "keywords": [ - "pnpm", - "pnpm10", - "dependencies", - "dependency manager", - "efficient", - "fast", - "hardlinks", - "install", - "installer", - "link", - "lockfile", - "modules", - "monorepo", - "multi-package", - "npm", - "package manager", - "package.json", - "packages", - "prune", - "rapid", - "remove", - "shrinkwrap", - "symlinks", - "uninstall", - "workspace" - ], - "license": "MIT", - "funding": "https://opencollective.com/pnpm", - "repository": { - "type": "git", - "url": "git+https://github.com/pnpm/pnpm.git", - "directory": "pnpm" - }, - "homepage": "https://pnpm.io", - "bugs": { - "url": "https://github.com/pnpm/pnpm/issues" - }, - "main": "bin/pnpm.cjs", - "exports": { - ".": "./package.json" - }, - "files": [ - "dist", - "bin" - ], - "bin": { - "pnpm": "bin/pnpm.cjs", - "pnpx": "bin/pnpx.cjs" - }, - "directories": { - "test": "test" - }, - "unpkg": "dist/pnpm.cjs", - "__dependencies": { - "v8-compile-cache": "2.4.0" - }, - "__optionalDependencies": { - "node-gyp": "^11.1.0" - }, - "__devDependencies": { - "@pnpm/assert-project": "workspace:*", - "@pnpm/byline": "catalog:", - "@pnpm/cache.commands": "workspace:*", - "@pnpm/cli-meta": "workspace:*", - "@pnpm/cli-utils": "workspace:*", - "@pnpm/client": "workspace:*", - "@pnpm/command": "workspace:*", - "@pnpm/common-cli-options-help": "workspace:*", - "@pnpm/config": "workspace:*", - "@pnpm/constants": "workspace:*", - "@pnpm/core-loggers": "workspace:*", - "@pnpm/crypto.hash": "workspace:*", - "@pnpm/default-reporter": "workspace:*", - "@pnpm/dependency-path": "workspace:*", - "@pnpm/env.path": "workspace:*", - "@pnpm/error": "workspace:*", - "@pnpm/exec.build-commands": "workspace:*", - "@pnpm/filter-workspace-packages": "workspace:*", - "@pnpm/find-workspace-dir": "workspace:*", - "@pnpm/lockfile.types": "workspace:*", - "@pnpm/logger": "workspace:*", - "@pnpm/modules-yaml": "workspace:*", - "@pnpm/nopt": "catalog:", - "@pnpm/parse-cli-args": "workspace:*", - "@pnpm/plugin-commands-audit": "workspace:*", - "@pnpm/plugin-commands-completion": "workspace:*", - "@pnpm/plugin-commands-config": "workspace:*", - "@pnpm/plugin-commands-deploy": "workspace:*", - "@pnpm/plugin-commands-doctor": "workspace:*", - "@pnpm/plugin-commands-env": "workspace:*", - "@pnpm/plugin-commands-init": "workspace:*", - "@pnpm/plugin-commands-installation": "workspace:*", - "@pnpm/plugin-commands-licenses": "workspace:*", - "@pnpm/plugin-commands-listing": "workspace:*", - "@pnpm/plugin-commands-outdated": "workspace:*", - "@pnpm/plugin-commands-patching": "workspace:*", - "@pnpm/plugin-commands-publishing": "workspace:*", - "@pnpm/plugin-commands-rebuild": "workspace:*", - "@pnpm/plugin-commands-script-runners": "workspace:*", - "@pnpm/plugin-commands-server": "workspace:*", - "@pnpm/plugin-commands-setup": "workspace:*", - "@pnpm/plugin-commands-store": "workspace:*", - "@pnpm/plugin-commands-store-inspecting": "workspace:*", - "@pnpm/prepare": "workspace:*", - "@pnpm/read-package-json": "workspace:*", - "@pnpm/read-project-manifest": "workspace:*", - "@pnpm/registry-mock": "catalog:", - "@pnpm/run-npm": "workspace:*", - "@pnpm/store.cafs": "workspace:*", - "@pnpm/tabtab": "catalog:", - "@pnpm/test-fixtures": "workspace:*", - "@pnpm/test-ipc-server": "workspace:*", - "@pnpm/tools.path": "workspace:*", - "@pnpm/tools.plugin-commands-self-updater": "workspace:*", - "@pnpm/types": "workspace:*", - "@pnpm/worker": "workspace:*", - "@pnpm/workspace.find-packages": "workspace:*", - "@pnpm/workspace.pkgs-graph": "workspace:*", - "@pnpm/workspace.read-manifest": "workspace:*", - "@pnpm/workspace.state": "workspace:*", - "@pnpm/write-project-manifest": "workspace:*", - "@types/cross-spawn": "catalog:", - "@types/is-windows": "catalog:", - "@types/pnpm__byline": "catalog:", - "@types/ramda": "catalog:", - "@types/semver": "catalog:", - "@zkochan/retry": "catalog:", - "@zkochan/rimraf": "catalog:", - "chalk": "catalog:", - "ci-info": "catalog:", - "cross-spawn": "catalog:", - "deep-require-cwd": "catalog:", - "delay": "catalog:", - "dir-is-case-sensitive": "catalog:", - "esbuild": "catalog:", - "execa": "catalog:", - "exists-link": "catalog:", - "is-windows": "catalog:", - "load-json-file": "catalog:", - "loud-rejection": "catalog:", - "normalize-newline": "catalog:", - "p-any": "catalog:", - "p-defer": "catalog:", - "path-name": "catalog:", - "pidtree": "catalog:", - "ps-list": "catalog:", - "ramda": "catalog:", - "read-yaml-file": "catalog:", - "render-help": "catalog:", - "semver": "catalog:", - "split-cmd": "catalog:", - "symlink-dir": "catalog:", - "tempy": "catalog:", - "tree-kill": "catalog:", - "write-json-file": "catalog:", - "write-pkg": "catalog:", - "write-yaml-file": "catalog:" - }, - "engines": { - "node": ">=18.12" - }, - "jest": { - "preset": "@pnpm/jest-config/with-registry" - }, - "preferGlobal": true, - "publishConfig": { - "tag": "next-10", - "executableFiles": [ - "./dist/node-gyp-bin/node-gyp", - "./dist/node-gyp-bin/node-gyp.cmd", - "./dist/node_modules/node-gyp/bin/node-gyp.js" - ] - }, - "scripts": { - "bundle": "ts-node bundle.ts", - "start": "tsc --watch", - "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", - "pretest:e2e": "rimraf node_modules/.bin/pnpm", - "_test": "jest", - "test": "pnpm run compile && pnpm run _test", - "_compile": "tsc --build", - "compile": "tsc --build && pnpm run lint --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/templates dist/templates && shx cp -r node_modules/ps-list/vendor dist/vendor && shx cp pnpmrc dist/pnpmrc" - } -} From f6caaae7db05a3e681dc66961e11d127858d4560 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 15:42:08 -0700 Subject: [PATCH 42/72] readme --- README.md | 3364 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3364 insertions(+) diff --git a/README.md b/README.md index e69de29b..91c485ac 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3364 @@ +# Transcend CLI + + + + +## Table of Contents + +- [Changelog](#changelog) +- [Overview](#overview) +- [Installation](#installation) +- [transcend.yml](#transcendyml) +- [Usage](#usage) + - [`transcend request approve`](#transcend-request-approve) + - [Examples](#examples) + - [`transcend request upload`](#transcend-request-upload) + - [Examples](#examples-1) + - [`transcend request download-files`](#transcend-request-download-files) + - [Examples](#examples-2) + - [`transcend request cancel`](#transcend-request-cancel) + - [Examples](#examples-3) + - [`transcend request restart`](#transcend-request-restart) + - [Examples](#examples-4) + - [`transcend request notify-additional-time`](#transcend-request-notify-additional-time) + - [Examples](#examples-5) + - [`transcend request mark-silent`](#transcend-request-mark-silent) + - [Examples](#examples-6) + - [`transcend request enricher-restart`](#transcend-request-enricher-restart) + - [Examples](#examples-7) + - [`transcend request reject-unverified-identifiers`](#transcend-request-reject-unverified-identifiers) + - [Examples](#examples-8) + - [`transcend request export`](#transcend-request-export) + - [Examples](#examples-9) + - [`transcend request skip-preflight-jobs`](#transcend-request-skip-preflight-jobs) + - [Examples](#examples-10) + - [`transcend request system mark-request-data-silos-completed`](#transcend-request-system-mark-request-data-silos-completed) + - [Examples](#examples-11) + - [`transcend request system retry-request-data-silos`](#transcend-request-system-retry-request-data-silos) + - [Examples](#examples-12) + - [`transcend request system skip-request-data-silos`](#transcend-request-system-skip-request-data-silos) + - [Examples](#examples-13) + - [`transcend request preflight pull-identifiers`](#transcend-request-preflight-pull-identifiers) + - [Examples](#examples-14) + - [`transcend request preflight push-identifiers`](#transcend-request-preflight-push-identifiers) + - [Examples](#examples-15) + - [`transcend request cron pull-identifiers`](#transcend-request-cron-pull-identifiers) + - [Examples](#examples-16) + - [`transcend request cron mark-identifiers-completed`](#transcend-request-cron-mark-identifiers-completed) + - [Examples](#examples-17) + - [`transcend consent build-xdi-sync-endpoint`](#transcend-consent-build-xdi-sync-endpoint) + - [Examples](#examples-18) + - [`transcend consent pull-consent-metrics`](#transcend-consent-pull-consent-metrics) + - [Examples](#examples-19) + - [`transcend consent pull-consent-preferences`](#transcend-consent-pull-consent-preferences) + - [Examples](#examples-20) + - [`transcend consent update-consent-manager`](#transcend-consent-update-consent-manager) + - [Examples](#examples-21) + - [`transcend consent upload-consent-preferences`](#transcend-consent-upload-consent-preferences) + - [Examples](#examples-22) + - [`transcend consent upload-cookies-from-csv`](#transcend-consent-upload-cookies-from-csv) + - [Examples](#examples-23) + - [`transcend consent upload-data-flows-from-csv`](#transcend-consent-upload-data-flows-from-csv) + - [Examples](#examples-24) + - [`transcend consent upload-preferences`](#transcend-consent-upload-preferences) + - [Examples](#examples-25) + - [`transcend inventory pull`](#transcend-inventory-pull) + - [Scopes](#scopes) + - [Examples](#examples-26) + - [`transcend inventory push`](#transcend-inventory-push) + - [Scopes](#scopes-1) + - [Examples](#examples-27) + - [CI Integration](#ci-integration) + - [Dynamic Variables](#dynamic-variables) + - [`transcend inventory scan-packages`](#transcend-inventory-scan-packages) + - [Examples](#examples-28) + - [`transcend inventory discover-silos`](#transcend-inventory-discover-silos) + - [Examples](#examples-29) + - [`transcend inventory pull-datapoints`](#transcend-inventory-pull-datapoints) + - [Examples](#examples-30) + - [`transcend inventory pull-unstructured-discovery-files`](#transcend-inventory-pull-unstructured-discovery-files) + - [Examples](#examples-31) + - [`transcend inventory derive-data-silos-from-data-flows`](#transcend-inventory-derive-data-silos-from-data-flows) + - [Examples](#examples-32) + - [`transcend inventory derive-data-silos-from-data-flows-cross-instance`](#transcend-inventory-derive-data-silos-from-data-flows-cross-instance) + - [Examples](#examples-33) + - [`transcend inventory consent-manager-service-json-to-yml`](#transcend-inventory-consent-manager-service-json-to-yml) + - [Examples](#examples-34) + - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) + - [Examples](#examples-35) + - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) + - [Examples](#examples-36) + - [`transcend admin chunk-csv`](#transcend-admin-chunk-csv) + - [Examples](#examples-37) + - [`transcend migration sync-ot`](#transcend-migration-sync-ot) + - [Authentication](#authentication) + - [Examples](#examples-38) +- [Prompt Manager](#prompt-manager) +- [Proxy usage](#proxy-usage) + + + +## Changelog + +To stay up to date on breaking changes to the CLI between major version updates, please refer to [CHANGELOG.md](CHANGELOG.md). + +## Overview + +A command line interface that allows you to programatically interact with the Transcend. + +## Installation + +This package is distributed through npm, and assumes an installation of [npm and Node](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). + +```sh +npm install --global @transcend-io/cli +transcend --help +``` + +You can also run the CLI using npx: + +```sh +npx -p @transcend-io/cli -- transcend --help +``` + +Note + +_The CLI commands which interact with Transcend's API will default to using Transcend's EU backend. To use these commands with the US backend, you will need to add the flag --transcendUrl=https://api.us.transcend.io. You can also set the environment variable `TRANSCEND_API_URL=https://api.us.transcend.io`_ + +## transcend.yml + +Within your git repositories, you can define a file `transcend.yml`. This file allows you define part of your Data Map in code. Using the CLI, you can sync that configuration back to the Transcend Admin Dashboard (https://app.transcend.io/privacy-requests/connected-services). + +You can find various examples for your `transcend.yml` file in the [examples/](./examples/) folder. If you are looking for a starting point to copy and paste, [simple.yml](./examples/simple.yml) is a good place to start. This file is annotated with links and documentations that new members of your team can use if they come across the file. + +The API for this YAML file can be found in [./src/codecs.ts](./src/codecs.ts) under the variable named "TranscendInput". The shape of the YAML file will be type-checked every time a command is run. + +By default, your editor or IDE should recognize `transcend.yml` and validate it against our latest published [JSON schema](./transcend-yml-schema-latest.json). This is dependent on whether your editor uses [yaml-language-server](https://github.com/redhat-developer/yaml-language-server), such as through the [VS Code YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml). + +Your editor will use the latest version's schema. To pin the `transcend.yml` schema to a previous major version, include this at the top of your file (and change `v4` to your target major version): + +```yml +# yaml-language-server: $schema=https://raw.githubusercontent.com/transcend-io/cli/main/transcend-yml-schema-v4.json +``` + +The structure of `transcend.yml` looks something like the following: + +```yaml +# Manage at: https://app.transcend.io/infrastructure/api-keys +# See https://docs.transcend.io/docs/authentication +# Define API keys that may be shared across data silos +# in the data map. When creating new data silos through the YAML +# CLI, it is possible to specify which API key should be associated +# with the newly created data silo. +api-keys: + - title: Webhook Key + - title: Analytics Key + +# Manage at: https://app.transcend.io/privacy-requests/identifiers +# See https://docs.transcend.io/docs/identity-enrichment +# Define enricher or pre-flight check webhooks that will be executed +# prior to privacy request workflows. Some examples may include: +# - identity enrichment: look up additional identifiers for that user. +# i.e. map an email address to a user ID +# - fraud check: auto-cancel requests if the user is flagged for fraudulent behavior +# - customer check: auto-cancel request for some custom business criteria +enrichers: + - title: Basic Identity Enrichment + description: Enrich an email address to the userId and phone number + url: https://example.acme.com/transcend-enrichment-webhook + input-identifier: email + output-identifiers: + - userId + - phone + - myUniqueIdentifier + - title: Fraud Check + description: Ensure the email address is not marked as fraudulent + url: https://example.acme.com/transcend-fraud-check + input-identifier: email + output-identifiers: + - email + privacy-actions: + - ERASURE + +# Manage at: https://app.transcend.io/privacy-requests/connected-services +# See https://docs.transcend.io/docs/the-data-map#data-silos +# Define the data silos in your data map. A data silo can be a database, +# or a web service that may use a collection of different data stores under the hood. +data-silos: + # Note: title is the only required top-level field for a data silo + - title: Redshift Data Warehouse + description: The mega-warehouse that contains a copy over all SQL backed databases + integrationName: server + url: https://example.acme.com/transcend-webhook + api-key-title: Webhook Key + data-subjects: + - customer + - employee + - newsletter-subscriber + - b2b-contact + identity-keys: + - email + - userId + deletion-dependencies: + - Identity Service + owners: + - alice@transcend.io + datapoints: + - title: Webhook Notification + key: _global + privacy-actions: + - ACCESS + - ERASURE + - SALE_OPT_OUT + - title: User Model + description: The centralized user model user + key: users + privacy-actions: + - ACCESS + fields: + - key: firstName + title: First Name + description: The first name of the user, inputted during onboarding + - key: email + title: Email + description: The email address of the user +``` + +## Usage + + + +### `transcend request approve` + +```txt +USAGE + transcend request approve (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--origins PRIVACY_CENTER|ADMIN_DASHBOARD|API|SHOPIFY] [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--transcendUrl value] [--concurrency value] + transcend request approve --help + +Bulk approve a set of privacy requests from the DSR Automation -> Incoming Requests tab. + +FLAGS + --auth The Transcend API key. Requires scopes: "Request Approval and Communication", "View Incoming Requests", "Manage Request Compilation" + --actions The request actions to approve [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--origins] The request origins to approve [PRIVACY_CENTER|ADMIN_DASHBOARD|API|SHOPIFY, separator = ,] + [--silentModeBefore] Any requests made before this date should be marked as silent mode + [--createdAtBefore] Approve requests that were submitted before this time + [--createdAtAfter] Approve requests that were submitted after this time + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk approve all SALE_OPT_OUT and ERASURE requests** + +```sh +transcend request approve --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --transcendUrl=https://api.us.transcend.io +``` + +**Approve all Erasure requests that came through the API** + +```sh +transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --origins=API +``` + +**Approve all requests, but mark any request made before 05/03/2023 as silent mode to prevent emailing those requests** + +```sh +transcend request approve \ + --auth="$TRANSCEND_API_KEY" \ + --actions=SALE_OPT_OUT \ + --silentModeBefore=2024-05-03T00:00:00.000Z +``` + +**Increase the concurrency (defaults to 50)** + +```sh +transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=100 +``` + +**Approve ERASURE requests created within a specific time frame** + +```sh +transcend request approve \ + --auth="$TRANSCEND_API_KEY" \ + --actions=SALE_OPT_OUT \ + --createdAtBefore=2024-05-03T00:00:00.000Z \ + --createdAtAfter=2024-04-03T00:00:00.000Z +``` + +### `transcend request upload` + +```txt +USAGE + transcend request upload (--auth value) [--file value] [--transcendUrl value] [--cacheFilepath value] [--requestReceiptFolder value] [--sombraAuth value] [--concurrency value] [--attributes value] [--isTest] [--isSilent] [--skipSendingReceipt] [--emailIsVerified] [--skipFilterStep] [--dryRun] [--debug] [--defaultPhoneCountryCode value] + transcend request upload --help + +Upload a set of requests from a CSV. + +This 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. + +The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. + +FLAGS + --auth The Transcend API key. Requires scopes: "Submit New Data Subject Request", "View Identity Verification Settings", "View Global Attributes" + [--file] Path to the CSV file of requests to upload [default = ./requests.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--cacheFilepath] The path to the JSON file encoding the metadata used to map the CSV shape to Transcend API [default = ./transcend-privacy-requests-cache.json] + [--requestReceiptFolder] The path to the folder where receipts of each upload are stored [default = ./privacy-request-upload-receipts] + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] + [--attributes] Tag all of the requests with the following attributes. Format: key1:value1;value2,key2:value3;value4 [default = Tags:transcend-cli] + [--isTest] Flag whether the requests being uploaded are test requests or regular requests [default = false] + [--isSilent/--noIsSilent] Flag whether the requests being uploaded should be submitted in silent mode [default = true] + [--skipSendingReceipt] Flag whether to skip sending of the receipt email [default = false] + [--emailIsVerified/--noEmailIsVerified] Indicate whether the email address being uploaded is pre-verified. Set to false to send a verification email [default = true] + [--skipFilterStep] When true, skip the interactive step to filter down the CSV [default = false] + [--dryRun] When true, perform a dry run of the upload instead of calling the API to submit the requests [default = false] + [--debug] Debug logging [default = false] + [--defaultPhoneCountryCode] When uploading phone numbers, if the phone number is missing a country code, assume this country code [default = 1] + -h --help Print help information and exit +``` + +See a demo of the interactive mapping processbelow (_note: the command is slightly different from the one shown in the video, but the arguments are the same._) + +https://user-images.githubusercontent.com/10264973/205477183-d4762087-668c-43f1-a84c-0fce0ec3e132.mov + +#### Examples + +**Upload requests from a CSV file** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv +``` + +**For self-hosted sombras that use an internal key** + +```sh +transcend request upload \ + --auth="$TRANSCEND_API_KEY" \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" \ + --file=/Users/transcend/Desktop/test.csv +``` + +**Run without being prompted to filter requests** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --skipFilterStep +``` + +**Perform a dry run to see what will be uploaded, without calling the Transcend API** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --dryRun +``` + +**Mark the uploaded requests as test requests** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --isTest +``` + +**Send email communications to the users throughout the request lifecycle** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --isSilent=false +``` + +**Upload requests without sending initial email receipt, but still send later emails** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --skipSendingReceipt +``` + +**Increase the concurrency (defaults to 50)** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --concurrency=100 +``` + +**Specify default country code for phone numbers** + +```sh +transcend request upload \ + --auth="$TRANSCEND_API_KEY" \ + --file=/Users/transcend/Desktop/test.csv \ + --defaultPhoneCountryCode=44 +``` + +**Include debug logs - warning, this logs out personal data** + +```sh +transcend request upload --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv --debug +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request upload \ + --auth="$TRANSCEND_API_KEY" \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" \ + --file=/Users/transcend/Desktop/test.csv \ + --transcendUrl=https://api.us.transcend.io +``` + +**Send email verification to user before request continues** + +```sh +transcend request upload \ + --auth="$TRANSCEND_API_KEY" \ + --file=/Users/transcend/Desktop/test.csv \ + --isSilent=false \ + --emailIsVerified=false +``` + +**Tag all uploaded requests with custom fields (formerly known as "attributes")** + +```sh +transcend request upload \ + --auth="$TRANSCEND_API_KEY" \ + --file=/Users/transcend/Desktop/test.csv \ + --attributes=Tags:transcend-cli;my-customer-tag,Customer:acme-corp +``` + +### `transcend request download-files` + +```txt +USAGE + transcend request download-files (--auth value) [--sombraAuth value] [--concurrency value] [--requestIds value]... [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--folderPath value] [--createdAtBefore value] [--createdAtAfter value] [--approveAfterDownload] [--transcendUrl value] + transcend request download-files --help + +Download the files associated with a Data Subject Access Request (DSAR) from DSR Automation -> Incoming Requests tab. + +FLAGS + --auth The Transcend API key. Requires scopes: "View the Request Compilation", "View Incoming Requests", "Request Approval and Communication" + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--concurrency] The concurrency to use when downloading requests in parallel [default = 10] + [--requestIds]... Specify the specific request IDs to download [separator = ,] + [--statuses] The request statuses to download. Comma-separated list. Defaults to APPROVING,DOWNLOADABLE. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--folderPath] The folder to download files to [default = ./dsr-files] + [--createdAtBefore] Download requests that were submitted before this time + [--createdAtAfter] Download requests that were submitted after this time + [--approveAfterDownload] If the request is in status=APPROVING, approve the request after its downloaded [default = false] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +Download the files associated with a Data Subject Access Request (DSAR) from [DSR Automation -> Incoming Requests](https://app.transcend.io/privacy-requests/incoming-requests) tab. + +Screenshot 2025-06-03 at 3 32 00 PM + +#### Examples + +**Download all requests in status=APPROVING or status=DOWNLOADABLE** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io +``` + +**Write files to a specific folder on disk** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" --folderPath=./my-folder +``` + +**Auto approve after download** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" --approveAfterDownload +``` + +**Download requests in APPROVING state only** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" --statuses=APPROVING +``` + +**Increase the concurrency (defaults to 10)** + +```sh +transcend request download-files --auth="$TRANSCEND_API_KEY" --concurrency=100 +``` + +**Download requests in a timeframe** + +```sh +transcend request download-files \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-05-03T00:00:00.000Z \ + --createdAtAfter=2024-04-03T00:00:00.000Z +``` + +**Download specific requests** + +```sh +transcend request download-files \ + --auth="$TRANSCEND_API_KEY" \ + --requestIds=b8c2ce13-9e40-4104-af79-23c68f2a87ba,d5eedc52-0f85-4034-bc01-14951acad5aa +``` + +### `transcend request cancel` + +```txt +USAGE + transcend request cancel (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--requestIds value]... [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--cancellationTitle value] [--transcendUrl value] [--concurrency value] + transcend request cancel --help + +Bulk cancel a set of privacy requests from the DSR Automation -> Incoming Requests tab. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Incoming Requests", "Request Approval and Communication" + --actions The request actions to cancel [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--statuses] The request statuses to cancel. Comma-separated list. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--requestIds]... Specify the specific request IDs to cancel [separator = ,] + [--silentModeBefore] Any requests made before this date should be marked as silent mode for canceling to skip email sending + [--createdAtBefore] Cancel requests that were submitted before this time + [--createdAtAfter] Cancel requests that were submitted after this time + [--cancellationTitle] The title of the email template that should be sent to the requests upon cancelation [default = Request Canceled] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk cancel all open SALE_OPT_OUT and ERASURE requests** + +```sh +transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --transcendUrl=https://api.us.transcend.io +``` + +**Bulk cancel all Erasure (request.type=ERASURE) requests that are in an enriching state (request.status=ENRICHING)** + +```sh +transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --statuses=ENRICHING +``` + +**Send a specific email template to the request that are being canceled** + +```sh +transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --cancellationTitle="Custom Email Template" +``` + +**Cancel all open SALE_OPT_OUT, but mark any request made before 05/03/2023 as silent mode to prevent emailing those requests** + +```sh +transcend request cancel \ + --auth="$TRANSCEND_API_KEY" \ + --actions=SALE_OPT_OUT \ + --silentModeBefore=2024-05-03T00:00:00.000Z +``` + +**Cancel all open SALE_OPT_OUT, within a specific time frame** + +```sh +transcend request cancel \ + --auth="$TRANSCEND_API_KEY" \ + --actions=SALE_OPT_OUT \ + --createdAtBefore=2024-05-03T00:00:00.000Z \ + --createdAtAfter=2024-04-03T00:00:00.000Z +``` + +**Increase the concurrency (defaults to 50)** + +```sh +transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=500 +``` + +**Bulk cancel requests by ID** + +```sh +transcend request cancel \ + --auth="$TRANSCEND_API_KEY" \ + --actions=ACCESS,ERASURE,SALE_OPT_OUT,CONTACT_OPT_OUT \ + --statuses=ENRICHING,COMPILING,APPROVING,WAITING,REQUEST_MADE,ON_HOLD,DELAYED,SECONDARY \ + --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 +``` + +### `transcend request restart` + +```txt +USAGE + transcend request restart (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) (--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED) [--transcendUrl value] [--requestReceiptFolder value] [--sombraAuth value] [--concurrency value] [--requestIds value]... [--emailIsVerified] [--createdAt value] [--silentModeBefore value] [--createdAtBefore value] [--createdAtAfter value] [--sendEmailReceipt] [--copyIdentifiers] [--skipWaitingPeriod] + transcend request restart --help + +Bulk update a set of privacy requests based on a set of request filters. + +FLAGS + --auth The Transcend API key. Requires scopes: "Submit New Data Subject Request", "View the Request Compilation" + --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + --statuses The request statuses to restart [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--requestReceiptFolder] The path to the folder where receipts of each upload are stored [default = ./privacy-request-upload-receipts] + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--concurrency] The concurrency to use when uploading requests in parallel [default = 15] + [--requestIds]... Specify the specific request IDs to restart [separator = ,] + [--emailIsVerified/--noEmailIsVerified] Indicate whether the primary email address is verified. Set to false to send a verification email [default = true] + [--createdAt] Restart requests that were submitted before a specific date + [--silentModeBefore] Requests older than this date should be marked as silent mode + [--createdAtBefore] Restart requests that were submitted before this time + [--createdAtAfter] Restart requests that were submitted after this time + [--sendEmailReceipt] Send email receipts to the restarted requests [default = false] + [--copyIdentifiers] Copy over all enriched identifiers from the initial request [default = false] + [--skipWaitingPeriod] Skip queued state of request and go straight to compiling [default = false] + -h --help Print help information and exit +``` + +#### Examples + +**Restart requests with specific statuses and actions** + +```sh +transcend request restart --auth="$TRANSCEND_API_KEY" --statuses=COMPILING,ENRICHING --actions=ACCESS,ERASURE +``` + +**For self-hosted sombras that use an internal key** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --transcendUrl=https://api.us.transcend.io +``` + +**Increase the concurrency (defaults to 15)** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --concurrency=100 +``` + +**Re-verify emails** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --emailIsVerified=false +``` + +**Restart specific requests by ID** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 +``` + +**Restart requests that were submitted before a specific date** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --createdAt=2024-05-11T00:00:00.000Z +``` + +**Restart requests and place everything in silent mode submitted before a certain date** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --silentModeBefore=2024-12-05T00:00:00.000Z +``` + +**Restart requests within a specific timeframe** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --createdAtBefore=2024-04-05T00:00:00.000Z \ + --createdAtAfter=2024-02-21T00:00:00.000Z +``` + +**Send email receipts to the restarted requests** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --sendEmailReceipt +``` + +**Copy over all enriched identifiers from the initial request** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --copyIdentifiers +``` + +**Skip queued state of request and go straight to compiling** + +```sh +transcend request restart \ + --auth="$TRANSCEND_API_KEY" \ + --statuses=COMPILING,ENRICHING \ + --actions=ACCESS,ERASURE \ + --skipWaitingPeriod +``` + +### `transcend request notify-additional-time` + +```txt +USAGE + transcend request notify-additional-time (--auth value) (--createdAtBefore value) [--createdAtAfter value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--daysLeft value] [--days value] [--requestIds value]... [--emailTemplate value] [--transcendUrl value] [--concurrency value] + transcend request notify-additional-time --help + +Bulk notify a set of privacy requests from the DSR Automation -> Incoming Requests tab that more time is needed to complete the request. Note any request in silent mode will not be emailed. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Incoming Requests", "Request Approval and Communication" + --createdAtBefore Notify requests that are open but submitted before this time + [--createdAtAfter] Notify requests that are open but submitted after this time + [--actions] The request actions to notify [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--daysLeft] Only notify requests that have less than this number of days until they are considered expired [default = 10] + [--days] The number of days to adjust the expiration of the request to [default = 45] + [--requestIds]... Specify the specific request IDs to notify [separator = ,] + [--emailTemplate] The title of the email template that should be sent to the requests [default = Additional Time Needed] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] + -h --help Print help information and exit +``` + +#### Examples + +**Notify all request types that were made before 01/01/2024** + +```sh +transcend request notify-additional-time --auth="$TRANSCEND_API_KEY" --createdAtBefore=2024-01-01T00:00:00.000Z +``` + +**Notify all request types that were made during a date range** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtAfter=2024-12-15T00:00:00.000Z +``` + +**Notify certain request types** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --actions=SALE_OPT_OUT,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --transcendUrl=https://api.us.transcend.io +``` + +**Bulk notify requests by ID** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 +``` + +**Only notify requests that are expiring in the next 3 days or less** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --daysLeft=3 +``` + +**Change number of days to extend request by** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --days=30 +``` + +**Send a specific email template to the request that instead of the default** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --emailTemplate="Custom Email Template" +``` + +**Increase the concurrency (defaults to 50)** + +```sh +transcend request notify-additional-time \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-01-01T00:00:00.000Z \ + --concurrency=500 +``` + +### `transcend request mark-silent` + +```txt +USAGE + transcend request mark-silent (--auth value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--requestIds value]... [--createdAtBefore value] [--createdAtAfter value] [--transcendUrl value] [--concurrency value] + transcend request mark-silent --help + +Bulk update a set of privacy requests from the DSR Automation -> Incoming Requests tab to be in silent mode. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --actions The request actions to mark silent [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--statuses] The request statuses to mark silent. Comma-separated list. Defaults to REQUEST_MADE,WAITING,ENRICHING,COMPILING,DELAYED,APPROVING,SECONDARY,SECONDARY_APPROVING. [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--requestIds]... Specify the specific request IDs to mark silent [separator = ,] + [--createdAtBefore] Mark silent requests that were submitted before this time + [--createdAtAfter] Mark silent requests that were submitted after this time + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 50] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk mark silent all open SALE_OPT_OUT and ERASURE requests** + +```sh +transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=SALE_OPT_OUT,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request mark-silent \ + --auth="$TRANSCEND_API_KEY" \ + --actions=ERASURE \ + --transcendUrl=https://api.us.transcend.io +``` + +**Bulk mark as silent all Erasure (request.type=ERASURE) requests that are in an enriching state (request.status=ENRICHING)** + +```sh +transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=ERASURE --statuses=ENRICHING +``` + +**Bulk mark as silent requests by ID** + +```sh +transcend request mark-silent \ + --auth="$TRANSCEND_API_KEY" \ + --actions=ACCESS,ERASURE,SALE_OPT_OUT,CONTACT_OPT_OUT \ + --statuses=ENRICHING,COMPILING,APPROVING,WAITING,REQUEST_MADE,ON_HOLD,DELAYED,SECONDARY \ + --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 +``` + +**Mark sale opt out requests as silent within a certain date range** + +```sh +transcend request mark-silent \ + --auth="$TRANSCEND_API_KEY" \ + --actions=SALE_OPT_OUT \ + --createdAtBefore=2024-05-03T00:00:00.000Z \ + --createdAtAfter=2024-04-03T00:00:00.000Z +``` + +**Increase the concurrency (defaults to 50)** + +```sh +transcend request mark-silent --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concurrency=500 +``` + +### `transcend request enricher-restart` + +```txt +USAGE + transcend request enricher-restart (--auth value) (--enricherId value) [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--requestEnricherStatuses QUEUED|WAITING|SKIPPED|ERROR|RESOLVED|ACTION_REQUIRED|REMOTE_PROCESSING|WAITING_ON_DEPENDENCIES|POLLING] [--transcendUrl value] [--concurrency value] [--requestIds value]... [--createdAtBefore value] [--createdAtAfter value] + transcend request enricher-restart --help + +Bulk restart a particular enricher across a series of DSRs. + +The API key needs the following scopes: +- Manage Request Compilation + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --enricherId The ID of the enricher to restart + [--actions] The request action to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--requestEnricherStatuses] The request enricher statuses to restart [QUEUED|WAITING|SKIPPED|ERROR|RESOLVED|ACTION_REQUIRED|REMOTE_PROCESSING|WAITING_ON_DEPENDENCIES|POLLING, separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 15] + [--requestIds]... Specify the specific request IDs to restart [separator = ,] + [--createdAtBefore] Restart requests that were submitted before this time + [--createdAtAfter] Restart requests that were submitted after this time + -h --help Print help information and exit +``` + +#### Examples + +**Restart a particular enricher across a series of DSRs** + +```sh +transcend request enricher-restart --auth="$TRANSCEND_API_KEY" --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 +``` + +**Restart specific request types** + +```sh +transcend request enricher-restart \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ + --actions=ACCESS,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request enricher-restart \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ + --transcendUrl=https://api.us.transcend.io +``` + +**Increase the concurrency (defaults to 15)** + +```sh +transcend request enricher-restart \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ + --concurrency=100 +``` + +**Restart requests within a specific timeframe** + +```sh +transcend request enricher-restart \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ + --createdAtBefore=2024-04-05T00:00:00.000Z \ + --createdAtAfter=2024-02-21T00:00:00.000Z +``` + +**Restart requests that are in an error state** + +```sh +transcend request enricher-restart \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ + --requestEnricherStatuses=ERROR +``` + +### `transcend request reject-unverified-identifiers` + +```txt +USAGE + transcend request reject-unverified-identifiers (--auth value) (--identifierNames value)... [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--transcendUrl value] + transcend request reject-unverified-identifiers --help + +Bulk clear out any request identifiers that are unverified. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --identifierNames... The names of identifiers to clear out [separator = ,] + [--actions] The request action to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk clear out any request identifiers that are unverified** + +```sh +transcend request reject-unverified-identifiers --auth="$TRANSCEND_API_KEY" --identifierNames=phone +``` + +**Restart specific request types** + +```sh +transcend request reject-unverified-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --identifierNames=phone \ + --actions=ACCESS,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request reject-unverified-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --identifierNames=phone \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend request export` + +```txt +USAGE + transcend request export (--auth value) [--sombraAuth value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED] [--transcendUrl value] [--file value] [--concurrency value] [--createdAtBefore value] [--createdAtAfter value] [--showTests] [--pageLimit value] + transcend request export --help + +Export privacy requests and request identifiers to a CSV file. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Incoming Requests", "View the Request Compilation" + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--actions] The request actions to export [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--statuses] The request statuses to export [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--file] Path to the CSV file where identifiers will be written to [default = ./transcend-request-export.csv] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] + [--createdAtBefore] Pull requests that were submitted before this time + [--createdAtAfter] Pull requests that were submitted after this time + [--showTests/--noShowTests] Filter for test requests or production requests - when not provided, pulls both + [--pageLimit] The page limit to use when pulling in pages of requests [default = 100] + -h --help Print help information and exit +``` + +#### Examples + +**Pull all requests** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" +``` + +**Filter for specific actions and statuses** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --statuses=COMPILING,ENRICHING --actions=ACCESS,ERASURE +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io +``` + +**With Sombra authentication** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --sombraAuth="$SOMBRA_INTERNAL_KEY" +``` + +**Increase the concurrency (defaults to 100)** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --concurrency=500 +``` + +**Filter for production requests only** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --showTests=false +``` + +**Filter for requests within a date range** + +```sh +transcend request export \ + --auth="$TRANSCEND_API_KEY" \ + --createdAtBefore=2024-04-05T00:00:00.000Z \ + --createdAtAfter=2024-02-21T00:00:00.000Z +``` + +**Write to a specific file location** + +```sh +transcend request export --auth="$TRANSCEND_API_KEY" --file=./path/to/file.csv +``` + +### `transcend request skip-preflight-jobs` + +```txt +USAGE + transcend request skip-preflight-jobs (--auth value) (--enricherIds value)... [--transcendUrl value] + transcend request skip-preflight-jobs --help + +This command allows for bulk skipping preflight checks. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --enricherIds... The ID of the enrichers to skip privacy request jobs for [separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk skipping preflight checks** + +```sh +transcend request skip-preflight-jobs --auth="$TRANSCEND_API_KEY" --enricherIds=70810f2e-cf90-43f6-9776-901a5950599f +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request skip-preflight-jobs \ + --auth="$TRANSCEND_API_KEY" \ + --enricherIds=70810f2e-cf90-43f6-9776-901a5950599f,db1e64ba-cea6-43ff-ad27-5dc8122e5224 \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend request system mark-request-data-silos-completed` + +```txt +USAGE + transcend request system mark-request-data-silos-completed (--auth value) (--dataSiloId value) [--file value] [--transcendUrl value] + transcend request system mark-request-data-silos-completed --help + +This command takes in a CSV of Request IDs as well as a Data Silo ID and marks all associated privacy request jobs as completed. +This command is useful with the "Bulk Response" UI. The CSV is expected to have 1 column named "Request Id". + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --dataSiloId The ID of the data silo to pull in + [--file] Path to the CSV file where identifiers will be written to. The CSV is expected to have 1 column named "Request Id". [default = ./request-identifiers.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Mark all associated privacy request jobs as completed** + +```sh +transcend request system mark-request-data-silos-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f +``` + +**Pull to a specific file location** + +```sh +transcend request system mark-request-data-silos-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --file=/Users/transcend/Desktop/test.csv +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request system mark-request-data-silos-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend request system retry-request-data-silos` + +```txt +USAGE + transcend request system retry-request-data-silos (--auth value) (--dataSiloId value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--transcendUrl value] + transcend request system retry-request-data-silos --help + +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. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --dataSiloId The ID of the data silo to pull in + --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk restarting a set of data silos jobs for open privacy requests** + +```sh +transcend request system retry-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ACCESS +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request system retry-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ACCESS \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend request system skip-request-data-silos` + +```txt +USAGE + transcend request system skip-request-data-silos (--auth value) (--dataSiloId value) [--transcendUrl value] (--statuses REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED) [--status SKIPPED|RESOLVED] + transcend request system skip-request-data-silos --help + +This command allows for bulk skipping all open privacy request jobs for a particular data silo. This command is useful if you want to disable a data silo and then clear out any active privacy requests that are still queued up for that data silo. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Compilation" + --dataSiloId The ID of the data silo to skip privacy request jobs for + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + --statuses The request statuses to skip [REQUEST_MADE|FAILED_VERIFICATION|ENRICHING|ON_HOLD|WAITING|COMPILING|APPROVING|DELAYED|COMPLETED|DOWNLOADABLE|VIEW_CATEGORIES|CANCELED|SECONDARY|SECONDARY_COMPLETED|SECONDARY_APPROVING|REVOKED, separator = ,] + [--status] The status to set the request data silo job to [SKIPPED|RESOLVED, default = SKIPPED] + -h --help Print help information and exit +``` + +#### Examples + +**Bulk skipping all open privacy request jobs for a particular data silo** + +```sh +transcend request system skip-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request system skip-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --transcendUrl=https://api.us.transcend.io +``` + +**Only mark as completed requests in "removing data" phase** + +```sh +transcend request system skip-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --statuses=SECONDARY +``` + +**Set to status "RESOLVED" instead of status "SKIPPED"** + +```sh +transcend request system skip-request-data-silos \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --status=RESOLVED +``` + +### `transcend request preflight pull-identifiers` + +```txt +USAGE + transcend request preflight pull-identifiers (--auth value) [--sombraAuth value] [--transcendUrl value] [--file value] [--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD] [--concurrency value] + transcend request preflight pull-identifiers --help + +This command pulls down the set of privacy requests that are currently pending manual enrichment. + +This is useful for the following workflow: + +1. Pull identifiers to CSV: + + transcend request preflight pull-identifiers --file=./enrichment-requests.csv + +2. Fill out the CSV with additional identifiers + +3. Push updated back to Transcend: + + transcend request preflight push-identifiers --file=./enrichment-requests.csv + +FLAGS + --auth The Transcend API key. Requires scopes: "View Incoming Requests", "View the Request Compilation" + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--file] Path to the CSV file where requests will be written to [default = ./manual-enrichment-identifiers.csv] + [--actions] The request actions to pull for [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] + -h --help Print help information and exit +``` + +#### Examples + +**Pull down the set of privacy requests that are currently pending manual enrichment** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" +``` + +**Pull to a specific file location** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --file=/Users/transcend/Desktop/test.csv +``` + +**For specific types of requests** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --actions=ACCESS,ERASURE +``` + +**For US hosted infrastructure** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io +``` + +**With Sombra authentication** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --sombraAuth="$SOMBRA_INTERNAL_KEY" +``` + +**With specific concurrency** + +```sh +transcend request preflight pull-identifiers --auth="$TRANSCEND_API_KEY" --concurrency=200 +``` + +### `transcend request preflight push-identifiers` + +```txt +USAGE + transcend request preflight push-identifiers (--auth value) (--enricherId value) [--sombraAuth value] [--transcendUrl value] [--file value] [--markSilent] [--concurrency value] + transcend request preflight push-identifiers --help + +This command push up a set of identifiers for a set of requests pending manual enrichment. + +This is useful for the following workflow: + +1. Pull identifiers to CSV: + + transcend request preflight pull-identifiers --file=./enrichment-requests.csv + +2. Fill out the CSV with additional identifiers + +3. Push updated back to Transcend: + + transcend request preflight push-identifiers --file=./enrichment-requests.csv + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Request Identity Verification", "Manage Request Compilation" + --enricherId The ID of the Request Enricher to upload to + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--file] Path to the CSV file where requests will be written to [default = ./manual-enrichment-identifiers.csv] + [--markSilent] When true, set requests into silent mode before enriching [default = false] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] + -h --help Print help information and exit +``` + +#### Examples + +**Push up a set of identifiers for a set of requests pending manual enrichment** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf +``` + +**Pull to a specific file location** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ + --file=/Users/transcend/Desktop/test.csv +``` + +**For US hosted infrastructure** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ + --transcendUrl=https://api.us.transcend.io +``` + +**With Sombra authentication** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" +``` + +**With specific concurrency** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ + --concurrency=200 +``` + +**When enriching requests, mark all requests as silent mode before processing** + +```sh +transcend request preflight push-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --enricherId=27d45a0d-7d03-47fa-9b30-6d697005cfcf \ + --markSilent +``` + +### `transcend request cron pull-identifiers` + +```txt +USAGE + transcend request cron pull-identifiers (--auth value) (--dataSiloId value) (--actions AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD) [--file value] [--transcendUrl value] [--sombraAuth value] [--pageLimit value] [--skipRequestCount] [--chunkSize value] + transcend request cron pull-identifiers --help + +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. + +For 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. + +Read more at https://docs.transcend.io/docs/integrations/cron-job-integration. + +FLAGS + --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. No scopes are required for this command. + --dataSiloId The ID of the data silo to pull in + --actions The request actions to restart [AUTOMATED_DECISION_MAKING_OPT_OUT|USE_OF_SENSITIVE_INFORMATION_OPT_OUT|CONTACT_OPT_OUT|SALE_OPT_OUT|TRACKING_OPT_OUT|CUSTOM_OPT_OUT|AUTOMATED_DECISION_MAKING_OPT_IN|USE_OF_SENSITIVE_INFORMATION_OPT_IN|SALE_OPT_IN|TRACKING_OPT_IN|CONTACT_OPT_IN|CUSTOM_OPT_IN|ACCESS|ERASURE|RECTIFICATION|RESTRICTION|BUSINESS_PURPOSE|PLACE_ON_LEGAL_HOLD|REMOVE_FROM_LEGAL_HOLD, separator = ,] + [--file] Path to the CSV file where identifiers will be written to [default = ./cron-identifiers.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--pageLimit] The page limit to use when pulling in pages of identifiers [default = 100] + [--skipRequestCount] 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 [default = false] + [--chunkSize] 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 [default = 10000] + -h --help Print help information and exit +``` + +#### Examples + +**Pull outstanding identifiers for a data silo** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE +``` + +**Pull to a specific file location** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --file=/Users/transcend/Desktop/test.csv +``` + +**For self-hosted sombras that use an internal key** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --transcendUrl=https://api.us.transcend.io +``` + +**Specifying the page limit, defaults to 100** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --pageLimit=300 \ + --chunkSize=6000 +``` + +**Specifying the chunk size for large datasets to avoid file size limits (defaults to 100,000 rows per file)** + +```sh +transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --chunkSize=50000 +``` + +### `transcend request cron mark-identifiers-completed` + +```txt +USAGE + transcend request cron mark-identifiers-completed (--auth value) (--dataSiloId value) [--file value] [--transcendUrl value] [--sombraAuth value] + transcend request cron mark-identifiers-completed --help + +This command takes the output of "transcend request cron pull-identifiers" and notifies Transcend that all of the requests in the CSV have been processed. +This is used in the workflow like: + +1. Pull identifiers to CSV: + + transcend request cron pull-identifiers \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --actions=ERASURE \ + --file=./outstanding-requests.csv + +2. Run your process to operate on that CSV of requests. + +3. Notify Transcend of completion + + transcend request cron mark-identifiers-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --file=./outstanding-requests.csv + +Read more at https://docs.transcend.io/docs/integrations/cron-job-integration. + +FLAGS + --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. No scopes are required for this command. + --dataSiloId The ID of the data silo to pull in + [--file] Path to the CSV file where identifiers will be written to [default = ./cron-identifiers.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + -h --help Print help information and exit +``` + +#### Examples + +**Mark identifiers as completed** + +```sh +transcend request cron mark-identifiers-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f +``` + +**Pull to a specific file location** + +```sh +transcend request cron mark-identifiers-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --file=/Users/transcend/Desktop/test.csv +``` + +**For self-hosted sombras that use an internal key** + +```sh +transcend request cron mark-identifiers-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend request cron mark-identifiers-completed \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=70810f2e-cf90-43f6-9776-901a5950599f \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend consent build-xdi-sync-endpoint` + +```txt +USAGE + transcend consent build-xdi-sync-endpoint (--auth value) (--xdiLocation value) [--file value] [--removeIpAddresses] [--domainBlockList value] [--xdiAllowedCommands value] [--transcendUrl value] + transcend consent build-xdi-sync-endpoint --help + +This command allows for building of the XDI Sync Endpoint across a set of Transcend accounts. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Consent Manager" + --xdiLocation The location of the XDI that will be loaded by the generated sync endpoint + [--file] The HTML file path where the sync endpoint should be written [default = ./sync-endpoint.html] + [--removeIpAddresses/--noRemoveIpAddresses] When true, remove IP addresses from the domain list [default = true] + [--domainBlockList] The set of domains that should be excluded from the sync endpoint. Comma-separated list. [default = localhost] + [--xdiAllowedCommands] The allowed set of XDI commands [default = ConsentManager:Sync] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Build XDI sync endpoint** + +```sh +transcend consent build-xdi-sync-endpoint --auth="$TRANSCEND_API_KEY" --xdiLocation=https://cdn.your-site.com/xdi.js +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent build-xdi-sync-endpoint \ + --auth="$TRANSCEND_API_KEY" \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --transcendUrl=https://api.us.transcend.io +``` + +**Pull to specific file location** + +```sh +transcend consent build-xdi-sync-endpoint \ + --auth="$TRANSCEND_API_KEY" \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --file=./my-folder/sync-endpoint.html +``` + +**Don't filter out regular expressions** + +```sh +transcend consent build-xdi-sync-endpoint \ + --auth="$TRANSCEND_API_KEY" \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --removeIpAddresses=false +``` + +**Filter out certain domains that should not be included in the sync endpoint definition** + +```sh +transcend consent build-xdi-sync-endpoint \ + --auth="$TRANSCEND_API_KEY" \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --domainBlockList=ignored.com,localhost +``` + +**Override XDI allowed commands** + +```sh +transcend consent build-xdi-sync-endpoint \ + --auth="$TRANSCEND_API_KEY" \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --xdiAllowedCommands=ExtractIdentifiers:Simple +``` + +**Configuring across multiple Transcend Instances** + +```sh +# Pull down API keys across all Transcend instances +transcend admin generate-api-keys \ + --email="$TRANSCEND_EMAIL" \ + --password="$TRANSCEND_PASSWORD" \ + --transcendUrl=https://api.us.transcend.io \ + --scopes="View Consent Manager" \ + --apiKeyTitle="[cli][$TRANSCEND_EMAIL] XDI Endpoint Construction" \ + --file=./api-keys.json \ + --parentOrganizationId=1821d872-6114-406e-90c3-73b4d5e246cf + +# Path list of API keys as authentication +transcend consent build-xdi-sync-endpoint \ + --auth=./api-keys.json \ + --xdiLocation=https://cdn.your-site.com/xdi.js \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend consent pull-consent-metrics` + +```txt +USAGE + transcend consent pull-consent-metrics (--auth value) (--start value) [--end value] [--folder value] [--bin value] [--transcendUrl value] + transcend consent pull-consent-metrics --help + +This command allows for pulling consent manager metrics for a Transcend account, or a set of Transcend accounts. + +By 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: + +- `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 });` +- `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 });` +- `CONSENT_SESSIONS_BY_REGIME_Default.csv` -> this contains the number of sessions detected for the bin period +- `PRIVACY_SIGNAL_TIMESERIES_DNT.csv` -> the number of DNT signals detected. +- `PRIVACY_SIGNAL_TIMESERIES_GPC.csv` -> the number of GPC signals detected. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Consent Manager" + --start The start date to pull metrics from + [--end] The end date to pull metrics until + [--folder] The folder to save metrics to [default = ./consent-metrics/] + [--bin] The bin metric when pulling data (1h or 1d) [default = 1d] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Pull consent manager metrics for a Transcend account** + +```sh +transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent pull-consent-metrics \ + --auth="$TRANSCEND_API_KEY" \ + --start=2024-01-01T00:00:00.000Z \ + --transcendUrl=https://api.us.transcend.io +``` + +**Pull start and end date explicitly** + +```sh +transcend consent pull-consent-metrics \ + --auth="$TRANSCEND_API_KEY" \ + --start=2024-01-01T00:00:00.000Z \ + --end=2024-03-01T00:00:00.000Z +``` + +**Save to an explicit folder** + +```sh +transcend consent pull-consent-metrics \ + --auth="$TRANSCEND_API_KEY" \ + --start=2024-01-01T00:00:00.000Z \ + --end=2024-03-01T00:00:00.000Z \ + --folder=./my-folder/ +``` + +**Bin data hourly vs daily** + +```sh +transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z --bin=1h +``` + +### `transcend consent pull-consent-preferences` + +```txt +USAGE + transcend consent pull-consent-preferences (--auth value) (--partition value) [--sombraAuth value] [--file value] [--transcendUrl value] [--timestampBefore value] [--timestampAfter value] [--identifiers value]... [--concurrency value] + transcend consent pull-consent-preferences --help + +This command allows for pull of consent preferences from the Managed Consent Database. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Managed Consent Database Admin API" + --partition The partition key to download consent preferences to + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--file] Path to the CSV file to save preferences to [default = ./preferences.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--timestampBefore] Filter for consents updated this time + [--timestampAfter] Filter for consents updated after this time + [--identifiers]... Filter for specific identifiers [separator = ,] + [--concurrency] The concurrency to use when downloading consents in parallel [default = 100] + -h --help Print help information and exit +``` + +#### Examples + +**Fetch all consent preferences from partition key** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 +``` + +**Fetch all consent preferences from partition key and save to ./consent.csv** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --file=./consent.csv +``` + +**Filter on consent updates before a date** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --timestampBefore=2024-04-03T00:00:00.000Z +``` + +**Filter on consent updates after a date** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --timestampAfter=2024-04-03T00:00:00.000Z +``` + +**For self-hosted sombras that use an internal key** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --sombraAuth="$SOMBRA_INTERNAL_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent pull-consent-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend consent update-consent-manager` + +```txt +USAGE + transcend consent update-consent-manager (--auth value) (--bundleTypes PRODUCTION|TEST) [--deploy] [--transcendUrl value] + transcend consent update-consent-manager --help + +This command allows for updating Consent Manager to latest version. The Consent Manager bundle can also be deployed using this command. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Consent Manager Developer Settings" + --bundleTypes The bundle types to deploy. Defaults to PRODUCTION,TEST. [PRODUCTION|TEST, separator = ,] + [--deploy] When true, deploy the Consent Manager after updating the version [default = false] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Update Consent Manager to latest version** + +```sh +transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --transcendUrl=https://api.us.transcend.io +``` + +**Update version and deploy bundles** + +```sh +transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --deploy +``` + +**Update just the TEST bundle** + +```sh +transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --bundleTypes=TEST +``` + +**Update just the PRODUCTION bundle** + +```sh +transcend consent update-consent-manager --auth="$TRANSCEND_API_KEY" --bundleTypes=PRODUCTION +``` + +**Update multiple organizations at once** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="Manage Consent Manager" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./transcend-api-keys.json +transcend consent update-consent-manager --auth=./transcend-api-keys.json --deploy +``` + +### `transcend consent upload-consent-preferences` + +```txt +USAGE + transcend consent upload-consent-preferences (--base64EncryptionKey value) (--base64SigningKey value) (--partition value) [--file value] [--consentUrl value] [--concurrency value] + transcend consent upload-consent-preferences --help + +This command allows for updating of consent preferences to the Managed Consent Database. + +FLAGS + --base64EncryptionKey The encryption key used to encrypt the userId + --base64SigningKey The signing key used to prove authentication of consent request + --partition The partition key to download consent preferences to + [--file] The file to pull consent preferences from [default = ./preferences.csv] + [--consentUrl] URL of the Transcend consent backend. Use https://consent.us.transcend.io for US hosting [default = https://consent.transcend.io] + [--concurrency] The concurrency to use when uploading requests in parallel [default = 100] + -h --help Print help information and exit +``` + +#### Examples + +**Upload consent preferences to partition key** + +```sh +transcend consent upload-consent-preferences \ + --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ + --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 +``` + +**Upload consent preferences to partition key from file** + +```sh +transcend consent upload-consent-preferences \ + --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ + --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --file=./consent.csv +``` + +**Upload consent preferences to partition key and set concurrency** + +```sh +transcend consent upload-consent-preferences \ + --base64EncryptionKey="$TRANSCEND_CONSENT_ENCRYPTION_KEY" \ + --base64SigningKey="$TRANSCEND_CONSENT_SIGNING_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --concurrency=200 +``` + +### `transcend consent upload-cookies-from-csv` + +```txt +USAGE + transcend consent upload-cookies-from-csv (--auth value) (--trackerStatus LIVE|NEEDS_REVIEW) [--file value] [--transcendUrl value] + transcend consent upload-cookies-from-csv --help + +Upload cookies from CSV. This command allows for uploading of cookies from CSV. + +Step 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. + +Step 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. + +Step 3) Upload the modified CSV file back into the dashboard with this command. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Data Flows" + --trackerStatus The status of the cookies you will upload. [LIVE|NEEDS_REVIEW] + [--file] Path to the CSV file to upload [default = ./cookies.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Upload the file of cookies in ./cookies.csv into the "Approved" tab** + +```sh +transcend consent upload-cookies-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE +``` + +**Upload the file of cookies in ./cookies.csv into the "Triage" tab** + +```sh +transcend consent upload-cookies-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=NEEDS_REVIEW +``` + +**Specifying the CSV file to read from** + +```sh +transcend consent upload-cookies-from-csv \ + --auth="$TRANSCEND_API_KEY" \ + --trackerStatus=LIVE \ + --file=./custom/my-cookies.csv +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent upload-cookies-from-csv \ + --auth="$TRANSCEND_API_KEY" \ + --trackerStatus=LIVE \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend consent upload-data-flows-from-csv` + +```txt +USAGE + transcend consent upload-data-flows-from-csv (--auth value) (--trackerStatus LIVE|NEEDS_REVIEW) [--file value] [--classifyService] [--transcendUrl value] + transcend consent upload-data-flows-from-csv --help + +Upload data flows from CSV. This command allows for uploading of data flows from CSV. + +Step 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. + +Step 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. + +Step 3) Upload the modified CSV file back into the dashboard with this command. + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Data Flows" + --trackerStatus The status of the data flows you will upload. [LIVE|NEEDS_REVIEW] + [--file] Path to the CSV file to upload [default = ./data-flows.csv] + [--classifyService] When true, automatically assign the service for a data flow based on the domain that is specified [default = false] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +To get a CSV of data flows, you can download the data flows 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. + +export-data-flows + +#### Examples + +**Upload the file of data flows in ./data-flows.csv into the "Approved" tab** + +```sh +transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE +``` + +**Upload the file of data flows in ./data-flows.csv into the "Triage" tab** + +```sh +transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=NEEDS_REVIEW +``` + +**Specifying the CSV file to read from** + +```sh +transcend consent upload-data-flows-from-csv \ + --auth="$TRANSCEND_API_KEY" \ + --trackerStatus=LIVE \ + --file=./custom/my-data-flows.csv +``` + +**Have Transcend automatically fill in the service names by looking up the data flow host in Transcend's database** + +```sh +transcend consent upload-data-flows-from-csv --auth="$TRANSCEND_API_KEY" --trackerStatus=LIVE --classifyService +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent upload-data-flows-from-csv \ + --auth="$TRANSCEND_API_KEY" \ + --trackerStatus=LIVE \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend consent upload-preferences` + +```txt +USAGE + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] [--file value] [--directory value] [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] + transcend consent upload-preferences --help + +Upload preference management data to your Preference Store. + +This 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. + +The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. + +Parallel preference uploader (Node 22+ ESM/TS) +----------------------------------------------------------------------------- +- Spawns a pool of child *processes* (not threads) to run uploads in parallel. +- Shows a live dashboard in the parent terminal with progress per worker. +- Creates per-worker log files and (optionally) opens OS terminals to tail them. +- Uses the same module as both parent and child; the child mode is toggled + by the presence of a CLI flag ('--child-upload-preferences'). + +FLAGS + --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" + --partition The partition key to download consent preferences to + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--file] Path to the CSV file to load preferences from + [--directory] Path to the directory of CSV files to load preferences from + [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] + [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] + [--receiptFileDir] Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided. + [--schemaFilePath] The path to where the schema for the file should be saved. If file is provided, it will default to ./-preference-upload-schema.json If directory is provided, it will default to /../preference-upload-schema.json + [--skipWorkflowTriggers] Whether to skip workflow triggers when uploading to preference store [default = false] + [--forceTriggerWorkflows] Whether to force trigger workflows for existing consent records [default = false] + [--skipConflictUpdates] Whether to skip uploading of any records where the preference store and file have a hard conflict [default = false] + [--isSilent/--noIsSilent] Whether to skip sending emails in workflows [default = true] + [--attributes] Attributes to add to any DSR request if created. Comma-separated list of key:value pairs. [default = Tags:transcend-cli,Source:transcend-cli] + [--receiptFilepath] Store resulting, continuing where left off [default = ./preference-management-upload-receipts.json] + [--concurrency] The number of concurrent processes to use to upload the files. When this is not set, it defaults to the number of CPU cores available on the machine. e.g. if there are 5 concurrent processes for 15 files, each parallel job would get 3 files to process. + [--uploadConcurrency] When uploading preferences to v1/preferences - this is the number of concurrent requests made at any given time by a single process.This is NOT the batch size—it's how many batch *tasks* run in parallel. The number of total concurrent requests is maxed out at concurrency * uploadConcurrency. [default = 75] + [--maxChunkSize] When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency. [default = 25] + [--rateLimitRetryDelay] When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. This is only used if the request is rate limited by the Transcend API. If the request fails for any other reason, it will not be retried. [default = 3000] + [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] + [--downloadIdentifierConcurrency] When downloading identifiers for the upload - this is the number of concurrent requests to make. This is only used if the records are not already cached in the preference store. [default = 30] + [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 10] + --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. + --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. + [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. + -h --help Print help information and exit +``` + +A sample CSV can be found [here](./examples/cli-upload-preferences-example.csv). In this example, `Sales` and `Marketing` are two custom Purposes, and `SalesCommunications` and `MarketingCommunications` are Preference Topics. During the interactive CLI prompt, you can map these columns to the slugs stored in Transcend! + +#### Examples + +**Upload consent preferences to partition key `4d1c5daa-90b7-4d18-aa40-f86a43d2c726`** + +```sh +transcend consent upload-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --file=./preferences.csv \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 +``` + +**Upload consent preferences with additional options** + +```sh +transcend consent upload-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --file=./preferences.csv \ + --dryRun \ + --skipWorkflowTriggers \ + --skipConflictUpdates \ + --isSilent=false \ + --attributes=Tags:transcend-cli,Source:transcend-cli \ + --receiptFilepath=./preference-management-upload-receipts.json +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend consent upload-preferences \ + --auth="$TRANSCEND_API_KEY" \ + --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ + --file=./preferences.csv \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend inventory pull` + +```txt +USAGE + transcend inventory pull (--auth value) [--resources all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes] [--file value] [--transcendUrl value] [--dataSiloIds value]... [--integrationNames value]... [--trackerStatuses LIVE|NEEDS_REVIEW] [--pageSize value] [--skipDatapoints] [--skipSubDatapoints] [--includeGuessedCategories] [--debug] + transcend inventory pull --help + +Generates a transcend.yml by pulling the configuration from your Transcend instance. + +The API key needs various scopes depending on the resources being pulled (see the CLI's README for more details). + +This command can be helpful if you are looking to: + +- Copy your data into another instance +- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code. + +FLAGS + --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. + [--resources] The different resource types to pull in. Defaults to dataSilos,enrichers,templates,apiKeys. [all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes, separator = ,] + [--file] Path to the YAML file to pull into [default = ./transcend.yml] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--dataSiloIds]... The UUIDs of the data silos that should be pulled into the YAML file [separator = ,] + [--integrationNames]... The types of integrations to pull down [separator = ,] + [--trackerStatuses] The statuses of consent manager trackers to pull down. Defaults to all statuses. [LIVE|NEEDS_REVIEW, separator = ,] + [--pageSize] The page size to use when paginating over the API [default = 50] + [--skipDatapoints] When true, skip pulling in datapoints alongside data silo resource [default = false] + [--skipSubDatapoints] When true, skip pulling in subDatapoints alongside data silo resource [default = false] + [--includeGuessedCategories] When true, included guessed data categories that came from the content classifier [default = false] + [--debug] Set to true to include debug logs while pulling the configuration [default = false] + -h --help Print help information and exit +``` + +#### Scopes + +The API key permissions for this command vary based on the `resources` argument: + +| Resource | Description | Scopes | Link | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| apiKeys | API Key definitions assigned to Data Silos. API keys cannot be created through the CLI, but you can map API key usage to Data Silos. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) | +| customFields | Custom field definitions that define extra metadata for each table in the Admin Dashboard. | View Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) | +| templates | Email templates. Only template titles can be created and mapped to other resources. | View Email Templates | [DSR Automation -> Email Templates](https://app.transcend.io/privacy-requests/email-templates) | +| dataSilos | The Data Silo/Integration definitions. | View Data Map, View Data Subject Request Settings | [Data Inventory -> Data Silos](https://app.transcend.io/data-map/data-inventory/) and [Infrastucture -> Integrations](https://app.transcend.io/infrastructure/integrationsdata-silos) | +| enrichers | The Privacy Request enricher configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | +| dataFlows | Consent Manager Data Flow definitions. | View Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) | +| businessEntities | The business entities in the data inventory. | View Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) | +| processingActivities | The processing activities in the data inventory. | View Data Inventory | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) | +| actions | The Privacy Request action settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | +| dataSubjects | The Privacy Request data subject settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | +| identifiers | The Privacy Request identifier configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | +| cookies | Consent Manager Cookie definitions. | View Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) | +| consentManager | Consent Manager general settings, including domain list. | View Consent Manager | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) | +| partitions | The partitions in the account (often representative of separate data controllers). | View Consent Manager | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) | +| prompts | The Transcend AI prompts | View Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) | +| promptPartials | The Transcend AI prompt partials | View Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partialss) | +| promptGroups | The Transcend AI prompt groups | View Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) | +| agents | The agents in the prompt manager. | View Prompts | [Prompt Manager -> Agents](https://app.transcend.io/prompts/agents) | +| agentFunctions | The agent functions in the prompt manager. | View Prompts | [Prompt Manager -> Agent Functions](https://app.transcend.io/prompts/agent-functions) | +| agentFiles | The agent files in the prompt manager. | View Prompts | [Prompt Manager -> Agent Files](https://app.transcend.io/prompts/agent-files) | +| vendors | The vendors in the data inventory. | View Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) | +| dataCategories | The data categories in the data inventory. | View Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) | +| processingPurposes | The processing purposes in the data inventory. | View Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) | +| actionItems | Onboarding related action items | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | +| actionItemCollections | Onboarding related action item group names | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | +| teams | Team definitions of users and scope groupings | View Scopes | [Administration -> Teams](https://app.transcend.io/admin/teams) | +| privacyCenters | The privacy center configurations. | View Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) | +| policies | The privacy center policies. | View Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) | +| messages | Message definitions used across consent, privacy center, email templates and more. | View Internationalization Messages | [Privacy Center -> Messages](https://app.transcend.io/privacy-center/messages-internationalization), [Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) | +| assessments | Assessment responses. | View Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) | +| assessmentTemplates | Assessment template configurations. | View Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) | +| purposes | Consent purposes and related preference management topics. | View Consent Manager, View Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) | + +#### Examples + +**Write out file to ./transcend.yml** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" +``` + +**Write out file to custom location** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml +``` + +**Pull specific data silo by ID** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --dataSiloIds=710fec3c-7bcc-4c9e-baff-bf39f9bec43e +``` + +**Pull specific types of data silos** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --integrationNames=salesforce,snowflake +``` + +**Pull specific resource types** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=apiKeys,templates,dataSilos,enrichers +``` + +**Pull data flows and cookies with specific tracker statuses (see [this example](./examples/data-flows-cookies.yml))** + +```sh +transcend inventory pull \ + --auth="$TRANSCEND_API_KEY" \ + --resources=dataFlows,cookies \ + --trackerStatuses=NEEDS_REVIEW,LIVE +``` + +**Pull data silos without datapoint information** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipDatapoints +``` + +**Pull data silos without subdatapoint information** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipSubDatapoints +``` + +**Pull data silos with guessed categories** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --includeGuessedCategories +``` + +**Pull custom field definitions only (see [this example](./examples/attributes.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields +``` + +**Pull business entities only (see [this example](./examples/business-entities.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=businessEntities +``` + +**Pull processing activities only (see [this example](./examples/processing-activities.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=processingActivities +``` + +**Pull enrichers and identifiers (see [this example](./examples/enrichers.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=enrichers,identifiers +``` + +**Pull onboarding action items (see [this example](./examples/action-items.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actionItems,actionItemCollections +``` + +**Pull consent manager domain list (see [this example](./examples/consent-manager-domains.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=consentManager +``` + +**Pull identifier configurations (see [this example](./examples/identifiers.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=identifiers +``` + +**Pull request actions configurations (see [this example](./examples/actions.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actions +``` + +**Pull consent manager purposes and preference management topics (see [this example](./examples/purposes.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=purposes +``` + +**Pull data subject configurations (see [this example](./examples/data-subjects.yml))** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSubjects +``` + +**Pull assessments and assessment templates** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=assessments,assessmentTemplates +``` + +**Pull everything** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=all +``` + +**Pull configuration files across multiple instances** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Consent Manager" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./transcend-api-keys.json +transcend inventory pull --auth=./transcend-api-keys.json --resources=consentManager --file=./transcend/ +``` + +Note: This command will overwrite the existing transcend.yml file that you have locally. + +### `transcend inventory push` + +```txt +USAGE + transcend inventory push (--auth value) [--file value] [--transcendUrl value] [--pageSize value] [--variables value] [--publishToPrivacyCenter] [--classifyService] [--deleteExtraAttributeValues] + transcend inventory push --help + +Given a transcend.yml file, sync the contents up to your Transcend instance. + +FLAGS + --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. + [--file] Path to the YAML file to push from [default = ./transcend.yml] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--pageSize] The page size to use when paginating over the API [default = 50] + [--variables] The variables to template into the YAML file when pushing configuration. Comma-separated list of key:value pairs. [default = ""] + [--publishToPrivacyCenter] When true, publish the configuration to the Privacy Center [default = false] + [--classifyService] When true, automatically assign the service for a data flow based on the domain that is specified [default = false] + [--deleteExtraAttributeValues] When true and syncing attributes, delete any extra attributes instead of just upserting [default = false] + -h --help Print help information and exit +``` + +#### Scopes + +The scopes for `transcend inventory push` are the same as the scopes for [`transcend inventory pull`](#transcend-inventory-pull). + +#### Examples + +**Looks for file at ./transcend.yml** + +```sh +transcend inventory push --auth="$TRANSCEND_API_KEY" +``` + +**Looks for file at custom location ./custom/location.yml** + +```sh +transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml +``` + +**Apply service classifier to all data flows** + +```sh +transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService +``` + +**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** + +```sh +transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues +``` + +**Use dynamic variables to fill out parameters in YAML files (see [./examples/multi-instance.yml](./examples/multi-instance.yml))** + +```sh +transcend inventory push --auth="$TRANSCEND_API_KEY" --variables=domain:acme.com,stage:staging +``` + +**Push a single .yml file configuration into multiple Transcend instances** + +This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./transcend-api-keys.json +transcend inventory pull --auth="$TRANSCEND_API_KEY" +transcend inventory push --auth=./transcend-api-keys.json +``` + +**Push multiple .yml file configurations into multiple Transcend instances** + +This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./transcend-api-keys.json +transcend inventory pull --auth=./transcend-api-keys.json --file=./transcend/ +# +transcend inventory push --auth=./transcend-api-keys.json --file=./transcend/ +``` + +**Apply service classifier to all data flows** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataFlows +transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService +``` + +**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** + +```sh +transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields +transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues +``` + +Some things to note about this sync process: + +1. Any field that is defined in your .yml file will be synced up to app.transcend.io. If any change was made on the Admin Dashboard, it will be overwritten. +2. If you omit a field from the .yml file, this field will not be synced. This gives you the ability to define as much or as little configuration in your transcend.yml file as you would like, and let the remainder of fields be labeled through the Admin Dashboard +3. If you define new data subjects, identifiers, data silos or datapoints that were not previously defined on the Admin Dashboard, the CLI will create these new resources automatically. +4. Currently, this CLI does not handle deleting or renaming of resources. If you need to delete or rename a data silo, identifier, enricher or API key, you should make the change on the Admin Dashboard. +5. The only resources that this CLI will not auto-generate are: + +- a) Data silo owners: If you assign an email address to a data silo, you must first make sure that user is invited into your Transcend instance (https://app.transcend.io/admin/users). +- b) API keys: This CLI will not create new API keys. You will need to first create the new API keys on the Admin Dashboard (https://app.transcend.io/infrastructure/api-keys). You can then list out the titles of the API keys that you generated in your transcend.yml file, after which the CLI is capable of updating that API key to be able to respond to different data silos in your Data Map + +#### CI Integration + +Once you have a workflow for creating your transcend.yml file, you will want to integrate your `transcend inventory push` command on your CI. + +Below is an example of how to set this up using a Github action: + +```yaml +name: Transcend Data Map Syncing +# See https://app.transcend.io/privacy-requests/connected-services + +on: + push: + branches: + - 'main' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' + + - name: Install Transcend CLI + run: npm install --global @transcend-io/cli + + # If you have a script that generates your transcend.yml file from + # an ORM or infrastructure configuration, add that step here + # Leave this step commented out if you want to manage your transcend.yml manually + # - name: Generate transcend.yml + # run: ./scripts/generate_transcend_yml.py + + - name: Push Transcend config + run: transcend inventory push --auth=${{ secrets.TRANSCEND_API_KEY }} +``` + +#### Dynamic Variables + +If you are using this CLI to sync your Data Map between multiple Transcend instances, you may find the need to make minor modifications to your configurations between environments. The most notable difference would be the domain where your webhook URLs are hosted on. + +The `transcend inventory push` command takes in a parameter `variables`. This is a CSV of `key:value` pairs. + +This command could fill out multiple parameters in a YAML file like [./examples/multi-instance.yml](./examples/multi-instance.yml), copied below: + +```yml +api-keys: + - title: Webhook Key +enrichers: + - title: Basic Identity Enrichment + description: Enrich an email address to the userId and phone number + # The data silo webhook URL is the same in each environment, + # except for the base domain in the webhook URL. + url: https://example.<>/transcend-enrichment-webhook + input-identifier: email + output-identifiers: + - userId + - phone + - myUniqueIdentifier + - title: Fraud Check + description: Ensure the email address is not marked as fraudulent + url: https://example.<>/transcend-fraud-check + input-identifier: email + output-identifiers: + - email + privacy-actions: + - ERASURE +data-silos: + - title: Redshift Data Warehouse + integrationName: server + description: The mega-warehouse that contains a copy over all SQL backed databases - <> + url: https://example.<>/transcend-webhook + api-key-title: Webhook Key +``` + +### `transcend inventory scan-packages` + +```txt +USAGE + transcend inventory scan-packages (--auth value) [--scanPath value] [--ignoreDirs value]... [--repositoryName value] [--transcendUrl value] + transcend inventory scan-packages --help + +Transcend scans packages and dependencies for the following frameworks: + +- package.json +- requirements.txt & setup.py +- Podfile +- Package.resolved +- build.gradle +- pubspec.yaml +- Gemfile & .gemspec +- composer.json + +This 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: + +- repository name +- package names +- dependency names and versions +- package descriptions + +FLAGS + --auth The Transcend API key. Requires scopes: "Manage Code Scanning" + [--scanPath] File path in the project to scan [default = ./] + [--ignoreDirs]... List of directories to ignore in scan [separator = ,] + [--repositoryName] Name of the git repository that the package should be tied to + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Scan the current directory** + +```sh +transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" +``` + +**Scan a specific directory** + +```sh +transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --scanPath=./examples/ +``` + +**Ignore certain folders** + +```sh +transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --ignoreDirs=./test,./build +``` + +**Specify the name of the repository** + +```sh +transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --repositoryName=transcend-io/test +``` + +### `transcend inventory discover-silos` + +```txt +USAGE + transcend inventory discover-silos (--scanPath value) (--dataSiloId value) (--auth value) [--fileGlobs value] [--ignoreDirs value] [--transcendUrl value] + transcend inventory discover-silos --help + +We support scanning for new data silos in JavaScript, Python, Gradle, and CocoaPods projects. + +To 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. + +FLAGS + --scanPath File path in the project to scan + --dataSiloId The UUID of the corresponding data silo + --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. Requires scopes: "Manage Assigned Data Inventory" + [--fileGlobs] You can pass a glob syntax pattern(s) to specify additional file paths to scan. Comma-separated list of globs. [default = ""] + [--ignoreDirs] Comma-separated list of directories to ignore. [default = ""] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Scan a JavaScript package.json** + +```sh +transcend inventory discover-silos \ + --scanPath=./myJavascriptProject \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=445ee241-5f2a-477b-9948-2a3682a43d0e +``` + +**Scan multiple file types (Podfile, Gradle, etc.) in examples directory** + +```sh +transcend inventory discover-silos \ + --scanPath=./examples/ \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloId=b6776589-0b7d-466f-8aad-4378ffd3a321 +``` + +This call will look for all the package.json files in the scan path `./myJavascriptProject`, parse each of the dependencies into their individual package names, and send it to our Transcend backend for classification. These classifications can then be viewed [here](https://app.transcend.io/data-map/data-inventory/silo-discovery/triage). The process is the same for scanning requirements.txt, podfiles and build.gradle files. + +Here are some examples of a [Podfile](./examples/code-scanning/test-cocoa-pods/Podfile) and [Gradle file](./examples/code-scanning/test-gradle/build.gradle). + +### `transcend inventory pull-datapoints` + +```txt +USAGE + transcend inventory pull-datapoints (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--includeAttributes] [--includeGuessedCategories] [--parentCategories FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER] [--subCategories value]... + transcend inventory pull-datapoints --help + +Export the datapoints from your Data Inventory into a CSV. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Data Inventory" + [--file] The file to save datapoints to [default = ./datapoints.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] + [--includeAttributes] Whether to include attributes in the output [default = false] + [--includeGuessedCategories] Whether to include guessed categories in the output [default = false] + [--parentCategories] List of parent categories to filter by [FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER, separator = ,] + [--subCategories]... List of subcategories to filter by [separator = ,] + -h --help Print help information and exit +``` + +#### Examples + +**All arguments** + +```sh +transcend inventory pull-datapoints \ + --auth="$TRANSCEND_API_KEY" \ + --file=./datapoints.csv \ + --includeGuessedCategories \ + --parentCategories=CONTACT,ID,LOCATION \ + --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ + --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 +``` + +**Pull datapoints for specific data silos** + +```sh +transcend inventory pull-datapoints \ + --auth="$TRANSCEND_API_KEY" \ + --file=./datapoints.csv \ + --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 +``` + +**Include attributes in the output** + +```sh +transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeAttributes +``` + +**Include guessed categories in the output** + +```sh +transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeGuessedCategories +``` + +**Filter by parent categories** + +```sh +transcend inventory pull-datapoints \ + --auth="$TRANSCEND_API_KEY" \ + --file=./datapoints.csv \ + --parentCategories=ID,LOCATION +``` + +**Filter by subcategories** + +```sh +transcend inventory pull-datapoints \ + --auth="$TRANSCEND_API_KEY" \ + --file=./datapoints.csv \ + --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 +``` + +**Specify the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend inventory pull-datapoints \ + --auth="$TRANSCEND_API_KEY" \ + --file=./datapoints.csv \ + --transcendUrl=https://api.us.transcend.io +``` + +### `transcend inventory pull-unstructured-discovery-files` + +```txt +USAGE + transcend inventory pull-unstructured-discovery-files (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--subCategories value]... [--status MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED] [--includeEncryptedSnippets] + transcend inventory pull-unstructured-discovery-files --help + +This command allows for pulling Unstructured Discovery into a CSV. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Data Inventory" + [--file] The file to save datapoints to [default = ./unstructured-discovery-files.csv] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] + [--subCategories]... List of data categories to filter by [separator = ,] + [--status] List of classification statuses to filter by [MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED, separator = ,] + [--includeEncryptedSnippets] Whether to include encrypted snippets of the entries classified [default = false] + -h --help Print help information and exit +``` + +#### Examples + +**All arguments** + +```sh +transcend inventory pull-unstructured-discovery-files \ + --auth="$TRANSCEND_API_KEY" \ + --file=./unstructured-discovery-files.csv \ + --transcendUrl=https://api.us.transcend.io \ + --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 \ + --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ + --status=VALIDATED,MANUALLY_ADDED,CORRECTED \ + --includeEncryptedSnippets +``` + +**Specify the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend inventory pull-unstructured-discovery-files \ + --auth="$TRANSCEND_API_KEY" \ + --transcendUrl=https://api.us.transcend.io +``` + +**Pull entries for specific data silos** + +```sh +transcend inventory pull-unstructured-discovery-files \ + --auth="$TRANSCEND_API_KEY" \ + --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 +``` + +**Filter by data categories** + +```sh +transcend inventory pull-unstructured-discovery-files \ + --auth="$TRANSCEND_API_KEY" \ + --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 +``` + +**Filter by classification status (exclude unconfirmed recommendations)** + +```sh +transcend inventory pull-unstructured-discovery-files \ + --auth="$TRANSCEND_API_KEY" \ + --status=VALIDATED,MANUALLY_ADDED,CORRECTED +``` + +**Filter by classification status (include rejected recommendations)** + +```sh +transcend inventory pull-unstructured-discovery-files --auth="$TRANSCEND_API_KEY" --status=REJECTED +``` + +### `transcend inventory derive-data-silos-from-data-flows` + +```txt +USAGE + transcend inventory derive-data-silos-from-data-flows (--auth value) (--dataFlowsYmlFolder value) (--dataSilosYmlFolder value) [--ignoreYmls value]... [--transcendUrl value] + transcend inventory derive-data-silos-from-data-flows --help + +Given a folder of data flow transcend.yml configurations, convert those configurations to set of data silo transcend.yml configurations. + +FLAGS + --auth The Transcend API key. No scopes are required for this command. + --dataFlowsYmlFolder The folder that contains data flow yml files + --dataSilosYmlFolder The folder that contains data silo yml files + [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Convert data flow configurations in folder to data silo configurations in folder** + +```sh +transcend inventory derive-data-silos-from-data-flows \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --dataSilosYmlFolder=./working/data-silos/ +``` + +**Use with US backend** + +```sh +transcend inventory derive-data-silos-from-data-flows \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --dataSilosYmlFolder=./working/data-silos/ \ + --transcendUrl=https://api.us.transcend.io +``` + +**Skip a set of yml files** + +```sh +transcend inventory derive-data-silos-from-data-flows \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --dataSilosYmlFolder=./working/data-silos/ \ + --ignoreYmls=Skip.yml,Other.yml +``` + +### `transcend inventory derive-data-silos-from-data-flows-cross-instance` + +```txt +USAGE + transcend inventory derive-data-silos-from-data-flows-cross-instance (--auth value) (--dataFlowsYmlFolder value) [--output value] [--ignoreYmls value]... [--transcendUrl value] + transcend inventory derive-data-silos-from-data-flows-cross-instance --help + +Given a folder of data flow transcend.yml configurations, convert those configurations to a single transcend.yml configurations of all related data silos. + +FLAGS + --auth The Transcend API key. No scopes are required for this command. + --dataFlowsYmlFolder The folder that contains data flow yml files + [--output] The output transcend.yml file containing the data silo configurations [default = ./transcend.yml] + [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Convert data flow configurations in folder to data silo configurations in file** + +```sh +transcend inventory derive-data-silos-from-data-flows-cross-instance \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ +``` + +**Use with US backend** + +```sh +transcend inventory derive-data-silos-from-data-flows-cross-instance \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --transcendUrl=https://api.us.transcend.io +``` + +**Skip a set of yml files** + +```sh +transcend inventory derive-data-silos-from-data-flows-cross-instance \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --ignoreYmls=Skip.yml,Other.yml +``` + +**Convert data flow configurations in folder to data silo configurations in file** + +```sh +transcend inventory derive-data-silos-from-data-flows-cross-instance \ + --auth="$TRANSCEND_API_KEY" \ + --dataFlowsYmlFolder=./working/data-flows/ \ + --output=./output.yml +``` + +### `transcend inventory consent-manager-service-json-to-yml` + +```txt +USAGE + transcend inventory consent-manager-service-json-to-yml [--file value] [--output value] + transcend inventory consent-manager-service-json-to-yml --help + +Import the services from an airgap.js file into a Transcend instance. + +1. Run `await airgap.getMetadata()` on a site with airgap +2. Right click on the printed object, and click `Copy object` +3. Place output of file in a file named `services.json` +4. Run: + + transcend inventory consent-manager-service-json-to-yml --file=./services.json --output=./transcend.yml + +5. Run: + + transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./transcend.yml --classifyService + +FLAGS + [--file] Path to the services.json file, output of await airgap.getMetadata() [default = ./services.json] + [--output] Path to the output transcend.yml to write to [default = ./transcend.yml] + -h --help Print help information and exit +``` + +#### Examples + +**Convert data flow configurations in folder to yml in ./transcend.yml** + +```sh +transcend inventory consent-manager-service-json-to-yml +``` + +**With file locations** + +```sh +transcend inventory consent-manager-service-json-to-yml --file=./folder/services.json --output=./folder/transcend.yml +``` + +### `transcend inventory consent-managers-to-business-entities` + +```txt +USAGE + transcend inventory consent-managers-to-business-entities (--consentManagerYmlFolder value) [--output value] + transcend inventory consent-managers-to-business-entities --help + +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. + +FLAGS + --consentManagerYmlFolder Path to the folder of Consent Manager transcend.yml files to combine + [--output] Path to the output transcend.yml with business entity configuration [default = ./combined-business-entities.yml] + -h --help Print help information and exit +``` + +#### Examples + +**Combine files in folder to file ./combined-business-entities.yml** + +```sh +transcend inventory consent-managers-to-business-entities --consentManagerYmlFolder=./working/consent-managers/ +``` + +**Specify custom output file** + +```sh +transcend inventory consent-managers-to-business-entities \ + --consentManagerYmlFolder=./working/consent-managers/ \ + --output=./custom.yml +``` + +### `transcend admin generate-api-keys` + +```txt +USAGE + transcend admin generate-api-keys (--email value) (--password value) (--apiKeyTitle value) (--file value) (--scopes View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories) [--deleteExistingApiKey] [--createNewApiKey] [--parentOrganizationId value] [--transcendUrl value] + transcend admin generate-api-keys --help + +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. + +Unlike 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. + +Authentication 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". + +FLAGS + --email The email address that you use to log into Transcend + --password The password for your account login + --apiKeyTitle The title of the API key being generated or destroyed + --file The file where API keys should be written to + --scopes The list of scopes that should be given to the API key [View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories, separator = ,] + [--deleteExistingApiKey/--noDeleteExistingApiKey] When true, if an API key exists with the specified apiKeyTitle, the existing API key is deleted [default = true] + [--createNewApiKey/--noCreateNewApiKey] When true, new API keys will be created. Set to false if you simply want to delete all API keys with a title [default = true] + [--parentOrganizationId] Filter for only a specific organization by ID, returning all child accounts associated with that organization + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + -h --help Print help information and exit +``` + +#### Examples + +**Generate API keys for cross-instance usage** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./working/auth.json +``` + +**Specifying the backend URL, needed for US hosted backend infrastructure** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./working/auth.json \ + --transcendUrl=https://api.us.transcend.io +``` + +**Filter for only a specific organization by ID, returning all child accounts associated with that organization** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./working/auth.json \ + --parentOrganizationId=7098bb38-070d-4f26-8fa4-1b61b9cdef77 +``` + +**Delete all API keys with a certain title** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./working/auth.json \ + --createNewApiKey=false +``` + +**Throw error if an API key already exists with that title, default behavior is to delete the existing API key and create a new one with that same title** + +```sh +transcend admin generate-api-keys \ + --email=test@transcend.io \ + --password="$TRANSCEND_PASSWORD" \ + --scopes="View Email Templates,View Data Map" \ + --apiKeyTitle="CLI Usage Cross Instance Sync" \ + --file=./working/auth.json \ + --deleteExistingApiKey=false +``` + +**Find your organization ID** + +You can use the following GQL query on the [EU GraphQL Playground](https://api.us.transcend.io/graphql) or [US GraphQL Playground](https://api.us.transcend.io/graphql) to get your organization IDs and their parent/child relationships. + +```gql +query { + user { + organization { + id + parentOrganizationId + } + } +} +``` + +### `transcend admin chunk-csv` + +```txt +USAGE + transcend admin chunk-csv (--inputFile value) [--outputDir value] [--clearOutputDir value] [--chunkSizeMB value] + transcend admin chunk-csv --help + +Chunks a large CSV file into smaller CSV files of approximately N MB each. + +Notes: +- The script streams the input CSV and writes out chunks incrementally to avoid high memory usage. +- You may still need to increase Node's memory limit for very large inputs. +- It validates row length consistency against the header row and logs periodic progress/memory usage. + +FLAGS + --inputFile Absolute or relative path to the large CSV file to split + [--outputDir] Directory to write chunk files (defaults to the input file's directory) + [--clearOutputDir] Clear the output directory before writing chunks [default = true] + [--chunkSizeMB] Approximate chunk size in megabytes. Keep well under JS string size limits [default = 10] + -h --help Print help information and exit +``` + +#### Examples + +**Chunk a file into smaller CSV files** + +```sh +transcend admin chunk-csv --inputFile=./working/full_export.csv --outputDir=./working/chunks +``` + +**Specify chunk size in MB** + +```sh +transcend admin chunk-csv --inputFile=./working/full_export.csv --outputDir=./working/chunks --chunkSizeMB=250 +``` + +### `transcend migration sync-ot` + +```txt +USAGE + transcend migration sync-ot [--hostname value] [--oneTrustAuth value] [--source oneTrust|file] [--transcendAuth value] [--transcendUrl value] [--file value] [--resource assessments] [--dryRun] [--debug] + transcend migration sync-ot --help + +Pulls resources from a OneTrust and syncs them to a Transcend instance. For now, it only supports retrieving OneTrust Assessments. + +This command can be helpful if you are looking to: +- Pull resources from your OneTrust account. +- Migrate your resources from your OneTrust account to Transcend. + +OneTrust authentication requires an OAuth Token with scope for accessing the assessment endpoints. +If syncing the resources to Transcend, you will also need to generate an API key on the Transcend Admin Dashboard. + +FLAGS + [--hostname] The domain of the OneTrust environment from which to pull the resource + [--oneTrustAuth] The OAuth access token with the scopes necessary to access the OneTrust Public APIs + [--source] Whether to read the assessments from OneTrust or from a file [oneTrust|file, default = oneTrust] + [--transcendAuth] The Transcend API key. Requires scopes: "Manage Assessments" + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + [--file] Path to the file to pull the resource into. Must be a json file! + [--resource] The resource to pull from OneTrust. For now, only assessments is supported [assessments, default = assessments] + [--dryRun] Whether to export the resource to a file rather than sync to Transcend [default = false] + [--debug] Whether to print detailed logs in case of error [default = false] + -h --help Print help information and exit +``` + +#### Authentication + +In order to use this command, you will need to generate a OneTrust OAuth Token with scope for accessing the following endpoints: + +- [GET /v2/assessments](https://developer.onetrust.com/onetrust/reference/getallassessmentbasicdetailsusingget) +- [GET /v2/assessments/{assessmentId}/export](https://developer.onetrust.com/onetrust/reference/exportassessmentusingget) +- [GET /risks/{riskId}](https://developer.onetrust.com/onetrust/reference/getriskusingget) +- [GET /v2/Users/{userId}](https://developer.onetrust.com/onetrust/reference/getuserusingget) + +To learn how to generate the token, see the [OAuth 2.0 Scopes](https://developer.onetrust.com/onetrust/reference/oauth-20-scopes) and [Generate Access Token](https://developer.onetrust.com/onetrust/reference/getoauthtoken) pages. + +#### Examples + +**Syncs all assessments from the OneTrust instance to Transcend** + +```sh +transcend migration sync-ot \ + --hostname=trial.onetrust.com \ + --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ + --transcendAuth="$TRANSCEND_API_KEY" +``` + +**Set dryRun to true and sync the resource to disk (writes out file to ./oneTrustAssessments.json)** + +```sh +transcend migration sync-ot \ + --hostname=trial.onetrust.com \ + --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ + --dryRun \ + --file=./oneTrustAssessments.json +``` + +**Sync to Transcend by reading from file instead of OneTrust** + +```sh +transcend migration sync-ot --source=file --file=./oneTrustAssessments.json --transcendAuth="$TRANSCEND_API_KEY" +``` + + + +## Prompt Manager + +If you are integrating Transcend's Prompt Manager into your code, it may look like: + +```ts +import * as t from 'io-ts'; +import { TranscendPromptManager } from '@transcend-io/cli'; +import { + ChatCompletionMessage, + PromptRunProductArea, +} from '@transcend-io/privacy-types'; + +/** + * Example prompt integration + */ +export async function main(): Promise { + // Instantiate the Transcend Prompt Manager instance + const promptManager = new TranscendPromptManager({ + // API key + transcendApiKey: process.env.TRANSCEND_API_KEY, + // Define the prompts that are stored in Transcend + prompts: { + test: { + // identify by ID + id: '30bcaa79-889a-4af3-842d-2e8ba443d36d', + // no runtime variables + paramCodec: t.type({}), + // response is list of strings + outputCodec: t.array(t.string), + }, + json: { + // identify by title + title: 'test', + // one runtime variable "test" + paramCodec: t.type({ test: t.string }), + // runtime is json object + outputCodec: t.record(t.string, t.string), + // response is stored in atg + extractFromTag: 'json', + }, + predictProductLine: { + // identify by title + title: 'Predict Product Line', + // runtime parameter for slack channel name + paramCodec: t.type({ + slackChannelName: t.string, + }), + // response is specific JSON shape + outputCodec: t.type({ + product: t.union([t.string, t.null]), + clarification: t.union([t.string, t.null]), + }), + // response is stored in atg + extractFromTag: 'json', + }, + }, + // Optional arguments + // transcendUrl: 'https://api.us.transcend.io', // defaults to 'https://api.transcend.io' + // requireApproval: false, // defaults to true + // cacheDuration: 1000 * 60 * 60, // defaults to undefined, no cache + // defaultVariables: { myVariable: 'this is custom', other: [{ name: 'custom' }] }, // defaults to {} + // handlebarsOptions: { helpers, templates }, // defaults to {} + }); + + // Fetch the prompt from Transcend and template any variables + // in this case, we template the slack channel name in the LLM prompt + const systemPrompt = await promptManager.compilePrompt('predictProductLine', { + slackChannelName: channelName, + }); + + // Parameters to pass to the LLM + const input: ChatCompletionMessage[] = [ + { + role: 'system', + content: systemPrompt, + }, + { + role: 'user', + content: input, + }, + ]; + const largeLanguageModel = { + name: 'gpt-4', + client: 'openai' as const, + }; + const temperature = 1; + const topP = 1; + const maxTokensToSample = 1000; + + // Run prompt against LLM + let response: string; + const t0 = new Date().getTime(); + try { + response = await openai.createCompletion(input, { + temperature, + top_p: topP, + max_tokens: maxTokensToSample, + }); + } catch (err) { + // report error upon failure + await promptManager.reportPromptRunError('predictProductLine', { + promptRunMessages: input, + duration: new Date().getTime() - t0, + temperature, + topP, + error: err.message, + maxTokensToSample, + largeLanguageModel, + }); + } + const t1 = new Date().getTime(); + + // Parsed response as JSON and do not report to Transcend + // const parsedResponse = promptManager.parseAiResponse( + // 'predictProductLine', + // response, + // ); + + // Parsed response as JSON and report output to Transcend + const parsedResponse = await promptManager.reportAndParsePromptRun( + 'predictProductLine', + { + promptRunMessages: [ + ...input, + { + role: 'assistant', + content: response, + }, + ], + duration: t1 - t0, + temperature, + topP, + maxTokensToSample, + largeLanguageModel, + // Optional parameters + // name, // unique identifier for this run + // productArea, // Transcend product area that the prompt relates to + // runByEmployeeEmail, // Employee email that is executing the request + // promptGroupId, // The prompt group being reported + }, + ); +} +``` + +## Proxy usage + +If you are trying to use the CLI inside a corporate firewall and need to send traffic through a proxy, you can do so via the `http_proxy` environment variable,with a command like `http_proxy=http://localhost:5051 transcend inventory pull --auth=$TRANSCEND_API_KEY`. From e6274f22acc0c47bfb40a0bab61c1d0e6f5fa7e8 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sun, 17 Aug 2025 15:50:41 -0700 Subject: [PATCH 43/72] Delete src/lib/pooling/replayTail.ts --- src/lib/pooling/replayTail.ts | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/lib/pooling/replayTail.ts diff --git a/src/lib/pooling/replayTail.ts b/src/lib/pooling/replayTail.ts deleted file mode 100644 index 50da3d7b..00000000 --- a/src/lib/pooling/replayTail.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createReadStream, statSync } from 'node:fs'; - -/** - * Replay the tail of a file to stdout. - * - * @param path - The absolute path to the file to read. - * @param maxBytes - The maximum number of bytes to read from the end of the file. - * @param write - A function to write the output to stdout. - */ -export async function replayFileTailToStdout( - path: string, - maxBytes: number, - write: (s: string) => void, -): Promise { - await new Promise((resolve) => { - try { - const st = statSync(path); - const start = Math.max(0, st.size - maxBytes); - const stream = createReadStream(path, { start, encoding: 'utf8' }); - stream.on('data', (chunk) => write(chunk as string)); - stream.on('end', resolve); - stream.on('error', resolve); - } catch { - resolve(undefined); - } - }); -} From 9671e921f3ce549445b1c93f9130f41b89d8e295 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 17:01:33 -0700 Subject: [PATCH 44/72] TSC --- .../consent/upload-preferences/impl.ts | 10 +- .../upload-preferences/ui/keypressExtra.ts | 140 ------------------ .../getPreferencesForIdentifiers.ts | 1 - 3 files changed, 7 insertions(+), 144 deletions(-) delete mode 100644 src/commands/consent/upload-preferences/ui/keypressExtra.ts diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 9a4c0495..336dd7f8 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -38,12 +38,16 @@ import { AnyTotals, isUploadModeTotals, isCheckModeTotals, + makeOnKeypressExtra, } from './ui'; -import { writeFailingUpdatesCsv, ExportManager } from './artifacts'; +import { + writeFailingUpdatesCsv, + ExportManager, + type FailingUpdateRow, +} from './artifacts'; -import { applyReceiptSummary, FailingUpdateRow } from './receipts'; +import { applyReceiptSummary } from './receipts'; import { buildCommonOpts } from './buildTaskOptions'; -import { makeOnKeypressExtra } from './ui/keypressExtra'; function getCurrentModulePath(): string { if (typeof __filename !== 'undefined') return __filename as unknown as string; diff --git a/src/commands/consent/upload-preferences/ui/keypressExtra.ts b/src/commands/consent/upload-preferences/ui/keypressExtra.ts deleted file mode 100644 index 9f4c1a0c..00000000 --- a/src/commands/consent/upload-preferences/ui/keypressExtra.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { join } from 'node:path'; -import type { FailingUpdateRow } from '../receipts'; -import { - showCombinedLogs, - type ExportStatusMap, - type SlotPaths, -} from '../../../../lib/pooling'; -import { writeFailingUpdatesCsv, type ExportManager } from '../artifacts'; - -/** - * Handles keypress events for extra functionalities in the CLI. - * - * @param args - Configuration for the keypress handler. - * @returns A function that processes keypress events. - */ -export function makeOnKeypressExtra({ - slotLogPaths, - exportMgr, - failingUpdates, - exportStatus, - onRepaint, - onPause, -}: { - /** Rows that failed to update */ - failingUpdates: FailingUpdateRow[]; - /** Map of worker IDs to their log paths */ - slotLogPaths: SlotPaths; - /** Export manager for handling export operations */ - exportMgr: ExportManager; - /** Map of export statuses for different kinds of logs */ - exportStatus: ExportStatusMap; - /** Callback for repainting the UI */ - onRepaint: () => void; - /** Callback to pause the UI */ - onPause: (paused: boolean) => void; -}): (buf: Buffer) => void { - const noteExport = (slot: keyof ExportStatusMap, p: string): void => { - const now = Date.now(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const current: any = exportStatus[slot] || { path: p }; - // eslint-disable-next-line no-param-reassign - exportStatus[slot] = { - path: p || current.path, - savedAt: now, - exported: true, - }; - onRepaint(); - }; - - const view = ( - sources: Array<'out' | 'err' | 'structured' | 'warn' | 'info'>, - level: 'error' | 'warn' | 'all', - ): void => { - onPause(true); - showCombinedLogs(slotLogPaths, sources, level); - }; - - return (buf: Buffer): void => { - const s = buf.toString('utf8'); - - // viewers (lowercase) - if (s === 'e') { - view(['err'], 'error'); - return; - } - if (s === 'w') { - view(['warn', 'err'], 'warn'); - return; - } - if (s === 'i') { - view(['info'], 'all'); - return; - } - if (s === 'l') { - view(['out', 'err', 'structured'], 'all'); - return; - } - - // exports (uppercase) - if (s === 'E') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'error'); - process.stdout.write(`\nWrote combined error logs to: ${p}\n`); - noteExport('error', p); - } catch { - process.stdout.write('\nFailed to write combined error logs\n'); - } - return; - } - if (s === 'W') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'warn'); - process.stdout.write(`\nWrote combined warn logs to: ${p}\n`); - noteExport('warn', p); - } catch { - process.stdout.write('\nFailed to write combined warn logs\n'); - } - return; - } - if (s === 'I') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'info'); - process.stdout.write(`\nWrote combined info logs to: ${p}\n`); - noteExport('info', p); - } catch { - process.stdout.write('\nFailed to write combined info logs\n'); - } - return; - } - if (s === 'A') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); - process.stdout.write(`\nWrote combined ALL logs to: ${p}\n`); - noteExport('all', p); - } catch { - process.stdout.write('\nFailed to write combined ALL logs\n'); - } - return; - } - if (s === 'F') { - try { - const fPath = join(exportMgr.exportsDir, 'failing-updates.csv'); - writeFailingUpdatesCsv(failingUpdates, fPath); - process.stdout.write(`\nWrote failing updates CSV to: ${fPath}\n`); - noteExport('failuresCsv', fPath); - } catch (err) { - process.stdout.write( - `\nFailed to write failing updates CSV - ${err.stack}\n`, - ); - } - return; - } - - // back to dashboard - if (s === '\x1b' || s === '\x1d') { - onPause(false); - onRepaint(); - } - }; -} diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 29e85d25..58947679 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -1,7 +1,6 @@ import { PreferenceQueryResponseItem } from '@transcend-io/privacy-types'; import type { Got } from 'got'; import colors from 'colors'; -// import cliProgress from 'cli-progress'; import { chunk } from 'lodash-es'; import { decodeCodec } from '@transcend-io/type-utils'; import * as t from 'io-ts'; From 1ad75121d744b33676df0d3dd6c39812e402ec49 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 20:04:11 -0700 Subject: [PATCH 45/72] CSV split concurrency --- .vscode/settings.json | 1 + README.md | 31 +- src/commands/admin/chunk-csv/command.ts | 28 +- src/commands/admin/chunk-csv/impl.ts | 421 ++++++++++-------- src/commands/admin/chunk-csv/readme.ts | 4 +- .../admin/chunk-csv/ui/buildFrameModel.ts | 98 ++++ .../admin/chunk-csv/ui/renderDashboard.ts | 82 ++++ .../ui/tests/buildFrameModel.test.ts | 142 ++++++ .../ui/tests/renderDashboard.test.ts | 140 ++++++ src/commands/admin/chunk-csv/worker.ts | 146 ++++++ .../collectCsvFilesOrExit.ts | 75 ---- .../consent/upload-preferences/command.ts | 7 - .../consent/upload-preferences/impl.ts | 19 +- .../consent/upload-preferences/readme.ts | 6 +- .../{runChild.ts => worker.ts} | 0 src/lib/helpers/chunkOneCsvFile.ts | 251 +++++++++++ src/lib/helpers/collectCsvFilesOrExit.ts | 49 ++ src/lib/helpers/getCurrentModulePath.ts | 12 + src/lib/helpers/index.ts | 1 + src/lib/helpers/tests/chunkOneCsvFile.test.ts | 99 ++++ .../tests/collectCsvFilesOrExit.test.ts | 167 +++++++ src/lib/tests/codebase.test.ts | 1 + 22 files changed, 1473 insertions(+), 307 deletions(-) create mode 100644 src/commands/admin/chunk-csv/ui/buildFrameModel.ts create mode 100644 src/commands/admin/chunk-csv/ui/renderDashboard.ts create mode 100644 src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts create mode 100644 src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts create mode 100644 src/commands/admin/chunk-csv/worker.ts delete mode 100644 src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts rename src/commands/consent/upload-preferences/{runChild.ts => worker.ts} (100%) create mode 100644 src/lib/helpers/chunkOneCsvFile.ts create mode 100644 src/lib/helpers/collectCsvFilesOrExit.ts create mode 100644 src/lib/helpers/getCurrentModulePath.ts create mode 100644 src/lib/helpers/tests/chunkOneCsvFile.test.ts create mode 100644 src/lib/helpers/tests/collectCsvFilesOrExit.test.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index ceba0cbe..e48e3e8a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,6 +71,7 @@ "subdatapoint", "subdatapoints", "thlorenz", + "thresholding", "Timeseries", "tsbuildinfo", "undici", diff --git a/README.md b/README.md index 91c485ac..25afd3fb 100644 --- a/README.md +++ b/README.md @@ -2081,7 +2081,7 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] [--file value] [--directory value] [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] transcend consent upload-preferences --help Upload preference management data to your Preference Store. @@ -2103,8 +2103,7 @@ FLAGS --partition The partition key to download consent preferences to [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the CSV file to load preferences from - [--directory] Path to the directory of CSV files to load preferences from + --directory Path to the directory of CSV files to load preferences from [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] [--receiptFileDir] Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided. @@ -2137,7 +2136,7 @@ A sample CSV can be found [here](./examples/cli-upload-preferences-example.csv). ```sh transcend consent upload-preferences \ --auth="$TRANSCEND_API_KEY" \ - --file=./preferences.csv \ + --directory=./ \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 ``` @@ -2147,7 +2146,7 @@ transcend consent upload-preferences \ transcend consent upload-preferences \ --auth="$TRANSCEND_API_KEY" \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./preferences.csv \ + --directory=./csvs \ --dryRun \ --skipWorkflowTriggers \ --skipConflictUpdates \ @@ -2162,7 +2161,7 @@ transcend consent upload-preferences \ transcend consent upload-preferences \ --auth="$TRANSCEND_API_KEY" \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --file=./preferences.csv \ + --directory=./folder \ --transcendUrl=https://api.us.transcend.io ``` @@ -3114,21 +3113,19 @@ query { ```txt USAGE - transcend admin chunk-csv (--inputFile value) [--outputDir value] [--clearOutputDir value] [--chunkSizeMB value] + transcend admin chunk-csv (--directory value) [--outputDir value] [--clearOutputDir value] [--chunkSizeMB value] [--concurrency value] transcend admin chunk-csv --help -Chunks a large CSV file into smaller CSV files of approximately N MB each. - -Notes: -- The script streams the input CSV and writes out chunks incrementally to avoid high memory usage. -- You may still need to increase Node's memory limit for very large inputs. -- It validates row length consistency against the header row and logs periodic progress/memory usage. +Streams every CSV in --directory and writes chunked files of approximately N MB each. +- Runs files in parallel across worker processes (configurable via --concurrency). +- Validates row-length consistency against the header row; logs periodic progress and memory usage. FLAGS - --inputFile Absolute or relative path to the large CSV file to split - [--outputDir] Directory to write chunk files (defaults to the input file's directory) + --directory Directory containing CSV files to split (required) + [--outputDir] Directory to write chunk files (defaults to each input file's directory) [--clearOutputDir] Clear the output directory before writing chunks [default = true] [--chunkSizeMB] Approximate chunk size in megabytes. Keep well under JS string size limits [default = 10] + [--concurrency] Max number of worker processes (defaults based on CPU and file count) -h --help Print help information and exit ``` @@ -3137,13 +3134,13 @@ FLAGS **Chunk a file into smaller CSV files** ```sh -transcend admin chunk-csv --inputFile=./working/full_export.csv --outputDir=./working/chunks +transcend admin chunk-csv --directory=./working/files --outputDir=./working/chunks ``` **Specify chunk size in MB** ```sh -transcend admin chunk-csv --inputFile=./working/full_export.csv --outputDir=./working/chunks --chunkSizeMB=250 +transcend admin chunk-csv --directory=./working/files --outputDir=./working/chunks --chunkSizeMB=250 ``` ### `transcend migration sync-ot` diff --git a/src/commands/admin/chunk-csv/command.ts b/src/commands/admin/chunk-csv/command.ts index 5f4813a4..11a260b7 100644 --- a/src/commands/admin/chunk-csv/command.ts +++ b/src/commands/admin/chunk-csv/command.ts @@ -2,21 +2,21 @@ import { buildCommand } from '@stricli/core'; export const chunkCsvCommand = buildCommand({ loader: async () => { - const { chunkCsvImpl } = await import('./impl'); - return chunkCsvImpl; + const { chunkCsvParent } = await import('./impl'); + return chunkCsvParent; }, parameters: { flags: { - inputFile: { + directory: { kind: 'parsed', parse: String, - brief: 'Absolute or relative path to the large CSV file to split', + brief: 'Directory containing CSV files to split (required)', }, outputDir: { kind: 'parsed', parse: String, brief: - "Directory to write chunk files (defaults to the input file's directory)", + "Directory to write chunk files (defaults to each input file's directory)", optional: true, }, clearOutputDir: { @@ -38,15 +38,19 @@ export const chunkCsvCommand = buildCommand({ 'Approximate chunk size in megabytes. Keep well under JS string size limits', default: '10', }, + concurrency: { + kind: 'parsed', + parse: (v: string) => Math.max(1, Number(v) || 0), + brief: + 'Max number of worker processes (defaults based on CPU and file count)', + optional: true, + }, }, }, docs: { - brief: 'Chunk a large CSV into smaller CSV files', - fullDescription: `Chunks a large CSV file into smaller CSV files of approximately N MB each. - -Notes: -- The script streams the input CSV and writes out chunks incrementally to avoid high memory usage. -- You may still need to increase Node's memory limit for very large inputs. -- It validates row length consistency against the header row and logs periodic progress/memory usage.`, + brief: 'Chunk all CSVs in a directory into smaller CSV files', + fullDescription: `Streams every CSV in --directory and writes chunked files of approximately N MB each. +- Runs files in parallel across worker processes (configurable via --concurrency). +- Validates row-length consistency against the header row; logs periodic progress and memory usage.`, }, }); diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index 5255e35b..bfe72a61 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -1,211 +1,276 @@ -#!/usr/bin/env node - -import { Parser } from 'csv-parse'; -import { createReadStream, mkdirSync, unlinkSync } from 'node:fs'; -import { readdir } from 'node:fs/promises'; -import { basename, dirname, join } from 'node:path'; -import { pipeline } from 'node:stream/promises'; -import { Transform } from 'node:stream'; +import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; -import { appendCsvSync, writeCsvSync } from '../../../lib/cron'; -import type { LocalContext } from '../../../context'; +import type { ChildProcess } from 'node:child_process'; + +import { + computePoolSize, + getWorkerLogPaths, + isIpcOpen, + safeSend, + spawnWorkerProcess, + classifyLogLevel, + makeLineSplitter, + initLogDir, + CHILD_FLAG, +} from '../../../lib/pooling'; +import { getCurrentModulePath, RateCounter } from '../../../lib/helpers'; +import { renderDashboard } from './ui/renderDashboard'; +import { runChild } from './worker'; +import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; -export interface ChunkCsvCommandFlags { - inputFile: string; +/** + * Command-line flags supported by `chunkCsvParent`. + */ +export type ChunkCsvCommandFlags = { + /** Input directory containing CSV files to be chunked */ + directory: string; + /** Directory where chunked CSV files will be written (defaults to CWD if not set) */ outputDir?: string; + /** Whether to clear the output directory before writing new chunks */ clearOutputDir: boolean; + /** Target chunk size in MB (approximate) */ chunkSizeMB: number; -} + /** Optional concurrency override (defaults to pool-size heuristic) */ + concurrency?: number; +}; -/** - * Format memory usage for logging - * - * @param memoryData - The NodeJS memory usage data to format - * @returns A formatted string showing memory usage in MB - */ -function formatMemoryUsage(memoryData: NodeJS.MemoryUsage): string { - return Object.entries(memoryData) - .map(([key, value]) => `${key}: ${(value / 1024 / 1024).toFixed(2)} MB`) - .join(', '); -} +/** Per-worker progress (rows processed out of total rows, if known). */ +type WorkerProgress = { + processed: number; + total?: number; +}; + +/** Lightweight state tracked for each worker process. */ +type WorkerState = { + /** Whether the worker is currently busy with a task */ + busy: boolean; + /** The file currently being processed, or null if idle */ + file: string | null; + /** Last observed log severity from this worker */ + lastLevel: 'ok' | 'warn' | 'error'; + /** Progress stats, if reported */ + progress?: WorkerProgress; +}; + +/** Messages received from child processes */ +type ReadyMsg = { type: 'ready' }; +type ProgressMsg = { + type: 'progress'; + payload: { filePath: string; processed: number; total?: number }; +}; +type ResultMsg = { + type: 'result'; + payload: { ok: boolean; filePath: string; error?: string }; +}; +type ParentInbound = ReadyMsg | ProgressMsg | ResultMsg; /** - * Chunks a large CSV file into smaller files of approximately 1.5GB each - * Note that you may need to increase the node memory limit for this script to run!! - * - * Dev Usage: - * yarn ts-node ./src/cli-chunk-csv.ts --inputFile=./working/full_export.csv + * Parent entrypoint for the "chunk-csv" command. * - * Standard usage: - * yarn tr-chunk-csv --inputFile=/path/to/large.csv --outputDir=/path/to/output + * - Collects input CSV files. + * - Spawns a pool of worker processes to handle them concurrently. + * - Tracks per-worker state, progress, and errors. + * - Periodically re-renders a TTY dashboard until all work completes. * - * @param this - The local context (not used here, but required by the CLI framework) - * @param flags - The command line flags containing input file, output directory, and chunk size - * @returns A promise that resolves when the chunking is complete + * @param this - Execution context (includes logger, config, etc.) + * @param flags - Command-line flags controlling chunking behavior */ -export async function chunkCsvImpl( +export async function chunkCsvParent( this: LocalContext, flags: ChunkCsvCommandFlags, ): Promise { - const { inputFile, outputDir, chunkSizeMB, clearOutputDir } = flags; - - // Ensure inputFile is provided - if (!inputFile) { - logger.error( - colors.red( - 'An input file must be provided. You can specify using --inputFile=/path/to/large.csv', - ), - ); - process.exit(1); - } + const { directory, outputDir, clearOutputDir, chunkSizeMB, concurrency } = + flags; - const chunkSize = Math.floor((chunkSizeMB || chunkSizeMB) * 1024 * 1024); - - const baseFileName = basename(inputFile, '.csv'); - const outputDirectory = outputDir || dirname(inputFile); - mkdirSync(outputDirectory, { recursive: true }); - - // clear previous files - if (clearOutputDir) { - logger.info(colors.blue(`Clearing output directory: ${outputDirectory}`)); - try { - const files = await readdir(outputDirectory); - await Promise.all( - files - .filter((file) => file.startsWith(`${baseFileName}_chunk`)) - .map((file) => unlinkSync(join(outputDirectory, file))), - ); - logger.info(colors.green('Output directory cleared.')); - } catch (error) { - logger.error(colors.red('Error clearing output directory:'), error); - process.exit(1); - } - } + // Gather CSV files from the input directory or exit if none found + const files = collectCsvFilesOrExit(directory, this); - let currentChunkSize = 0; - let currentChunkNumber = 1; - let headerRow: string[] | null = null; - let currentOutputFile = join(outputDirectory, `${baseFileName}_chunk1.csv`); - let expectedColumnCount: number | null = null; - let totalLinesProcessed = 0; + // Determine pool size and CPU availability + const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); - const parser = new Parser({ - columns: false, - skip_empty_lines: true, - }); + logger.info( + colors.green( + `Chunking ${files.length} CSV file(s) with pool size ${poolSize} (CPU=${cpuCount})`, + ), + ); - const chunker = new Transform({ - objectMode: true, - /** - * Transform function that processes each chunk of CSV data - * - * @param chunk - Array of strings representing a CSV row - * @param _encoding - The encoding of the chunk - * @param callback - Callback function to signal completion - */ - transform(chunk: string[], _encoding, callback) { - if (!headerRow) { - headerRow = chunk; - expectedColumnCount = headerRow.length; - logger.info( - colors.blue( - `Found header row with ${expectedColumnCount} columns: ${headerRow.join( - ', ', - )}`, - ), - ); - callback(); - return; + // --- Shared counters and global state --- + const workers = new Map(); + const workerState = new Map(); + const slotLogs = new Map>(); + const pending = [...files]; + const totals = { completed: 0, failed: 0 }; + let activeWorkers = 0; + + // Track global throughput (rows/sec across all workers) + const meter = new RateCounter(); + + /** + * Refresh the dashboard view in the terminal. + * + * @param final - When true, finalizes the view (shows cursor again). + */ + const repaint = (final = false): void => { + renderDashboard({ + poolSize, + cpuCount, + filesTotal: files.length, + filesCompleted: totals.completed, + filesFailed: totals.failed, + workerState, + final, + throughput: { + successSoFar: 0, // success counting not wired here + r10s: meter.rate(10_000), + r60s: meter.rate(60_000), + }, + exportsDir: directory || outputDir || process.cwd(), + exportStatus: {}, // not used in this command + }); + }; + + /** + * Assign the next pending file to a given worker. + * + * @param id - Worker ID + */ + const assign = (id: number): void => { + if (pending.length === 0) return; + const filePath = pending.shift(); + if (!filePath) return; + + const w = workers.get(id)!; + workerState.set(id, { + busy: true, + file: filePath, + lastLevel: 'ok', + progress: { processed: 0, total: undefined }, + }); + + safeSend(w, { + type: 'task', + payload: { + filePath, + options: { outputDir, clearOutputDir, chunkSizeMB }, + }, + }); + + repaint(); + }; + + // --- Spawn worker pool --- + const logDir = initLogDir(directory || outputDir || process.cwd()); + const modulePath = getCurrentModulePath(); + + for (let i = 0; i < poolSize; i += 1) { + const child = spawnWorkerProcess({ + id: i, + modulePath, + logDir, + openLogWindows: false, + isSilent: true, + }); + + workers.set(i, child); + workerState.set(i, { + busy: false, + file: null, + lastLevel: 'ok', + progress: undefined, + }); + slotLogs.set(i, getWorkerLogPaths(child)); + activeWorkers += 1; + + // Classify stderr lines into warn/error for dashboard badges + const errLine = makeLineSplitter((line) => { + const lvl = classifyLogLevel(line); + if (!lvl) return; + const prev = workerState.get(i)!; + if (prev.lastLevel !== lvl) { + workerState.set(i, { ...prev, lastLevel: lvl }); + repaint(); } + }); + child.stderr?.on('data', errLine); - // Validate row structure - if ( - expectedColumnCount !== null && - chunk.length !== expectedColumnCount - ) { - logger.warn( - colors.yellow( - `Warning: Row ${totalLinesProcessed + 1} has ${ - chunk.length - } columns, expected ${expectedColumnCount}`, - ), - ); + // Handle structured messages from the child process + child.on('message', (msg: ParentInbound) => { + if (!msg || typeof msg !== 'object') return; + + if (msg.type === 'ready') { + assign(i); + repaint(); + return; } - totalLinesProcessed += 1; - if (totalLinesProcessed % 1_000_000 === 0) { - const memoryUsage = formatMemoryUsage(process.memoryUsage()); - logger.info( - colors.blue( - `Processed ${totalLinesProcessed.toLocaleString()} lines... ` + - `Memory usage: ${memoryUsage}`, - ), - ); + if (msg.type === 'progress') { + const { filePath, processed, total } = msg.payload || {}; + const prev = workerState.get(i)!; + workerState.set(i, { + ...prev, + file: prev.file ?? filePath ?? prev.file, + progress: { processed, total }, + }); + repaint(); + return; } - const rowSize = Buffer.byteLength(chunk.join(','), 'utf8'); - - // Prepare row object from header/values - const data = [ - { - ...Object.fromEntries( - headerRow.map((header, index) => [header, chunk[index]]), - ), - }, - ]; - - // If this is the first write of the current chunk, write with headers - if (currentChunkSize === 0) { - logger.info( - colors.yellow( - `Starting new chunk ${currentChunkNumber} at ${currentOutputFile}`, - ), - ); - writeCsvSync(currentOutputFile, data, headerRow); - currentChunkSize += rowSize; - } else { - appendCsvSync(currentOutputFile, data); - currentChunkSize += rowSize; + if (msg.type === 'result') { + const { ok } = msg.payload || {}; + if (ok) totals.completed += 1; + else totals.failed += 1; + + const prev = workerState.get(i)!; + workerState.set(i, { + ...prev, + busy: false, + file: null, + progress: undefined, + lastLevel: ok ? 'ok' : 'error', + }); + + if (pending.length > 0) assign(i); + else if (!pending.length) { + if (isIpcOpen(child)) safeSend(child, { type: 'shutdown' }); + } + repaint(); } + }); - // Determine if we need to start a new chunk - if (currentChunkSize >= chunkSize) { - currentChunkNumber += 1; - currentChunkSize = 0; - currentOutputFile = join( - outputDirectory, - `${baseFileName}_chunk${currentChunkNumber}.csv`, - ); + // Handle worker exit + // eslint-disable-next-line no-loop-func + child.on('exit', () => { + activeWorkers -= 1; + if (activeWorkers === 0) { + repaint(true); } + }); + } + + // Periodic repaint while work is ongoing + const tick = setInterval(() => repaint(false), 350); - callback(); - }, - /** - * Flush function that writes the final chunk of data - * - * @param callback - Callback function to signal completion - */ - flush(callback) { - callback(); - }, + // Resolve once all files are processed and all workers have exited + await new Promise((resolve) => { + const check = setInterval(() => { + if (pending.length === 0 && activeWorkers === 0) { + clearInterval(check); + clearInterval(tick); + repaint(true); + resolve(); + } + }, 300); }); +} - const readStream = createReadStream(inputFile); - - try { - logger.info( - colors.blue(`Starting to process ${inputFile}... for ${chunkSizeMB}MB`), - ); - await pipeline(readStream, parser, chunker); - logger.info( - colors.green( - `Successfully chunked ${inputFile} into ${currentChunkNumber} files ` + - `(${totalLinesProcessed.toLocaleString()} total lines processed)`, - ), - ); - } catch (error) { - logger.error(colors.red('Error chunking CSV file:'), error); +/* ------------------------------------------------------------------------------------------------- + * Child entrypoint: + * If process was invoked with CHILD_FLAG, run worker loop instead of parent. + * ------------------------------------------------------------------------------------------------- */ +if (process.argv.includes(CHILD_FLAG)) { + runChild().catch((err) => { + logger.error(err); process.exit(1); - } + }); } diff --git a/src/commands/admin/chunk-csv/readme.ts b/src/commands/admin/chunk-csv/readme.ts index f384eb45..ecf03680 100644 --- a/src/commands/admin/chunk-csv/readme.ts +++ b/src/commands/admin/chunk-csv/readme.ts @@ -7,14 +7,14 @@ const examples = buildExamples( { description: 'Chunk a file into smaller CSV files', flags: { - inputFile: './working/full_export.csv', + directory: './working/files', outputDir: './working/chunks', }, }, { description: 'Specify chunk size in MB', flags: { - inputFile: './working/full_export.csv', + directory: './working/files', outputDir: './working/chunks', chunkSizeMB: 250, }, diff --git a/src/commands/admin/chunk-csv/ui/buildFrameModel.ts b/src/commands/admin/chunk-csv/ui/buildFrameModel.ts new file mode 100644 index 00000000..b14fcd94 --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/buildFrameModel.ts @@ -0,0 +1,98 @@ +/** + * Input shape for rendering the dashboard and building the frame model. + * + * This represents a snapshot of the current state of the worker pool, + * file progress, and throughput statistics. + */ +export type RenderDashboardInput = { + /** Number of worker processes available in the pool. */ + poolSize: number; + /** Number of CPU cores detected on the host machine. */ + cpuCount: number; + /** Total number of files scheduled for processing. */ + filesTotal: number; + /** Number of files successfully completed so far. */ + filesCompleted: number; + /** Number of files that have failed processing. */ + filesFailed: number; + /** + * Current state of each worker in the pool. + * - Key: worker ID + * - Value: status for that worker + */ + workerState: Map< + number, + { + /** The file currently assigned to this worker, or null if idle. */ + file: string | null; + /** Whether the worker is actively processing a file. */ + busy: boolean; + /** The last log level seen for this worker (used for status coloring). */ + lastLevel: 'ok' | 'warn' | 'error'; + /** + * Progress details if the worker is processing a file. + * - `processed`: rows processed so far + * - `total`: optional total rows if known + */ + progress?: { processed: number; total?: number }; + } + >; + /** True if this is the final render after all processing is complete. */ + final: boolean; + /** + * Throughput metrics: + * - `successSoFar`: total rows successfully processed across all workers + * - `r10s`: rolling rows/sec average over the last 10s + * - `r60s`: rolling rows/sec average over the last 60s + */ + throughput: { successSoFar: number; r10s: number; r60s: number }; + /** Optional directory where export artifacts are being written. */ + exportsDir?: string; + /** Optional object containing export status details for the dashboard. */ + exportStatus?: unknown; +}; + +/** + * Normalized structure returned by `buildFrameModel`. + * This includes all original input fields plus a `workers` array + * derived from the `workerState` map. + */ +export type FrameModel = RenderDashboardInput & { + workers: Array<{ + /** Worker ID (key from workerState map). */ + id: number; + /** File currently being processed, or null if idle. */ + file: string | null; + /** Whether the worker is actively processing a file. */ + busy: boolean; + /** Last reported log level for this worker. */ + level: 'ok' | 'warn' | 'error'; + /** Number of rows processed so far. */ + processed: number; + /** Total rows expected, if known. */ + total?: number; + }>; +}; + +/** + * Build a normalized "frame model" for dashboard rendering. + * + * This transforms the raw `RenderDashboardInput` into a structure that includes + * a derived `workers` array for easy iteration in UI components. + * + * @param input - The current snapshot of dashboard state. + * @returns A `FrameModel` containing the normalized dashboard data. + */ +export function buildFrameModel(input: RenderDashboardInput): FrameModel { + // Flatten workerState map into an array of worker summaries. + const workers = [...input.workerState.entries()].map(([id, st]) => ({ + id, + file: st.file, + busy: st.busy, + level: st.lastLevel, + processed: st.progress?.processed ?? 0, + total: st.progress?.total, + })); + + return { ...input, workers }; +} diff --git a/src/commands/admin/chunk-csv/ui/renderDashboard.ts b/src/commands/admin/chunk-csv/ui/renderDashboard.ts new file mode 100644 index 00000000..09201905 --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/renderDashboard.ts @@ -0,0 +1,82 @@ +import * as readline from 'node:readline'; +import { buildFrameModel, type RenderDashboardInput } from './buildFrameModel'; + +let lastFrame = ''; + +/** + * Render a flicker-free, text-based dashboard in the terminal (TTY) + * for monitoring CSV chunking workers. + * + * This function is designed to continuously refresh the screen in place, + * showing: + * - Overall progress (completed/failed/total files) + * - Pool and CPU metrics + * - Recent throughput rates + * - Each worker’s state (busy/idle, warning/error state, progress, file path) + * + * It achieves flicker-free rendering by: + * 1. Caching the last rendered frame (`lastFrame`) and only redrawing if + * something has changed. + * 2. Using ANSI escape sequences + `readline` helpers to overwrite the + * existing terminal content rather than printing new lines. + * 3. Hiding the cursor while active updates are happening, then restoring it + * when rendering the final frame. + * + * @param input - The current state of the pool, worker map, throughput, etc. + * Typically produced on each heartbeat/interval by the main process. + */ +export function renderDashboard(input: RenderDashboardInput): void { + // Normalize the current state into a simple model shape. + const m = buildFrameModel(input); + + // Build the header line summarizing pool and throughput statistics. + const head = + `Chunk CSV — ${m.filesCompleted}/${m.filesTotal} done, failed ${m.filesFailed}\n` + + `Pool ${m.poolSize} • CPU ${m.cpuCount} • r10s ${m.throughput.r10s.toFixed( + 1, + )} r60s ${m.throughput.r60s.toFixed(1)}`; + + // Build per-worker status rows, including: + // - worker ID + // - BUSY/IDLE state + // - optional WARN/ERROR tag + // - processed row count + // - file currently assigned (if any) + const rows = m.workers + .map((w) => { + const status = w.busy ? 'BUSY' : 'IDLE'; + const lvl = + w.level === 'ok' ? '' : w.level === 'warn' ? ' [WARN]' : ' [ERROR]'; + const prog = w.processed + ? `rows=${w.processed.toLocaleString()}` + : 'rows=0'; + const file = w.file ? ` — ${w.file}` : ''; + return `w${w.id + .toString() + .padStart(2, '0')}: ${status}${lvl} ${prog}${file}`; + }) + .join('\n'); + + // Assemble the full frame string for this render cycle. + const frame = `${head}\n\n${rows}\n`; + + // Optimization: avoid redrawing if nothing has changed and we’re not final. + if (!input.final && frame === lastFrame) return; + lastFrame = frame; + + if (!input.final) { + // Live updates: + // - Hide the cursor for aesthetics + // - Reset cursor to top-left + // - Clear the screen before redrawing + process.stdout.write('\x1b[?25l'); + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + } else { + // Final frame: restore the cursor so the user can interact normally again. + process.stdout.write('\x1b[?25h'); + } + + // Write the frame (always with a trailing newline for separation). + process.stdout.write(`${frame}\n`); +} diff --git a/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts b/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts new file mode 100644 index 00000000..6fcbd7bd --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts @@ -0,0 +1,142 @@ +// buildFrameModel.test.ts +import { describe, it, expect } from 'vitest'; + +// Adjust the import path to match your repo layout: +// If this test lives in `src/commands/chunk-csv/ui/tests/`, then: +import { buildFrameModel, type RenderDashboardInput } from '../buildFrameModel'; + +function makeInput( + overrides: Partial = {}, +): RenderDashboardInput { + const workerState = new Map< + number, + { + file: string | null; + busy: boolean; + lastLevel: 'ok' | 'warn' | 'error'; + progress?: { processed: number; total?: number }; + } + >(); + + return { + poolSize: 3, + cpuCount: 8, + filesTotal: 10, + filesCompleted: 4, + filesFailed: 1, + workerState, + final: false, + throughput: { successSoFar: 1234, r10s: 12.3, r60s: 8.9 }, + exportsDir: '/tmp/exports', + exportStatus: { info: true }, + ...overrides, + }; +} + +describe('buildFrameModel', () => { + it('converts workerState map into a workers array with expected fields', () => { + const input = makeInput(); + input.workerState.set(0, { + file: '/data/a.csv', + busy: true, + lastLevel: 'ok', + progress: { processed: 100, total: 200 }, + }); + input.workerState.set(1, { + file: null, + busy: false, + lastLevel: 'warn', + // no progress + }); + + const model = buildFrameModel(input); + + // workers array length matches map size + expect(model.workers).toHaveLength(2); + + // preserves insertion order (Map is ordered) + expect(model.workers[0]).toMatchObject({ + id: 0, + file: '/data/a.csv', + busy: true, + level: 'ok', + processed: 100, + total: 200, + }); + + // defaults processed to 0 when no progress is present + expect(model.workers[1]).toMatchObject({ + id: 1, + file: null, + busy: false, + level: 'warn', + processed: 0, + total: undefined, + }); + }); + + it('passes through top-level fields unchanged', () => { + const input = makeInput({ + poolSize: 5, + cpuCount: 16, + filesTotal: 42, + filesCompleted: 21, + filesFailed: 2, + final: true, + throughput: { successSoFar: 999, r10s: 1.23, r60s: 0.45 }, + exportsDir: '/var/logs', + exportStatus: { done: true }, + }); + + const model = buildFrameModel(input); + + expect(model.poolSize).toBe(5); + expect(model.cpuCount).toBe(16); + expect(model.filesTotal).toBe(42); + expect(model.filesCompleted).toBe(21); + expect(model.filesFailed).toBe(2); + expect(model.final).toBe(true); + expect(model.throughput).toEqual({ + successSoFar: 999, + r10s: 1.23, + r60s: 0.45, + }); + expect(model.exportsDir).toBe('/var/logs'); + expect(model.exportStatus).toEqual({ done: true }); + }); + + it('does not mutate the input workerState', () => { + const input = makeInput(); + input.workerState.set(3, { + file: '/x.csv', + busy: true, + lastLevel: 'error', + progress: { processed: 7 }, + }); + + const before = JSON.stringify( + [...input.workerState.entries()].map(([id, st]) => [id, { ...st }]), + ); + + const model = buildFrameModel(input); + expect(model.workers[0]).toMatchObject({ + id: 3, + file: '/x.csv', + busy: true, + level: 'error', + processed: 7, + total: undefined, + }); + + const after = JSON.stringify( + [...input.workerState.entries()].map(([id, st]) => [id, { ...st }]), + ); + expect(after).toBe(before); // unchanged + }); + + it('handles empty workerState', () => { + const input = makeInput(); // workerState empty by default + const model = buildFrameModel(input); + expect(model.workers).toEqual([]); + }); +}); diff --git a/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts b/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts new file mode 100644 index 00000000..2a498745 --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts @@ -0,0 +1,140 @@ +// renderDashboard.test.ts +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Import SUT AFTER mocks +import { renderDashboard } from '../renderDashboard'; +import type { RenderDashboardInput } from '../buildFrameModel'; + +// Mock readline helpers used by renderDashboard +const H = vi.hoisted(() => ({ + cursorTo: vi.fn(), + clearScreenDown: vi.fn(), +})); + +vi.mock('node:readline', () => ({ + cursorTo: H.cursorTo, + clearScreenDown: H.clearScreenDown, +})); + +function makeInput( + overrides: Partial = {}, +): RenderDashboardInput { + const workerState = new Map< + number, + { + file: string | null; + busy: boolean; + lastLevel: 'ok' | 'warn' | 'error'; + progress?: { processed: number; total?: number }; + } + >(); + + // Two workers: one busy, one idle + workerState.set(0, { + file: '/data/a.csv', + busy: true, + lastLevel: 'ok', + progress: { processed: 123 }, + }); + workerState.set(1, { + file: null, + busy: false, + lastLevel: 'warn', + }); + + return { + poolSize: 2, + cpuCount: 8, + filesTotal: 5, + filesCompleted: 1, + filesFailed: 0, + workerState, + final: false, + throughput: { successSoFar: 123, r10s: 1.23, r60s: 4.56 }, + exportsDir: '/tmp/out', + exportStatus: undefined, + ...overrides, + }; +} + +let writeSpy: ReturnType; + +beforeEach(() => { + writeSpy = vi + .spyOn(process.stdout, 'write') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(() => true) as any; + vi.clearAllMocks(); +}); + +afterEach(() => { + writeSpy.mockRestore(); + vi.restoreAllMocks(); +}); + +describe('renderDashboard', () => { + it('renders an initial frame (hides cursor, clears screen, prints content)', () => { + const input = makeInput(); + + renderDashboard(input); + + // Should hide cursor and clear screen for live updates + expect(writeSpy).toHaveBeenCalledWith('\x1b[?25l'); + expect(H.cursorTo).toHaveBeenCalledWith(process.stdout, 0, 0); + expect(H.clearScreenDown).toHaveBeenCalledWith(process.stdout); + + // Should print a frame containing header and both workers + const calls = writeSpy.mock.calls.map((c) => String(c[0])); + const bigOut = calls.join(''); + + expect(bigOut).toContain('Chunk CSV — 1/5 done, failed 0'); + // r10s/r60s formatted to 1 decimal + expect(bigOut).toContain('r10s 1.2'); + expect(bigOut).toContain('r60s 4.6'); + + // Worker rows + // w00 BUSY on /data/a.csv with rows=123 + expect(bigOut).toMatch( + /w00: BUSY(?: \[WARN\])?\srows=123 — \/data\/a\.csv/, + ); + // w01 IDLE with warn flag and rows=0 + expect(bigOut).toContain('w01: IDLE [WARN] rows=0'); + }); + + it('skips redraw when frame is unchanged and final=false', () => { + const input = makeInput(); + + // First render — draws + renderDashboard(input); + + // Reset spies to measure only the second render + writeSpy.mockClear(); + H.cursorTo.mockClear(); + H.clearScreenDown.mockClear(); + + // Second render with the same input — should NO-OP + renderDashboard(input); + + expect(writeSpy).not.toHaveBeenCalled(); + expect(H.cursorTo).not.toHaveBeenCalled(); + expect(H.clearScreenDown).not.toHaveBeenCalled(); + }); + + it('final render shows cursor and still prints the frame (no clear)', () => { + const input = makeInput({ final: true }); + + renderDashboard(input); + + // Should show cursor on final frame + expect(writeSpy).toHaveBeenCalledWith('\x1b[?25h'); + + // Should NOT clear screen on final frame + expect(H.cursorTo).not.toHaveBeenCalled(); + expect(H.clearScreenDown).not.toHaveBeenCalled(); + + // Should print a frame + const calls = writeSpy.mock.calls.map((c) => String(c[0])); + const bigOut = calls.join(''); + expect(bigOut).toContain('Chunk CSV — 1/5 done, failed 0'); + }); +}); diff --git a/src/commands/admin/chunk-csv/worker.ts b/src/commands/admin/chunk-csv/worker.ts new file mode 100644 index 00000000..5331c0af --- /dev/null +++ b/src/commands/admin/chunk-csv/worker.ts @@ -0,0 +1,146 @@ +import { chunkOneCsvFile } from '../../../lib/helpers/chunkOneCsvFile'; +import { logger } from '../../../logger'; + +/** + * Parent → Worker task message. + * Instructs this worker to chunk a single CSV file according to the provided options. + */ +type TaskMsg = { + type: 'task'; + payload: { + /** Absolute or relative path to the CSV file this worker should process. */ + filePath: string; + options: { + /** + * Optional directory where chunk files are written. + * If omitted, chunks are written next to the input file. + */ + outputDir?: string; + /** + * When true, any existing files matching this input’s chunk filename pattern + * (e.g. `${base}_chunk_0001.csv`) are deleted before writing. + */ + clearOutputDir: boolean; + /** + * Target chunk size in megabytes. This is an approximate threshold used to + * roll to the next output file while streaming. + */ + chunkSizeMB: number; + }; + }; +}; + +/** Parent → Worker shutdown message. Signals the worker to exit cleanly. */ +type ShutdownMsg = { type: 'shutdown' }; + +/** Union of messages the worker can receive from the parent process. */ +type ParentMsg = TaskMsg | ShutdownMsg; + +/** + * Worker → Parent progress message. + * Emitted periodically (and finally) as rows are processed from the input file. + */ +type ProgressMsg = { + type: 'progress'; + payload: { + /** Path of the file currently being processed (echoed back for easy correlation). */ + filePath: string; + /** Number of data rows processed so far (header not counted). */ + processed: number; + /** Optional total row count if known (usually undefined for streaming). */ + total?: number; + }; +}; + +/** + * Worker → Parent result message. + * Emitted once per task upon success or failure. + */ +type ResultMsg = { + type: 'result'; + payload: { + /** Whether the task completed successfully. */ + ok: boolean; + /** Path of the file that was processed. */ + filePath: string; + /** Error string when ok === false. */ + error?: string; + }; +}; + +/** + * Worker entrypoint. + * + * Lifecycle: + * 1) Announce readiness to the parent via `{ type: 'ready' }`. + * 2) Wait for `{ type: 'task' }` messages; for each, call `chunkOneCsvFile(...)`. + * - While chunking, forward progress to the parent via `{ type: 'progress' }`. + * - On completion, send `{ type: 'result', ok: true }`. + * - On error, send `{ type: 'result', ok: false, error }` and exit(1). + * 3) On `{ type: 'shutdown' }`, exit(0) gracefully. + * + * Notes: + * - This process is typically spawned by a pool manager that assigns file paths to workers. + * - The long-lived promise at the end keeps the worker alive between tasks until the parent + * sends an explicit shutdown. + */ +export async function runChild(): Promise { + const workerId = Number(process.env.WORKER_ID || '0'); + logger.info(`[w${workerId}] ready pid=${process.pid}`); + + // Notify the parent that the worker is ready to receive tasks. + process.send?.({ type: 'ready' }); + + // Main message loop: receive tasks and shutdown requests from the parent. + process.on('message', async (msg: ParentMsg) => { + if (!msg || typeof msg !== 'object') return; + + // Graceful shutdown: let the parent control lifecycle. + if (msg.type === 'shutdown') { + process.exit(0); + } + + // Only handle task messages here. + if (msg.type !== 'task') return; + + const { filePath, options } = msg.payload; + const { outputDir, clearOutputDir, chunkSizeMB } = options; + + try { + // Stream the input CSV and write chunk files asynchronously. + await chunkOneCsvFile({ + filePath, + outputDir, + clearOutputDir, + chunkSizeMB, + // Propagate incremental progress to the parent. + onProgress: (processed, total) => + process.send?.({ + type: 'progress', + payload: { filePath, processed, total }, + } as ProgressMsg), + }); + + // Report success to the parent. + process.send?.({ + type: 'result', + payload: { ok: true, filePath }, + } as ResultMsg); + } catch (err) { + // Log locally and report failure upstream; exit the worker with error code. + const message = (err as Error)?.message ?? String(err); + logger.error(`[w${workerId}] ERROR ${filePath}: ${message}`); + process.send?.({ + type: 'result', + payload: { ok: false, filePath, error: String(err) }, + } as ResultMsg); + process.exit(1); + } + }); + + // keep alive + await new Promise(() => { + // This promise never resolves, keeping the worker alive indefinitely + // until the parent process instructs shutdown. + }); +} diff --git a/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts b/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts deleted file mode 100644 index 8cb3060e..00000000 --- a/src/commands/consent/upload-preferences/collectCsvFilesOrExit.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { logger } from '../../../logger'; -import { join } from 'path'; -import { readdirSync } from 'fs'; -import colors from 'colors'; -import type { LocalContext } from '../../../context'; - -/** - * Validate flags and collect CSV file paths from a directory or single file. - * On validation error, the provided `exit` function is called. - * - * @param directory - the directory containing CSV files, or undefined if a single file is provided - * @param file - the path to a single CSV file, or undefined if a directory is provided - * @param localContext - the context of the command, used for logging and exit - * @returns an array of valid CSV file paths - */ -export function collectCsvFilesOrExit( - directory: string | undefined, - file: string | undefined, - localContext: LocalContext, -): string[] { - const files: string[] = []; - - // Mutually exclusive inputs. - if (!!directory && !!file) { - logger.error( - colors.red( - 'Cannot provide both a directory and a file. Please provide only one.', - ), - ); - localContext.process.exit(1); - } - - // At least one must be present. - if (!file && !directory) { - logger.error( - colors.red( - 'A file or directory must be provided. Use --file=./preferences.csv or --directory=./preferences', - ), - ); - localContext.process.exit(1); - } - - if (directory) { - // Collect all CSVs under directory. - try { - const csvFiles = readdirSync(directory).filter((f) => f.endsWith('.csv')); - if (csvFiles.length === 0) { - logger.error( - colors.red(`No CSV files found in directory: ${directory}`), - ); - localContext.process.exit(1); - } - files.push(...csvFiles.map((f) => join(directory, f))); - } catch (err) { - logger.error(colors.red(`Failed to read directory: ${directory}`)); - logger.error(colors.red((err as Error).message)); - localContext.process.exit(1); - } - } else if (file) { - // Single-file mode; ensure it's CSV. - try { - if (!file.endsWith('.csv')) { - logger.error(colors.red('File must be a CSV file')); - localContext.process.exit(1); - } - files.push(file); - } catch (err) { - logger.error(colors.red(`Failed to access file: ${file}`)); - logger.error(colors.red((err as Error).message)); - localContext.process.exit(1); - } - } - - return files; -} diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 3499806d..b327f043 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -27,17 +27,10 @@ export const uploadPreferencesCommand = buildCommand({ }, sombraAuth: createSombraAuthParameter(), transcendUrl: createTranscendUrlParameter(), - file: { - kind: 'parsed', - parse: String, - brief: 'Path to the CSV file to load preferences from', - optional: true, - }, directory: { kind: 'parsed', parse: String, brief: 'Path to the directory of CSV files to load preferences from', - optional: true, }, dryRun: { kind: 'boolean', diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 336dd7f8..3896f649 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -6,11 +6,10 @@ import { join } from 'node:path'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; -import { collectCsvFilesOrExit } from './collectCsvFilesOrExit'; +import { runChild } from './worker'; import type { ChildProcess } from 'node:child_process'; -import { runChild } from './runChild'; import { computePoolSize, getWorkerLogPaths, @@ -31,8 +30,9 @@ import { WorkerMaps, WorkerState, installInteractiveSwitcher, + CHILD_FLAG, } from '../../../lib/pooling'; -import { RateCounter } from '../../../lib/helpers'; +import { getCurrentModulePath, RateCounter } from '../../../lib/helpers'; import { renderDashboard, AnyTotals, @@ -48,11 +48,7 @@ import { import { applyReceiptSummary } from './receipts'; import { buildCommonOpts } from './buildTaskOptions'; - -function getCurrentModulePath(): string { - if (typeof __filename !== 'undefined') return __filename as unknown as string; - return process.argv[1]; -} +import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; /** CLI flags */ export interface UploadPreferencesCommandFlags { @@ -60,8 +56,7 @@ export interface UploadPreferencesCommandFlags { partition: string; sombraAuth?: string; transcendUrl: string; - file?: string; - directory?: string; + directory: string; dryRun: boolean; skipExistingRecordCheck: boolean; receiptFileDir?: string; @@ -90,7 +85,6 @@ export async function uploadPreferences( ): Promise { const { partition, - file = '', directory, dryRun, skipExistingRecordCheck, @@ -100,7 +94,7 @@ export async function uploadPreferences( concurrency, } = flags; - const files = collectCsvFilesOrExit(directory, file, this); + const files = collectCsvFilesOrExit(directory, this); doneInputValidation(this.process.exit); logger.info( @@ -478,7 +472,6 @@ export async function uploadPreferences( /* ------------------------------------------------------------------------------------------------- * If invoked directly as a child process, enter worker loop * ------------------------------------------------------------------------------------------------- */ -const CHILD_FLAG = '--child-upload-preferences'; if (process.argv.includes(CHILD_FLAG)) { runChild().catch((err) => { logger.error(err); diff --git a/src/commands/consent/upload-preferences/readme.ts b/src/commands/consent/upload-preferences/readme.ts index 953bfdf5..409c1460 100644 --- a/src/commands/consent/upload-preferences/readme.ts +++ b/src/commands/consent/upload-preferences/readme.ts @@ -9,7 +9,7 @@ const examples = buildExamples( 'Upload consent preferences to partition key `4d1c5daa-90b7-4d18-aa40-f86a43d2c726`', flags: { auth: '$TRANSCEND_API_KEY', - file: './preferences.csv', + directory: './', partition: '4d1c5daa-90b7-4d18-aa40-f86a43d2c726', }, }, @@ -18,7 +18,7 @@ const examples = buildExamples( flags: { auth: '$TRANSCEND_API_KEY', partition: '4d1c5daa-90b7-4d18-aa40-f86a43d2c726', - file: './preferences.csv', + directory: './csvs', dryRun: true, skipWorkflowTriggers: true, skipConflictUpdates: true, @@ -33,7 +33,7 @@ const examples = buildExamples( flags: { auth: '$TRANSCEND_API_KEY', partition: '4d1c5daa-90b7-4d18-aa40-f86a43d2c726', - file: './preferences.csv', + directory: './folder', transcendUrl: 'https://api.us.transcend.io', }, }, diff --git a/src/commands/consent/upload-preferences/runChild.ts b/src/commands/consent/upload-preferences/worker.ts similarity index 100% rename from src/commands/consent/upload-preferences/runChild.ts rename to src/commands/consent/upload-preferences/worker.ts diff --git a/src/lib/helpers/chunkOneCsvFile.ts b/src/lib/helpers/chunkOneCsvFile.ts new file mode 100644 index 00000000..38a1fa40 --- /dev/null +++ b/src/lib/helpers/chunkOneCsvFile.ts @@ -0,0 +1,251 @@ +import { createReadStream, createWriteStream } from 'node:fs'; +import { mkdir, readdir, unlink } from 'node:fs/promises'; +import { pipeline } from 'node:stream/promises'; +import { Transform } from 'node:stream'; +import { once } from 'node:events'; +import { Parser } from 'csv-parse'; +import { basename, dirname, join } from 'node:path'; +import colors from 'colors'; +import * as fastcsv from 'fast-csv'; +import { logger } from '../../logger'; + +/** + * Options for chunking a single CSV file + */ +export type ChunkOpts = { + /** Path to the CSV file to chunk */ + filePath: string; + /** Output directory for chunk files; defaults to the same directory as the input file */ + outputDir?: string; + /** Clear output directory before starting */ + clearOutputDir: boolean; + /** Chunk size in MB */ + chunkSizeMB: number; + /** Callback for progress updates */ + onProgress: (processed: number, total?: number) => void; +}; + +/** + * Create a CSV writer (fast-csv formatter piped to a write stream) that writes + * a header line first, and then accepts object rows. Returns a tiny API to + * write rows with backpressure handling and to close the file cleanly. + * + * @param filePath - The path to the output CSV file + * @param headers - The headers for the CSV file + * @returns An object with `write` and `end` methods + */ +function createCsvChunkWriter( + filePath: string, + headers: string[], +): { + /** Write a row object to the CSV file */ + write: (row: Record) => Promise; + /** Close the CSV file, ensuring all data is flushed */ + end: () => Promise; +} { + const ws = createWriteStream(filePath); + const csv = fastcsv.format({ headers, writeHeaders: true, objectMode: true }); + // Pipe csv → file stream + csv.pipe(ws); + + return { + /** + * Write a row object to the CSV file. + * + * @param row - The row data as an object + */ + async write(row) { + // Respect backpressure from fast-csv formatter + const ok = csv.write(row); + if (!ok) { + await once(csv, 'drain'); + } + }, + /** + * Close the CSV file, ensuring all data is flushed. + */ + async end() { + // End formatter; wait for underlying file stream to finish flush/close + const finished = Promise.all([once(ws, 'finish')]); + csv.end(); + await finished; + }, + }; +} + +/** + * Zero-pad chunk numbers to four digits (e.g., 1 → "0001"). + * + * @param n - The chunk number to pad + * @returns The padded chunk number as a string + */ +function pad4(n: number): string { + return String(n).padStart(4, '0'); +} + +/** + * Approximate row size in bytes using comma-joined field values. + * + * @param obj - The row object to estimate size for + * @returns Approximate byte size of the row when serialized as CSV + */ +function approxRowBytes(obj: Record): number { + // naive but fast; adequate for chunk rollover thresholding + return Buffer.byteLength( + Object.values(obj) + .map((v) => (v == null ? '' : String(v))) + .join(','), + 'utf8', + ); +} + +/** + * Stream a single CSV file and write chunk files of roughly chunkSizeMB. + * - Writes header to each chunk. + * - Logs periodic progress via onProgress. + * + * @param opts - Options for chunking the file + * @returns Promise that resolves when done + */ +export async function chunkOneCsvFile(opts: ChunkOpts): Promise { + const { filePath, outputDir, clearOutputDir, chunkSizeMB, onProgress } = opts; + + const chunkSizeBytes = Math.floor(chunkSizeMB * 1024 * 1024); + const baseName = basename(filePath, '.csv'); + const outDir = outputDir || dirname(filePath); + await mkdir(outDir, { recursive: true }); + + // Clear previous chunk files for this base + if (clearOutputDir) { + const files = await readdir(outDir); + await Promise.all( + files + .filter((f) => f.startsWith(`${baseName}_chunk_`) && f.endsWith('.csv')) + .map((f) => unlink(join(outDir, f))), + ); + } + + let headerRow: string[] | null = null; + let expectedCols: number | null = null; + let totalLines = 0; + let currentChunk = 1; + let currentSize = 0; + + const parser = new Parser({ + columns: false, + skip_empty_lines: true, + }); + + // Current active chunk writer; created after we know headers + let writer: { + /** Write a row object to the current chunk file */ + write: (row: Record) => Promise; + /** Close the current chunk file */ + end: () => Promise; + } | null = null; + + // Returns current chunk file path — chunk number is always 4-digit padded + const currentChunkPath = (): string => + join(outDir, `${baseName}_chunk_${pad4(currentChunk)}.csv`); + + const t = new Transform({ + objectMode: true, + /** + * Transform each row of the CSV file into a chunk. + * + * @param row - The current row being processed + * @param _enc - Encoding (not used) + * @param cb - Callback to signal completion or error + */ + async transform(row: string[], _enc, cb) { + try { + // First row is the header + if (!headerRow) { + headerRow = row.slice(0); + expectedCols = headerRow.length; + + // Open first chunk with header asynchronously + writer = createCsvChunkWriter(currentChunkPath(), headerRow); + cb(); + return; + } + + // sanity check rows (non-fatal) + if (expectedCols !== null && row.length !== expectedCols) { + // optionally log a warning or collect metrics + logger.warn( + colors.yellow( + `Row has ${row.length} cols; expected ${expectedCols}`, + ), + ); + } + + totalLines += 1; + if (totalLines % 250_000 === 0) { + onProgress(totalLines); + } + + // Build row object using the original header + const obj = Object.fromEntries(headerRow!.map((h, i) => [h, row[i]])); + + // Determine the row size up-front + const rowBytes = approxRowBytes(obj); + + // If adding this row would exceed the threshold, roll first, + // so this row becomes the first row in the next chunk. + if ( + writer && + currentSize > 0 && + currentSize + rowBytes > chunkSizeBytes + ) { + await writer.end(); + currentChunk += 1; + currentSize = 0; + writer = createCsvChunkWriter(currentChunkPath(), headerRow!); + } + + // Ensure writer exists (should after header) + if (!writer) { + writer = createCsvChunkWriter(currentChunkPath(), headerRow!); + } + + // Write row and update approximate size + await writer.write(obj); + currentSize += rowBytes; + + cb(); + } catch (e) { + cb(e as Error); + } + }, + + // Ensure final file is closed + /** + * Flush is called when the readable has ended; we close any open writer. + * + * @param cb - Callback to signal completion or error + */ + async flush(cb) { + try { + if (writer) { + await writer.end(); + writer = null; + } + cb(); + } catch (e) { + cb(e as Error); + } + }, + }); + + const rs = createReadStream(filePath); + await pipeline(rs, parser, t); + + // Final progress tick + onProgress(totalLines); + logger.info( + colors.green( + `Chunked ${filePath} into ${currentChunk} file(s); processed ${totalLines.toLocaleString()} rows.`, + ), + ); +} diff --git a/src/lib/helpers/collectCsvFilesOrExit.ts b/src/lib/helpers/collectCsvFilesOrExit.ts new file mode 100644 index 00000000..e956dfab --- /dev/null +++ b/src/lib/helpers/collectCsvFilesOrExit.ts @@ -0,0 +1,49 @@ +import { join } from 'node:path'; +import { readdirSync, statSync } from 'node:fs'; +import colors from 'colors'; +import { logger } from '../../logger'; +import type { LocalContext } from '../../context'; + +/** + * Validate flags and collect CSV file paths from a directory. + * On validation error, the provided `exit` function is called. + * + * @param directory - the directory containing CSV files + * @param localContext - the context of the command, used for logging and exit + * @returns an array of valid CSV file paths + */ +export function collectCsvFilesOrExit( + directory: string | undefined, + localContext: LocalContext, +): string[] { + if (!directory) { + logger.error(colors.red('A --directory must be provided.')); + localContext.process.exit(1); + } + + let files: string[] = []; + try { + const entries = readdirSync(directory); + files = entries + .filter((f) => f.endsWith('.csv')) + .map((f) => join(directory, f)) + .filter((p) => { + try { + return statSync(p).isFile(); + } catch { + return false; + } + }); + } catch (err) { + logger.error(colors.red(`Failed to read directory: ${directory}`)); + logger.error(colors.red((err as Error).message)); + localContext.process.exit(1); + } + + if (files.length === 0) { + logger.error(colors.red(`No CSV files found in directory: ${directory}`)); + localContext.process.exit(1); + } + + return files; +} diff --git a/src/lib/helpers/getCurrentModulePath.ts b/src/lib/helpers/getCurrentModulePath.ts new file mode 100644 index 00000000..d3e2c769 --- /dev/null +++ b/src/lib/helpers/getCurrentModulePath.ts @@ -0,0 +1,12 @@ +/** + * Returns the current module's path so the worker pool knows what file to re-exec. + * In Node ESM, __filename is undefined, so we fall back to argv[1]. + * + * @returns The current module's path as a string + */ +export function getCurrentModulePath(): string { + if (typeof __filename !== 'undefined') { + return __filename as unknown as string; + } + return process.argv[1]; +} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index acce1e3e..8ae1d5eb 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -10,3 +10,4 @@ export * from './retrySamePromise'; export * from './limitRecords'; export * from './RateCounter'; export * from './readSafe'; +export * from './getCurrentModulePath'; diff --git a/src/lib/helpers/tests/chunkOneCsvFile.test.ts b/src/lib/helpers/tests/chunkOneCsvFile.test.ts new file mode 100644 index 00000000..9905e031 --- /dev/null +++ b/src/lib/helpers/tests/chunkOneCsvFile.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { tmpdir } from 'node:os'; +import { join as pathJoin } from 'node:path'; +import { mkdtemp, writeFile, readdir, readFile, rm } from 'node:fs/promises'; + +// SUT +import { chunkOneCsvFile } from '../chunkOneCsvFile'; + +// 🪝 Hoist shared logger spies to match your existing pattern +const H = vi.hoisted(() => ({ + loggerSpies: { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + }, +})); + +// Mock the SAME module id the SUT imports. +// From tests → ../../logger (adjust if your relative path differs) +vi.mock('../../../logger', () => ({ + logger: H.loggerSpies, +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('chunkOneCsvFile (async, streaming)', () => { + it('splits into multiple padded chunk files and preserves headers/rows', async () => { + // Create a temp work dir + const work = await mkdtemp(pathJoin(tmpdir(), 'chunk-csv-')); + + // Prepare a small CSV input that will produce 2 chunks: + // Header + three data rows; each row ~32 bytes, threshold ~70 bytes → first 2 rows in chunk 1, last row in chunk 2 + const inputCsv = [ + 'colA,colB,colC', + 'xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx', + 'xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx', + 'xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx', + ].join('\n'); + + const inPath = pathJoin(work, 'sample.csv'); + await writeFile(inPath, inputCsv, 'utf8'); + + // Track progress calls + const onProgress = vi.fn(); + + // ~70 bytes threshold: 0.00007 MB * 1,048,576 ≈ 73.4 bytes + await chunkOneCsvFile({ + filePath: inPath, + outputDir: work, + clearOutputDir: true, + chunkSizeMB: 0.00007, + onProgress, + }); + + // Expect two chunk files with 4-digit padding + const files = (await readdir(work)).sort(); + const chunk1 = pathJoin(work, 'sample_chunk_0001.csv'); + const chunk2 = pathJoin(work, 'sample_chunk_0002.csv'); + + expect(files).toContain('sample_chunk_0001.csv'); + expect(files).toContain('sample_chunk_0002.csv'); + + // Read back and verify headers + row counts + const c1 = await readFile(chunk1, 'utf8'); + const c2 = await readFile(chunk2, 'utf8'); + + // Each chunk should include header + const c1Lines = c1.trim().split('\n'); + const c2Lines = c2.trim().split('\n'); + + // Header must match original + expect(c1Lines[0]).toBe('colA,colB,colC'); + expect(c2Lines[0]).toBe('colA,colB,colC'); + + // With our threshold, expect 2 data rows in chunk 1, 1 data row in chunk 2 + expect(c1Lines.length).toBe(1 /* header */ + 2 /* rows */); + expect(c2Lines.length).toBe(1 /* header */ + 1 /* row */); + + // Progress callback should be called at least once with the final total (3) + // We don't rely on the 250k periodic tick; final tick is guaranteed. + expect(onProgress).toHaveBeenCalled(); + const last = onProgress.mock.calls.at(-1); + expect(last?.[0]).toBe(3); + + // Optional: ensure logger.info was called with a "Chunked ..." line + expect(H.loggerSpies.info).toHaveBeenCalledWith( + expect.stringContaining('Chunked '), + ); + + // Cleanup + await rm(work, { recursive: true, force: true }); + }); +}); diff --git a/src/lib/helpers/tests/collectCsvFilesOrExit.test.ts b/src/lib/helpers/tests/collectCsvFilesOrExit.test.ts new file mode 100644 index 00000000..20d2cb9e --- /dev/null +++ b/src/lib/helpers/tests/collectCsvFilesOrExit.test.ts @@ -0,0 +1,167 @@ +// collectCsvFilesOrExit.test.ts +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { join as pathJoin } from 'node:path'; + +// Now import mocked symbols + SUT +import { readdirSync, statSync } from 'node:fs'; + +import { collectCsvFilesOrExit } from '../collectCsvFilesOrExit'; +import type { LocalContext } from '../../../context'; + +/** 🔧 Hoist shared spies so the mock factory can safely capture them */ +const H = vi.hoisted(() => ({ + loggerSpies: { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + }, +})); + +/** Mock fs BEFORE importing the SUT */ +vi.mock('node:fs', () => ({ + readdirSync: vi.fn(), + statSync: vi.fn(), +})); + +/** + * Mock the SAME module id the SUT imports. + * From src/lib/helpers/tests/* to src/logger = ../../../logger + */ +vi.mock('../../../logger', () => ({ + logger: H.loggerSpies, +})); +const mReadDir = vi.mocked(readdirSync); +const mStat = vi.mocked(statSync); + +/** + * Minimal LocalContext stub that throws on exit + * + * @returns A mock context with an exit function + */ +function makeCtx(): LocalContext { + return { + process: { + exit: vi.fn((code?: number) => { + const err = new Error(`EXIT:${code ?? ''}`); + err.name = 'ExitSignal'; + throw err; + }), + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any; +} + +beforeEach(() => { + vi.clearAllMocks(); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('collectCsvFilesOrExit', () => { + it('exits when directory is undefined', () => { + const ctx = makeCtx(); + + expect(() => collectCsvFilesOrExit(undefined, ctx)).toThrowError(/EXIT:1/); + + expect(H.loggerSpies.error).toHaveBeenCalledWith( + expect.stringContaining('--directory must be provided'), + ); + expect(ctx.process.exit).toHaveBeenCalledWith(1); + }); + + it('exits when readdirSync throws (cannot read directory)', () => { + const ctx = makeCtx(); + + mReadDir.mockImplementation(() => { + throw new Error('boom'); + }); + + expect(() => collectCsvFilesOrExit('/data/in', ctx)).toThrowError(/EXIT:1/); + + expect(H.loggerSpies.error).toHaveBeenNthCalledWith( + 1, + expect.stringContaining('Failed to read directory: /data/in'), + ); + expect(H.loggerSpies.error).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('boom'), + ); + expect(ctx.process.exit).toHaveBeenCalledWith(1); + }); + + it('exits when no CSV files are found', () => { + const ctx = makeCtx(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mReadDir.mockReturnValue(['notes.txt', 'image.png'] as any); + mStat.mockReturnValue({ isFile: () => true } as unknown as ReturnType< + typeof statSync + >); + + expect(() => collectCsvFilesOrExit('/dir', ctx)).toThrowError(/EXIT:1/); + + expect(H.loggerSpies.error).toHaveBeenCalledWith( + expect.stringContaining('No CSV files found in directory: /dir'), + ); + expect(ctx.process.exit).toHaveBeenCalledWith(1); + }); + + it('returns only CSV files that are real files', () => { + const ctx = makeCtx(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mReadDir.mockReturnValue(['a.csv', 'b.txt', 'c.csv'] as any); + mStat.mockImplementation((p) => { + const isFile = + p === pathJoin('/data', 'a.csv') || p === pathJoin('/data', 'c.csv'); + return { isFile: () => isFile } as unknown as ReturnType; + }); + + const out = collectCsvFilesOrExit('/data', ctx); + + expect(out).toEqual([ + pathJoin('/data', 'a.csv'), + pathJoin('/data', 'c.csv'), + ]); + expect(ctx.process.exit).not.toHaveBeenCalled(); + expect(H.loggerSpies.error).not.toHaveBeenCalled(); + }); + + it('filters out CSV entries whose statSync throws (e.g., broken symlink)', () => { + const ctx = makeCtx(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mReadDir.mockReturnValue(['good.csv', 'bad.csv', 'skip.txt'] as any); + mStat.mockImplementation((p) => { + if (p === pathJoin('/x', 'bad.csv')) throw new Error('ENOENT'); + return { isFile: () => true } as unknown as ReturnType; + }); + + const out = collectCsvFilesOrExit('/x', ctx); + + expect(out).toEqual([pathJoin('/x', 'good.csv')]); + expect(ctx.process.exit).not.toHaveBeenCalled(); + }); + + it('ignores CSVs that are directories (isFile() === false)', () => { + const ctx = makeCtx(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mReadDir.mockReturnValue(['dirlike.csv', 'real.csv'] as any); + mStat.mockImplementation((p) => { + if (p === pathJoin('/root', 'dirlike.csv')) { + return { isFile: () => false } as unknown as ReturnType< + typeof statSync + >; + } + return { isFile: () => true } as unknown as ReturnType; + }); + + const out = collectCsvFilesOrExit('/root', ctx); + + expect(out).toEqual([pathJoin('/root', 'real.csv')]); + expect(ctx.process.exit).not.toHaveBeenCalled(); + }); +}); diff --git a/src/lib/tests/codebase.test.ts b/src/lib/tests/codebase.test.ts index 46eade63..d00df2a2 100644 --- a/src/lib/tests/codebase.test.ts +++ b/src/lib/tests/codebase.test.ts @@ -242,6 +242,7 @@ describe('CLI Command Structure', () => { 'helpers.ts', 'types.ts', 'constants.ts', + 'worker.ts', ]; // Allowed subdirectories in leaf command dirs From 5bf5ac5259284823e6645e8069695f0cad7cb302 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 17 Aug 2025 20:42:34 -0700 Subject: [PATCH 46/72] checkpoint on csv parse --- src/commands/admin/chunk-csv/impl.ts | 213 +++++++++++++----- .../admin/chunk-csv/ui/renderDashboard.ts | 84 ++++--- src/lib/helpers/chunkOneCsvFile.ts | 10 + 3 files changed, 215 insertions(+), 92 deletions(-) diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index bfe72a61..883499a2 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -13,6 +13,7 @@ import { makeLineSplitter, initLogDir, CHILD_FLAG, + installInteractiveSwitcher, } from '../../../lib/pooling'; import { getCurrentModulePath, RateCounter } from '../../../lib/helpers'; import { renderDashboard } from './ui/renderDashboard'; @@ -20,40 +21,44 @@ import { runChild } from './worker'; import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; /** - * Command-line flags supported by `chunkCsvParent`. + * CLI flags accepted by the chunk-csv command. + * + * Most flags are passed down to workers as part of each task payload. */ export type ChunkCsvCommandFlags = { - /** Input directory containing CSV files to be chunked */ + /** Directory containing input CSVs to chunk (required). */ directory: string; - /** Directory where chunked CSV files will be written (defaults to CWD if not set) */ + /** Optional output directory for chunked files (defaults near the input). */ outputDir?: string; - /** Whether to clear the output directory before writing new chunks */ + /** Remove any previous chunk files for a CSV before writing new ones. */ clearOutputDir: boolean; - /** Target chunk size in MB (approximate) */ + /** Approximate chunk size threshold in MB (e.g., 10). */ chunkSizeMB: number; - /** Optional concurrency override (defaults to pool-size heuristic) */ + /** Optional override for parallelism; if omitted, use a CPU-based heuristic. */ concurrency?: number; }; -/** Per-worker progress (rows processed out of total rows, if known). */ +/** Per-worker progress snapshot. */ type WorkerProgress = { + /** Rows processed so far for the current file. */ processed: number; + /** Total rows (if known). Not all workers will report a total. */ total?: number; }; -/** Lightweight state tracked for each worker process. */ +/** Minimal state we track for each live worker. */ type WorkerState = { - /** Whether the worker is currently busy with a task */ + /** Whether the worker is actively processing a file (vs. idle/waiting). */ busy: boolean; - /** The file currently being processed, or null if idle */ + /** Which file the worker is assigned (null when idle). */ file: string | null; - /** Last observed log severity from this worker */ + /** Last “level” seen in its stderr (ok/warn/error) for dashboard badges. */ lastLevel: 'ok' | 'warn' | 'error'; - /** Progress stats, if reported */ + /** Streaming progress info (rows processed / total if available). */ progress?: WorkerProgress; }; -/** Messages received from child processes */ +/** Child → Parent messages we handle. */ type ReadyMsg = { type: 'ready' }; type ProgressMsg = { type: 'progress'; @@ -65,16 +70,50 @@ type ResultMsg = { }; type ParentInbound = ReadyMsg | ProgressMsg | ResultMsg; +/* ================================================================================================ + * Small utilities + * ============================================================================================== */ + +/** + * Coalesce multiple fast “repaint” calls into a single render in the same tick. + * This prevents “double draw at startup” and other quick successions of redraws. + * + * @param fn - Function to call with final=true when the repaint is flushed. + * @returns A function that can be called with final=false to queue a repaint. + */ +function createRepaintScheduler( + fn: (final?: boolean) => void, +): (final?: boolean) => void { + // Use a closure to track the state of the repaint queue + let queued = false; + let lastFinal = false; + return (final = false): void => { + // If any queued request is “final”, we propagate final=true once we flush. + lastFinal = lastFinal || final; + if (queued) return; + queued = true; + setImmediate(() => { + queued = false; + fn(lastFinal); + lastFinal = false; + }); + }; +} + /** - * Parent entrypoint for the "chunk-csv" command. + * Parent entrypoint for chunking many CSVs in parallel using a worker pool. * - * - Collects input CSV files. - * - Spawns a pool of worker processes to handle them concurrently. - * - Tracks per-worker state, progress, and errors. - * - Periodically re-renders a TTY dashboard until all work completes. + * Lifecycle: + * 1) Discover CSV inputs (exit if none). + * 2) Compute pool size (CPU-count heuristic or --concurrency). + * 3) Spawn workers and wire up IPC/STDERR listeners. + * 4) Assign work from a pending queue; track progress in `workerState`. + * 5) Render a flicker-free dashboard (periodic + on events). + * 6) Provide an interactive switcher (digits 0–9 to attach, etc.). + * 7) Gracefully shutdown once the queue is empty and all workers exit. * - * @param this - Execution context (includes logger, config, etc.) - * @param flags - Command-line flags controlling chunking behavior + * @param this - Bound CLI context (for process.exit and logging). + * @param flags - CLI options for the run. */ export async function chunkCsvParent( this: LocalContext, @@ -83,10 +122,10 @@ export async function chunkCsvParent( const { directory, outputDir, clearOutputDir, chunkSizeMB, concurrency } = flags; - // Gather CSV files from the input directory or exit if none found + /* 1) Discover inputs */ const files = collectCsvFilesOrExit(directory, this); - // Determine pool size and CPU availability + /* 2) Size the pool */ const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); logger.info( @@ -95,7 +134,7 @@ export async function chunkCsvParent( ), ); - // --- Shared counters and global state --- + /* 3) Global state for this run */ const workers = new Map(); const workerState = new Map(); const slotLogs = new Map>(); @@ -103,15 +142,16 @@ export async function chunkCsvParent( const totals = { completed: 0, failed: 0 }; let activeWorkers = 0; - // Track global throughput (rows/sec across all workers) + // Optional throughput meter (rows/s) if you want to aggregate across workers. const meter = new RateCounter(); /** - * Refresh the dashboard view in the terminal. + * Render the dashboard. Note: `renderDashboard` itself deduplicates frames, + * but we also coalesce call sites (see `repaint` below) to avoid bursty double draws. * - * @param final - When true, finalizes the view (shows cursor again). + * @param final - Whether this is the final frame (e.g., on shutdown). */ - const repaint = (final = false): void => { + const doRender = (final = false): void => { renderDashboard({ poolSize, cpuCount, @@ -121,19 +161,23 @@ export async function chunkCsvParent( workerState, final, throughput: { - successSoFar: 0, // success counting not wired here + successSoFar: 0, // Cumulated “success” not tracked here; easy to add if needed. r10s: meter.rate(10_000), r60s: meter.rate(60_000), }, exportsDir: directory || outputDir || process.cwd(), - exportStatus: {}, // not used in this command + exportStatus: {}, }); }; + // Combine multiple doRender() triggers in the same tick into a single repaint. + const repaint = createRepaintScheduler(doRender); + /** - * Assign the next pending file to a given worker. + * Assign the next file in the queue to a given worker slot. + * Updates dashboard state and sends a task message over IPC. * - * @param id - Worker ID + * @param id - Worker ID to assign the next file to. */ const assign = (id: number): void => { if (pending.length === 0) return; @@ -145,7 +189,7 @@ export async function chunkCsvParent( busy: true, file: filePath, lastLevel: 'ok', - progress: { processed: 0, total: undefined }, + progress: { processed: 0 }, }); safeSend(w, { @@ -159,7 +203,7 @@ export async function chunkCsvParent( repaint(); }; - // --- Spawn worker pool --- + /* 4) Spawn workers + attach listeners */ const logDir = initLogDir(directory || outputDir || process.cwd()); const modulePath = getCurrentModulePath(); @@ -173,16 +217,11 @@ export async function chunkCsvParent( }); workers.set(i, child); - workerState.set(i, { - busy: false, - file: null, - lastLevel: 'ok', - progress: undefined, - }); + workerState.set(i, { busy: false, file: null, lastLevel: 'ok' }); slotLogs.set(i, getWorkerLogPaths(child)); activeWorkers += 1; - // Classify stderr lines into warn/error for dashboard badges + // Classify each stderr line into ok/warn/error to badge the worker row. const errLine = makeLineSplitter((line) => { const lvl = classifyLogLevel(line); if (!lvl) return; @@ -194,16 +233,18 @@ export async function chunkCsvParent( }); child.stderr?.on('data', errLine); - // Handle structured messages from the child process + // Core IPC we expect from the worker lifecycle. child.on('message', (msg: ParentInbound) => { if (!msg || typeof msg !== 'object') return; + // Worker is ready to accept work. if (msg.type === 'ready') { assign(i); repaint(); return; } + // Streaming progress (per row chunk, etc). if (msg.type === 'progress') { const { filePath, processed, total } = msg.payload || {}; const prev = workerState.get(i)!; @@ -216,6 +257,7 @@ export async function chunkCsvParent( return; } + // File completed (ok / failed). We move on to the next file or shut down. if (msg.type === 'result') { const { ok } = msg.payload || {}; if (ok) totals.completed += 1; @@ -231,33 +273,95 @@ export async function chunkCsvParent( }); if (pending.length > 0) assign(i); - else if (!pending.length) { - if (isIpcOpen(child)) safeSend(child, { type: 'shutdown' }); - } + else if (isIpcOpen(child)) safeSend(child, { type: 'shutdown' }); + repaint(); } }); - // Handle worker exit + // Keep `activeWorkers` in sync; when it reaches zero and there’s no + // pending work, we can finalize the dashboard and exit. // eslint-disable-next-line no-loop-func child.on('exit', () => { activeWorkers -= 1; - if (activeWorkers === 0) { - repaint(true); - } + if (activeWorkers === 0) repaint(true); }); } - // Periodic repaint while work is ongoing - const tick = setInterval(() => repaint(false), 350); + /* 5) Interactive attach/switcher setup (digits 0–9, Tab cycle, Esc detach, etc.) */ + + // Periodic repaint handle (cleared on shutdown). + let tick: NodeJS.Timeout | undefined; + + /** + * Dashboard-level Ctrl+C handling: + * - If in dashboard mode: gracefully stop all workers and exit 130. + * (The switcher will forward Ctrl+C to this handler.) + * - If in attached mode: the switcher sends SIGINT to the focused child, + * then detaches back to the dashboard. + */ + const onSigint = (): void => { + if (tick) clearInterval(tick); + cleanupSwitcher?.(); + + process.stdout.write('\nStopping workers...\n'); + for (const [, w] of workers) { + if (isIpcOpen(w)) safeSend(w, { type: 'shutdown' }); + try { + w?.kill('SIGTERM'); + } catch { + // Best-effort; process may already be gone. + } + } + this.process.exit(130); + }; + process.once('SIGINT', onSigint); + + /** When detaching from a child, clear and immediately repaint the dashboard. */ + const onDetachScreen = (): void => { + process.stdout.write('\x1b[2J\x1b[H'); + repaint(); + }; + + /** + * When attaching to a child, clear and show a small banner. + * + * @param id - Worker ID to attach to. + */ + const onAttachScreen = (id: number): void => { + process.stdout.write('\x1b[2J\x1b[H'); + process.stdout.write( + `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT • Ctrl+D EOF)\n`, + ); + }; + + // Register the key-driven interactive switcher (0–9 attach, Tab cycle, etc). + let cleanupSwitcher: () => void = () => { + // No-op by default; will be set by installInteractiveSwitcher below. + }; + cleanupSwitcher = installInteractiveSwitcher({ + workers, + onAttach: onAttachScreen, + onDetach: onDetachScreen, + onCtrlC: onSigint, + getLogPaths: (id) => slotLogs.get(id), + replayBytes: 200 * 1024, // Tail ~200KB on attach so you see recent history + replayWhich: ['out', 'err'], // Replay stdout then stderr + onEnterAttachScreen: onAttachScreen, + }); + + /* 6) Initial paint + periodic refresh (coalesced with on-event repaints) */ + repaint(false); // single initial frame (prevents “double frame” burst) + tick = setInterval(() => repaint(false), 350); - // Resolve once all files are processed and all workers have exited + /* 7) Wait for completion: queue empty + all workers exited */ await new Promise((resolve) => { const check = setInterval(() => { if (pending.length === 0 && activeWorkers === 0) { clearInterval(check); - clearInterval(tick); - repaint(true); + if (tick) clearInterval(tick); + repaint(true); // render final frame (restores cursor) + cleanupSwitcher(); // remove key handlers/TTY muting resolve(); } }, 300); @@ -265,8 +369,7 @@ export async function chunkCsvParent( } /* ------------------------------------------------------------------------------------------------- - * Child entrypoint: - * If process was invoked with CHILD_FLAG, run worker loop instead of parent. + * If invoked directly as a child process, enter worker loop * ------------------------------------------------------------------------------------------------- */ if (process.argv.includes(CHILD_FLAG)) { runChild().catch((err) => { diff --git a/src/commands/admin/chunk-csv/ui/renderDashboard.ts b/src/commands/admin/chunk-csv/ui/renderDashboard.ts index 09201905..b1102c92 100644 --- a/src/commands/admin/chunk-csv/ui/renderDashboard.ts +++ b/src/commands/admin/chunk-csv/ui/renderDashboard.ts @@ -2,81 +2,91 @@ import * as readline from 'node:readline'; import { buildFrameModel, type RenderDashboardInput } from './buildFrameModel'; let lastFrame = ''; +let frameTick = 0; + +/** Simple unicode spinner for BUSY workers when total is unknown. */ +const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + +/** Left/right caps for bars; use ASCII if you prefer. */ +const BAR_LEFT = '│'; +const BAR_RIGHT = '│'; +const BAR_FULL = '█'; +const BAR_EMPTY = '░'; /** * Render a flicker-free, text-based dashboard in the terminal (TTY) * for monitoring CSV chunking workers. * - * This function is designed to continuously refresh the screen in place, - * showing: - * - Overall progress (completed/failed/total files) - * - Pool and CPU metrics - * - Recent throughput rates - * - Each worker’s state (busy/idle, warning/error state, progress, file path) + * What you see: + * - Overall progress (completed / failed / total) + * - Pool/CPU metrics + * - Recent throughput (r10s / r60s) + * - Each worker: BUSY/IDLE (+WARN/ERROR), rows processed, file path + * - A progress bar per worker (if total known), or a spinner if total unknown * - * It achieves flicker-free rendering by: - * 1. Caching the last rendered frame (`lastFrame`) and only redrawing if - * something has changed. - * 2. Using ANSI escape sequences + `readline` helpers to overwrite the - * existing terminal content rather than printing new lines. - * 3. Hiding the cursor while active updates are happening, then restoring it - * when rendering the final frame. + * Hotkeys (handled by the interactive switcher): + * 0–9 attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C quit * - * @param input - The current state of the pool, worker map, throughput, etc. - * Typically produced on each heartbeat/interval by the main process. + * @param input - Input object containing state and metrics to render */ export function renderDashboard(input: RenderDashboardInput): void { - // Normalize the current state into a simple model shape. + frameTick = (frameTick + 1) % SPIN.length; const m = buildFrameModel(input); - // Build the header line summarizing pool and throughput statistics. + // header const head = `Chunk CSV — ${m.filesCompleted}/${m.filesTotal} done, failed ${m.filesFailed}\n` + `Pool ${m.poolSize} • CPU ${m.cpuCount} • r10s ${m.throughput.r10s.toFixed( 1, - )} r60s ${m.throughput.r60s.toFixed(1)}`; + )} ` + + `r60s ${m.throughput.r60s.toFixed(1)}\n` + + '(hotkeys: 0–9 attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C quit)'; - // Build per-worker status rows, including: - // - worker ID - // - BUSY/IDLE state - // - optional WARN/ERROR tag - // - processed row count - // - file currently assigned (if any) + // per-worker line with progress bar / spinner const rows = m.workers .map((w) => { const status = w.busy ? 'BUSY' : 'IDLE'; const lvl = w.level === 'ok' ? '' : w.level === 'warn' ? ' [WARN]' : ' [ERROR]'; - const prog = w.processed - ? `rows=${w.processed.toLocaleString()}` - : 'rows=0'; + + const processed = w.processed ?? 0; + const total = w.total ?? undefined; + + let progressVisual = ''; + if (w.busy && typeof total === 'number' && total > 0) { + const pct = Math.max(0, Math.min(1, processed / total)); + const width = 28; // bar width + const filled = Math.round(pct * width); + const empty = width - filled; + progressVisual = `${BAR_LEFT}${BAR_FULL.repeat( + filled, + )}${BAR_EMPTY.repeat(empty)}${BAR_RIGHT} (${Math.floor(pct * 100)}%)`; + } else if (w.busy) { + progressVisual = `${SPIN[frameTick]} processing`; + } else { + progressVisual = '—'; + } + + const prog = `rows=${processed.toLocaleString()} ${progressVisual}`; const file = w.file ? ` — ${w.file}` : ''; + return `w${w.id .toString() - .padStart(2, '0')}: ${status}${lvl} ${prog}${file}`; + .padStart(2, '0')}: ${status}${lvl} ${prog}${file}`; }) .join('\n'); - // Assemble the full frame string for this render cycle. const frame = `${head}\n\n${rows}\n`; - // Optimization: avoid redrawing if nothing has changed and we’re not final. if (!input.final && frame === lastFrame) return; lastFrame = frame; if (!input.final) { - // Live updates: - // - Hide the cursor for aesthetics - // - Reset cursor to top-left - // - Clear the screen before redrawing process.stdout.write('\x1b[?25l'); readline.cursorTo(process.stdout, 0, 0); readline.clearScreenDown(process.stdout); } else { - // Final frame: restore the cursor so the user can interact normally again. process.stdout.write('\x1b[?25h'); } - - // Write the frame (always with a trailing newline for separation). process.stdout.write(`${frame}\n`); } diff --git a/src/lib/helpers/chunkOneCsvFile.ts b/src/lib/helpers/chunkOneCsvFile.ts index 38a1fa40..9d90ae74 100644 --- a/src/lib/helpers/chunkOneCsvFile.ts +++ b/src/lib/helpers/chunkOneCsvFile.ts @@ -109,14 +109,19 @@ function approxRowBytes(obj: Record): number { */ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { const { filePath, outputDir, clearOutputDir, chunkSizeMB, onProgress } = opts; + logger.info( + colors.magenta(`Chunking ${filePath} into ~${chunkSizeMB}MB files...`), + ); const chunkSizeBytes = Math.floor(chunkSizeMB * 1024 * 1024); const baseName = basename(filePath, '.csv'); const outDir = outputDir || dirname(filePath); + logger.info(colors.magenta(`Output directory: ${outDir}`)); await mkdir(outDir, { recursive: true }); // Clear previous chunk files for this base if (clearOutputDir) { + logger.warn(colors.yellow(`Clearing output directory: ${outDir}`)); const files = await readdir(outDir); await Promise.all( files @@ -201,6 +206,11 @@ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { await writer.end(); currentChunk += 1; currentSize = 0; + logger.info( + colors.green( + `Rolling to chunk ${currentChunk} after ${totalLines.toLocaleString()} rows.`, + ), + ); writer = createCsvChunkWriter(currentChunkPath(), headerRow!); } From 95321230c0abd01f3e300669f61a13197fdd2c27 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 18 Aug 2025 00:53:58 -0700 Subject: [PATCH 47/72] RefatoreD --- README.md | 20 +- src/commands/admin/chunk-csv/command.ts | 11 +- src/commands/admin/chunk-csv/impl.ts | 433 ++++-------- src/commands/admin/chunk-csv/test/index.ts | 1 + .../admin/chunk-csv/ui/buildFrameModel.ts | 98 --- src/commands/admin/chunk-csv/ui/index.ts | 1 + src/commands/admin/chunk-csv/ui/plugin.ts | 38 + .../admin/chunk-csv/ui/renderDashboard.ts | 92 --- .../ui/tests/buildFrameModel.test.ts | 142 ---- .../admin/chunk-csv/ui/tests/plugin.test.ts | 0 .../ui/tests/renderDashboard.test.ts | 140 ---- .../receipts/applyReceiptSummary.ts | 2 +- .../{ => artifacts}/receipts/index.ts | 0 .../receipts/readFailingUpdatesFromReceipt.ts | 2 +- .../{ => artifacts}/receipts/receiptsState.ts | 4 +- .../receipts/resolveReceiptPath.ts | 2 +- .../receipts/summarizeReceipt.ts | 2 +- .../tests/applyReceiptSummary.test.ts | 0 .../readFailingUpdatesFromReceipt.test.ts | 0 .../receipts/tests/receiptsState.test.ts | 0 .../receipts/tests/resolveReceiptPath.test.ts | 0 .../receipts/tests/summarizeReceipt.test.ts | 0 .../consent/upload-preferences/command.ts | 10 +- .../consent/upload-preferences/impl.ts | 647 ++++++++---------- .../upload-preferences/ui/buildFrameModel.ts | 215 ------ .../upload-preferences/ui/headerLines.ts | 227 ------ .../consent/upload-preferences/ui/index.ts | 6 +- .../ui/makeOnKeypressExtra.ts | 145 ---- .../consent/upload-preferences/ui/plugin.ts | 324 +++++++++ .../upload-preferences/ui/renderDashboard.ts | 46 -- .../ui/tests/buildFrameModel.test.ts | 303 -------- .../ui/tests/headerLines.test.ts | 258 ------- .../ui/tests/makeOnKeypressExtra.test.ts | 252 ------- .../ui/tests/plugin.test.ts | 0 .../ui/tests/renderDashboard.test.ts | 122 ---- .../ui/tests/typeGuards.test.ts | 0 .../upload-preferences/ui/typeGuards.ts | 62 ++ .../upload/buildInteractiveUploadPlan.ts | 4 +- .../interactivePreferenceUploaderFromPlan.ts | 4 +- .../transform/buildPendingUpdates.ts | 9 +- .../{ => upload}/transform/index.ts | 0 .../{ => upload}/transform/transformCsv.ts | 2 +- .../consent/upload-preferences/worker.ts | 2 +- src/constants.ts | 5 + src/lib/graphql/index.ts | 1 + src/lib/helpers/getCurrentModulePath.ts | 12 - src/lib/helpers/index.ts | 1 - src/lib/pooling/assignWorkToWorker.ts | 130 ---- src/lib/pooling/attachWorkerHandlers.ts | 213 ------ src/lib/pooling/dashboardPlugin.ts | 173 +++++ src/lib/pooling/diagnostics.ts | 59 -- src/lib/pooling/extraKeys.ts | 241 +++++++ src/lib/pooling/index.ts | 10 +- src/lib/pooling/installInteractiveSwitcher.ts | 42 +- src/lib/pooling/ipc.ts | 106 --- src/lib/pooling/logRotation.ts | 7 +- src/lib/pooling/runPool.ts | 620 +++++++++++++++++ src/lib/pooling/spawnWorkerProcess.ts | 5 +- .../pooling/tests/appendFailureLog.test.ts | 51 -- .../pooling/tests/assignWorkToSlot.test.ts | 185 ----- .../pooling/tests/assignWorkToWorker.test.ts | 239 ------- .../tests/attachWorkerHandlers.test.ts | 438 ------------ src/lib/pooling/tests/dashboardPlugin.test.ts | 0 src/lib/pooling/tests/extraKeys.test.ts | 0 .../tests/isWorkerProgressMessage.test.ts | 63 -- .../tests/isWorkerReadyMessage.test.ts | 38 - .../tests/isWorkerResultMessage.test.ts | 65 -- .../pooling/tests/refillIdleWorkers.test.ts | 134 ---- src/lib/pooling/tests/runPool.test.ts | 0 src/lib/pooling/tests/uiPlugins.test.ts | 0 .../pooling/tests/wireStderrBadges.test.ts | 138 ---- src/lib/pooling/types.ts | 80 +++ src/lib/pooling/uiPlugins.ts | 158 +++++ src/lib/pooling/workerAssignment.ts | 103 --- 74 files changed, 2190 insertions(+), 4753 deletions(-) create mode 100644 src/commands/admin/chunk-csv/test/index.ts delete mode 100644 src/commands/admin/chunk-csv/ui/buildFrameModel.ts create mode 100644 src/commands/admin/chunk-csv/ui/index.ts create mode 100644 src/commands/admin/chunk-csv/ui/plugin.ts delete mode 100644 src/commands/admin/chunk-csv/ui/renderDashboard.ts delete mode 100644 src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts create mode 100644 src/commands/admin/chunk-csv/ui/tests/plugin.test.ts delete mode 100644 src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/applyReceiptSummary.ts (97%) rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/index.ts (100%) rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/readFailingUpdatesFromReceipt.ts (95%) rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/receiptsState.ts (97%) rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/resolveReceiptPath.ts (95%) rename src/commands/consent/upload-preferences/{ => artifacts}/receipts/summarizeReceipt.ts (97%) create mode 100644 src/commands/consent/upload-preferences/artifacts/receipts/tests/applyReceiptSummary.test.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/receipts/tests/readFailingUpdatesFromReceipt.test.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/receipts/tests/receiptsState.test.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/receipts/tests/resolveReceiptPath.test.ts create mode 100644 src/commands/consent/upload-preferences/artifacts/receipts/tests/summarizeReceipt.test.ts delete mode 100644 src/commands/consent/upload-preferences/ui/buildFrameModel.ts delete mode 100644 src/commands/consent/upload-preferences/ui/headerLines.ts delete mode 100644 src/commands/consent/upload-preferences/ui/makeOnKeypressExtra.ts create mode 100644 src/commands/consent/upload-preferences/ui/plugin.ts delete mode 100644 src/commands/consent/upload-preferences/ui/renderDashboard.ts delete mode 100644 src/commands/consent/upload-preferences/ui/tests/buildFrameModel.test.ts delete mode 100644 src/commands/consent/upload-preferences/ui/tests/headerLines.test.ts delete mode 100644 src/commands/consent/upload-preferences/ui/tests/makeOnKeypressExtra.test.ts create mode 100644 src/commands/consent/upload-preferences/ui/tests/plugin.test.ts delete mode 100644 src/commands/consent/upload-preferences/ui/tests/renderDashboard.test.ts create mode 100644 src/commands/consent/upload-preferences/ui/tests/typeGuards.test.ts create mode 100644 src/commands/consent/upload-preferences/ui/typeGuards.ts rename src/commands/consent/upload-preferences/{ => upload}/transform/buildPendingUpdates.ts (94%) rename src/commands/consent/upload-preferences/{ => upload}/transform/index.ts (100%) rename src/commands/consent/upload-preferences/{ => upload}/transform/transformCsv.ts (96%) delete mode 100644 src/lib/helpers/getCurrentModulePath.ts delete mode 100644 src/lib/pooling/assignWorkToWorker.ts delete mode 100644 src/lib/pooling/attachWorkerHandlers.ts create mode 100644 src/lib/pooling/dashboardPlugin.ts delete mode 100644 src/lib/pooling/diagnostics.ts create mode 100644 src/lib/pooling/extraKeys.ts delete mode 100644 src/lib/pooling/ipc.ts create mode 100644 src/lib/pooling/runPool.ts delete mode 100644 src/lib/pooling/tests/appendFailureLog.test.ts delete mode 100644 src/lib/pooling/tests/assignWorkToSlot.test.ts delete mode 100644 src/lib/pooling/tests/assignWorkToWorker.test.ts delete mode 100644 src/lib/pooling/tests/attachWorkerHandlers.test.ts create mode 100644 src/lib/pooling/tests/dashboardPlugin.test.ts create mode 100644 src/lib/pooling/tests/extraKeys.test.ts delete mode 100644 src/lib/pooling/tests/isWorkerProgressMessage.test.ts delete mode 100644 src/lib/pooling/tests/isWorkerReadyMessage.test.ts delete mode 100644 src/lib/pooling/tests/isWorkerResultMessage.test.ts delete mode 100644 src/lib/pooling/tests/refillIdleWorkers.test.ts create mode 100644 src/lib/pooling/tests/runPool.test.ts create mode 100644 src/lib/pooling/tests/uiPlugins.test.ts delete mode 100644 src/lib/pooling/tests/wireStderrBadges.test.ts create mode 100644 src/lib/pooling/types.ts create mode 100644 src/lib/pooling/uiPlugins.ts delete mode 100644 src/lib/pooling/workerAssignment.ts diff --git a/README.md b/README.md index 25afd3fb..c9aefe0d 100644 --- a/README.md +++ b/README.md @@ -2081,7 +2081,7 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [--viewerMode] transcend consent upload-preferences --help Upload preference management data to your Preference Store. @@ -2096,7 +2096,7 @@ Parallel preference uploader (Node 22+ ESM/TS) - Shows a live dashboard in the parent terminal with progress per worker. - Creates per-worker log files and (optionally) opens OS terminals to tail them. - Uses the same module as both parent and child; the child mode is toggled - by the presence of a CLI flag ('--child-upload-preferences'). + by the presence of a CLI flag ('--as-child'). FLAGS --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" @@ -2124,6 +2124,7 @@ FLAGS --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. + [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] -h --help Print help information and exit ``` @@ -3113,7 +3114,7 @@ query { ```txt USAGE - transcend admin chunk-csv (--directory value) [--outputDir value] [--clearOutputDir value] [--chunkSizeMB value] [--concurrency value] + transcend admin chunk-csv (--directory value) [--outputDir value] [--clearOutputDir] [--chunkSizeMB value] [--concurrency value] [--viewerMode] transcend admin chunk-csv --help Streams every CSV in --directory and writes chunked files of approximately N MB each. @@ -3121,12 +3122,13 @@ Streams every CSV in --directory and writes chunked files of approximately N MB - Validates row-length consistency against the header row; logs periodic progress and memory usage. FLAGS - --directory Directory containing CSV files to split (required) - [--outputDir] Directory to write chunk files (defaults to each input file's directory) - [--clearOutputDir] Clear the output directory before writing chunks [default = true] - [--chunkSizeMB] Approximate chunk size in megabytes. Keep well under JS string size limits [default = 10] - [--concurrency] Max number of worker processes (defaults based on CPU and file count) - -h --help Print help information and exit + --directory Directory containing CSV files to split (required) + [--outputDir] Directory to write chunk files (defaults to each input file's directory) + [--clearOutputDir/--noClearOutputDir] Clear the output directory before writing chunks [default = true] + [--chunkSizeMB] Approximate chunk size in megabytes. Keep well under JS string size limits [default = 10] + [--concurrency] Max number of worker processes (defaults based on CPU and file count) + [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] + -h --help Print help information and exit ``` #### Examples diff --git a/src/commands/admin/chunk-csv/command.ts b/src/commands/admin/chunk-csv/command.ts index 11a260b7..c8f989c8 100644 --- a/src/commands/admin/chunk-csv/command.ts +++ b/src/commands/admin/chunk-csv/command.ts @@ -20,10 +20,9 @@ export const chunkCsvCommand = buildCommand({ optional: true, }, clearOutputDir: { - kind: 'parsed', - parse: Boolean, + kind: 'boolean', brief: 'Clear the output directory before writing chunks', - default: 'true', + default: true, }, chunkSizeMB: { kind: 'parsed', @@ -45,6 +44,12 @@ export const chunkCsvCommand = buildCommand({ 'Max number of worker processes (defaults based on CPU and file count)', optional: true, }, + viewerMode: { + kind: 'boolean', + brief: + 'Run in non-interactive viewer mode (no attach UI, auto-artifacts)', + default: false, + }, }, }, docs: { diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index 883499a2..e2be3b9e 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -1,128 +1,120 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; -import type { ChildProcess } from 'node:child_process'; - +import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; import { computePoolSize, - getWorkerLogPaths, - isIpcOpen, - safeSend, - spawnWorkerProcess, - classifyLogLevel, - makeLineSplitter, - initLogDir, CHILD_FLAG, - installInteractiveSwitcher, + type PoolHooks, + runPool, + dashboardPlugin, } from '../../../lib/pooling'; -import { getCurrentModulePath, RateCounter } from '../../../lib/helpers'; -import { renderDashboard } from './ui/renderDashboard'; import { runChild } from './worker'; -import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; +import { chunkCsvPlugin } from './ui'; +import { createExtraKeyHandler } from '../../../lib/pooling/extraKeys'; /** - * CLI flags accepted by the chunk-csv command. + * Returns the current module's path so the worker pool knows what file to re-exec. + * In Node ESM, __filename is undefined, so we fall back to argv[1]. * - * Most flags are passed down to workers as part of each task payload. + * @returns The current module's path as a string */ -export type ChunkCsvCommandFlags = { - /** Directory containing input CSVs to chunk (required). */ - directory: string; - /** Optional output directory for chunked files (defaults near the input). */ - outputDir?: string; - /** Remove any previous chunk files for a CSV before writing new ones. */ - clearOutputDir: boolean; - /** Approximate chunk size threshold in MB (e.g., 10). */ - chunkSizeMB: number; - /** Optional override for parallelism; if omitted, use a CPU-based heuristic. */ - concurrency?: number; +function getCurrentModulePath(): string { + if (typeof __filename !== 'undefined') { + return __filename as unknown as string; + } + return process.argv[1]; +} + +/** + * A unit of work: instructs a worker to chunk a single CSV file. + */ +export type ChunkTask = { + /** Absolute path of the CSV file to chunk. */ + filePath: string; + /** Options controlling output and chunk size. */ + options: { + /** Optional directory where chunked output files should be written. */ + outputDir?: string; + /** Whether to clear any pre-existing output chunks before writing new ones. */ + clearOutputDir: boolean; + /** Approximate target chunk size in MB (well under Node’s string size limits). */ + chunkSizeMB: number; + }; }; -/** Per-worker progress snapshot. */ -type WorkerProgress = { - /** Rows processed so far for the current file. */ +/** + * Per-worker progress snapshot for the chunk-csv command. + */ +export type ChunkProgress = { + /** File being processed by the worker. */ + filePath: string; + /** Number of rows processed so far. */ processed: number; - /** Total rows (if known). Not all workers will report a total. */ + /** Optional total rows in the file (not always known). */ total?: number; }; -/** Minimal state we track for each live worker. */ -type WorkerState = { - /** Whether the worker is actively processing a file (vs. idle/waiting). */ - busy: boolean; - /** Which file the worker is assigned (null when idle). */ - file: string | null; - /** Last “level” seen in its stderr (ok/warn/error) for dashboard badges. */ - lastLevel: 'ok' | 'warn' | 'error'; - /** Streaming progress info (rows processed / total if available). */ - progress?: WorkerProgress; -}; - -/** Child → Parent messages we handle. */ -type ReadyMsg = { type: 'ready' }; -type ProgressMsg = { - type: 'progress'; - payload: { filePath: string; processed: number; total?: number }; -}; -type ResultMsg = { - type: 'result'; - payload: { ok: boolean; filePath: string; error?: string }; +/** + * Worker result message once a file has finished processing. + */ +export type ChunkResult = { + /** Whether the file completed successfully. */ + ok: boolean; + /** File path for which this result applies. */ + filePath: string; + /** Optional error message if the file failed to chunk. */ + error?: string; }; -type ParentInbound = ReadyMsg | ProgressMsg | ResultMsg; -/* ================================================================================================ - * Small utilities - * ============================================================================================== */ +/** + * Totals aggregate for this command. + * We don’t need custom counters since the runner already tracks + * completed/failed counts in its header — so we just use an empty record. + */ +type Totals = Record; /** - * Coalesce multiple fast “repaint” calls into a single render in the same tick. - * This prevents “double draw at startup” and other quick successions of redraws. + * CLI flags accepted by the `chunk-csv` command. * - * @param fn - Function to call with final=true when the repaint is flushed. - * @returns A function that can be called with final=false to queue a repaint. + * These are passed down from the CLI parser into the parent process. */ -function createRepaintScheduler( - fn: (final?: boolean) => void, -): (final?: boolean) => void { - // Use a closure to track the state of the repaint queue - let queued = false; - let lastFinal = false; - return (final = false): void => { - // If any queued request is “final”, we propagate final=true once we flush. - lastFinal = lastFinal || final; - if (queued) return; - queued = true; - setImmediate(() => { - queued = false; - fn(lastFinal); - lastFinal = false; - }); - }; -} +export type ChunkCsvCommandFlags = { + directory: string; + outputDir?: string; + clearOutputDir: boolean; + chunkSizeMB: number; + concurrency?: number; + viewerMode: boolean; +}; /** - * Parent entrypoint for chunking many CSVs in parallel using a worker pool. + * Parent entrypoint for chunking many CSVs in parallel using the worker pool runner. * * Lifecycle: * 1) Discover CSV inputs (exit if none). * 2) Compute pool size (CPU-count heuristic or --concurrency). - * 3) Spawn workers and wire up IPC/STDERR listeners. - * 4) Assign work from a pending queue; track progress in `workerState`. - * 5) Render a flicker-free dashboard (periodic + on events). - * 6) Provide an interactive switcher (digits 0–9 to attach, etc.). - * 7) Gracefully shutdown once the queue is empty and all workers exit. + * 3) Build a FIFO queue of `ChunkTask`s. + * 4) Define pool hooks to drive task assignment, progress, and result handling. + * 5) Launch the pool with `runPool`, rendering via the `chunkCsvPlugin`. * - * @param this - Bound CLI context (for process.exit and logging). + * @param this - Bound CLI context (provides process exit + logging). * @param flags - CLI options for the run. */ export async function chunkCsvParent( this: LocalContext, flags: ChunkCsvCommandFlags, ): Promise { - const { directory, outputDir, clearOutputDir, chunkSizeMB, concurrency } = - flags; - - /* 1) Discover inputs */ + const { + directory, + outputDir, + clearOutputDir, + chunkSizeMB, + concurrency, + viewerMode, + } = flags; + + /* 1) Discover CSV inputs */ const files = collectCsvFilesOrExit(directory, this); /* 2) Size the pool */ @@ -134,237 +126,44 @@ export async function chunkCsvParent( ), ); - /* 3) Global state for this run */ - const workers = new Map(); - const workerState = new Map(); - const slotLogs = new Map>(); - const pending = [...files]; - const totals = { completed: 0, failed: 0 }; - let activeWorkers = 0; - - // Optional throughput meter (rows/s) if you want to aggregate across workers. - const meter = new RateCounter(); - - /** - * Render the dashboard. Note: `renderDashboard` itself deduplicates frames, - * but we also coalesce call sites (see `repaint` below) to avoid bursty double draws. - * - * @param final - Whether this is the final frame (e.g., on shutdown). - */ - const doRender = (final = false): void => { - renderDashboard({ - poolSize, - cpuCount, - filesTotal: files.length, - filesCompleted: totals.completed, - filesFailed: totals.failed, - workerState, - final, - throughput: { - successSoFar: 0, // Cumulated “success” not tracked here; easy to add if needed. - r10s: meter.rate(10_000), - r60s: meter.rate(60_000), - }, - exportsDir: directory || outputDir || process.cwd(), - exportStatus: {}, - }); - }; - - // Combine multiple doRender() triggers in the same tick into a single repaint. - const repaint = createRepaintScheduler(doRender); - - /** - * Assign the next file in the queue to a given worker slot. - * Updates dashboard state and sends a task message over IPC. - * - * @param id - Worker ID to assign the next file to. - */ - const assign = (id: number): void => { - if (pending.length === 0) return; - const filePath = pending.shift(); - if (!filePath) return; - - const w = workers.get(id)!; - workerState.set(id, { - busy: true, - file: filePath, - lastLevel: 'ok', - progress: { processed: 0 }, - }); - - safeSend(w, { - type: 'task', - payload: { - filePath, - options: { outputDir, clearOutputDir, chunkSizeMB }, - }, - }); - - repaint(); - }; - - /* 4) Spawn workers + attach listeners */ - const logDir = initLogDir(directory || outputDir || process.cwd()); - const modulePath = getCurrentModulePath(); - - for (let i = 0; i < poolSize; i += 1) { - const child = spawnWorkerProcess({ - id: i, - modulePath, - logDir, - openLogWindows: false, - isSilent: true, - }); - - workers.set(i, child); - workerState.set(i, { busy: false, file: null, lastLevel: 'ok' }); - slotLogs.set(i, getWorkerLogPaths(child)); - activeWorkers += 1; - - // Classify each stderr line into ok/warn/error to badge the worker row. - const errLine = makeLineSplitter((line) => { - const lvl = classifyLogLevel(line); - if (!lvl) return; - const prev = workerState.get(i)!; - if (prev.lastLevel !== lvl) { - workerState.set(i, { ...prev, lastLevel: lvl }); - repaint(); - } - }); - child.stderr?.on('data', errLine); - - // Core IPC we expect from the worker lifecycle. - child.on('message', (msg: ParentInbound) => { - if (!msg || typeof msg !== 'object') return; - - // Worker is ready to accept work. - if (msg.type === 'ready') { - assign(i); - repaint(); - return; - } - - // Streaming progress (per row chunk, etc). - if (msg.type === 'progress') { - const { filePath, processed, total } = msg.payload || {}; - const prev = workerState.get(i)!; - workerState.set(i, { - ...prev, - file: prev.file ?? filePath ?? prev.file, - progress: { processed, total }, - }); - repaint(); - return; - } - - // File completed (ok / failed). We move on to the next file or shut down. - if (msg.type === 'result') { - const { ok } = msg.payload || {}; - if (ok) totals.completed += 1; - else totals.failed += 1; - - const prev = workerState.get(i)!; - workerState.set(i, { - ...prev, - busy: false, - file: null, - progress: undefined, - lastLevel: ok ? 'ok' : 'error', - }); - - if (pending.length > 0) assign(i); - else if (isIpcOpen(child)) safeSend(child, { type: 'shutdown' }); - - repaint(); - } - }); - - // Keep `activeWorkers` in sync; when it reaches zero and there’s no - // pending work, we can finalize the dashboard and exit. - // eslint-disable-next-line no-loop-func - child.on('exit', () => { - activeWorkers -= 1; - if (activeWorkers === 0) repaint(true); - }); - } - - /* 5) Interactive attach/switcher setup (digits 0–9, Tab cycle, Esc detach, etc.) */ - - // Periodic repaint handle (cleared on shutdown). - let tick: NodeJS.Timeout | undefined; - - /** - * Dashboard-level Ctrl+C handling: - * - If in dashboard mode: gracefully stop all workers and exit 130. - * (The switcher will forward Ctrl+C to this handler.) - * - If in attached mode: the switcher sends SIGINT to the focused child, - * then detaches back to the dashboard. - */ - const onSigint = (): void => { - if (tick) clearInterval(tick); - cleanupSwitcher?.(); - - process.stdout.write('\nStopping workers...\n'); - for (const [, w] of workers) { - if (isIpcOpen(w)) safeSend(w, { type: 'shutdown' }); - try { - w?.kill('SIGTERM'); - } catch { - // Best-effort; process may already be gone. - } - } - this.process.exit(130); + /* 3) Prepare a simple FIFO queue of tasks (one per file). */ + const queue = files.map((filePath) => ({ + filePath, + options: { outputDir, clearOutputDir, chunkSizeMB }, + })); + + /* 4) Define pool hooks to adapt runner to this command. */ + const hooks: PoolHooks = { + nextTask: () => queue.shift(), + taskLabel: (t) => t.filePath, + initTotals: () => ({} as Totals), + initSlotProgress: () => undefined, + onProgress: (totals) => totals, + onResult: (totals, res) => ({ totals, ok: !!res.ok }), + // postProcess receives log context when viewerMode=true — we don’t need it here. + postProcess: async () => { + // nothing extra for chunk-csv + }, }; - process.once('SIGINT', onSigint); - - /** When detaching from a child, clear and immediately repaint the dashboard. */ - const onDetachScreen = (): void => { - process.stdout.write('\x1b[2J\x1b[H'); - repaint(); - }; - - /** - * When attaching to a child, clear and show a small banner. - * - * @param id - Worker ID to attach to. - */ - const onAttachScreen = (id: number): void => { - process.stdout.write('\x1b[2J\x1b[H'); - process.stdout.write( - `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT • Ctrl+D EOF)\n`, - ); - }; - - // Register the key-driven interactive switcher (0–9 attach, Tab cycle, etc). - let cleanupSwitcher: () => void = () => { - // No-op by default; will be set by installInteractiveSwitcher below. - }; - cleanupSwitcher = installInteractiveSwitcher({ - workers, - onAttach: onAttachScreen, - onDetach: onDetachScreen, - onCtrlC: onSigint, - getLogPaths: (id) => slotLogs.get(id), - replayBytes: 200 * 1024, // Tail ~200KB on attach so you see recent history - replayWhich: ['out', 'err'], // Replay stdout then stderr - onEnterAttachScreen: onAttachScreen, - }); - - /* 6) Initial paint + periodic refresh (coalesced with on-event repaints) */ - repaint(false); // single initial frame (prevents “double frame” burst) - tick = setInterval(() => repaint(false), 350); - /* 7) Wait for completion: queue empty + all workers exited */ - await new Promise((resolve) => { - const check = setInterval(() => { - if (pending.length === 0 && activeWorkers === 0) { - clearInterval(check); - if (tick) clearInterval(tick); - repaint(true); // render final frame (restores cursor) - cleanupSwitcher(); // remove key handlers/TTY muting - resolve(); - } - }, 300); + /* 5) Launch the pool runner with our hooks and custom dashboard plugin. */ + await runPool({ + title: 'Chunk CSV', + baseDir: directory || outputDir || process.cwd(), + childFlag: CHILD_FLAG, + childModulePath: getCurrentModulePath(), + poolSize, + cpuCount, + filesTotal: files.length, + hooks, + viewerMode, + render: (input) => dashboardPlugin(input, chunkCsvPlugin), + extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => + createExtraKeyHandler({ + logsBySlot, + repaint, + setPaused, + }), }); } diff --git a/src/commands/admin/chunk-csv/test/index.ts b/src/commands/admin/chunk-csv/test/index.ts new file mode 100644 index 00000000..c6a53ae3 --- /dev/null +++ b/src/commands/admin/chunk-csv/test/index.ts @@ -0,0 +1 @@ +export * from '../ui/plugin'; diff --git a/src/commands/admin/chunk-csv/ui/buildFrameModel.ts b/src/commands/admin/chunk-csv/ui/buildFrameModel.ts deleted file mode 100644 index b14fcd94..00000000 --- a/src/commands/admin/chunk-csv/ui/buildFrameModel.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Input shape for rendering the dashboard and building the frame model. - * - * This represents a snapshot of the current state of the worker pool, - * file progress, and throughput statistics. - */ -export type RenderDashboardInput = { - /** Number of worker processes available in the pool. */ - poolSize: number; - /** Number of CPU cores detected on the host machine. */ - cpuCount: number; - /** Total number of files scheduled for processing. */ - filesTotal: number; - /** Number of files successfully completed so far. */ - filesCompleted: number; - /** Number of files that have failed processing. */ - filesFailed: number; - /** - * Current state of each worker in the pool. - * - Key: worker ID - * - Value: status for that worker - */ - workerState: Map< - number, - { - /** The file currently assigned to this worker, or null if idle. */ - file: string | null; - /** Whether the worker is actively processing a file. */ - busy: boolean; - /** The last log level seen for this worker (used for status coloring). */ - lastLevel: 'ok' | 'warn' | 'error'; - /** - * Progress details if the worker is processing a file. - * - `processed`: rows processed so far - * - `total`: optional total rows if known - */ - progress?: { processed: number; total?: number }; - } - >; - /** True if this is the final render after all processing is complete. */ - final: boolean; - /** - * Throughput metrics: - * - `successSoFar`: total rows successfully processed across all workers - * - `r10s`: rolling rows/sec average over the last 10s - * - `r60s`: rolling rows/sec average over the last 60s - */ - throughput: { successSoFar: number; r10s: number; r60s: number }; - /** Optional directory where export artifacts are being written. */ - exportsDir?: string; - /** Optional object containing export status details for the dashboard. */ - exportStatus?: unknown; -}; - -/** - * Normalized structure returned by `buildFrameModel`. - * This includes all original input fields plus a `workers` array - * derived from the `workerState` map. - */ -export type FrameModel = RenderDashboardInput & { - workers: Array<{ - /** Worker ID (key from workerState map). */ - id: number; - /** File currently being processed, or null if idle. */ - file: string | null; - /** Whether the worker is actively processing a file. */ - busy: boolean; - /** Last reported log level for this worker. */ - level: 'ok' | 'warn' | 'error'; - /** Number of rows processed so far. */ - processed: number; - /** Total rows expected, if known. */ - total?: number; - }>; -}; - -/** - * Build a normalized "frame model" for dashboard rendering. - * - * This transforms the raw `RenderDashboardInput` into a structure that includes - * a derived `workers` array for easy iteration in UI components. - * - * @param input - The current snapshot of dashboard state. - * @returns A `FrameModel` containing the normalized dashboard data. - */ -export function buildFrameModel(input: RenderDashboardInput): FrameModel { - // Flatten workerState map into an array of worker summaries. - const workers = [...input.workerState.entries()].map(([id, st]) => ({ - id, - file: st.file, - busy: st.busy, - level: st.lastLevel, - processed: st.progress?.processed ?? 0, - total: st.progress?.total, - })); - - return { ...input, workers }; -} diff --git a/src/commands/admin/chunk-csv/ui/index.ts b/src/commands/admin/chunk-csv/ui/index.ts new file mode 100644 index 00000000..1110b645 --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/index.ts @@ -0,0 +1 @@ +export * from './plugin'; diff --git a/src/commands/admin/chunk-csv/ui/plugin.ts b/src/commands/admin/chunk-csv/ui/plugin.ts new file mode 100644 index 00000000..e1196e49 --- /dev/null +++ b/src/commands/admin/chunk-csv/ui/plugin.ts @@ -0,0 +1,38 @@ +import { + makeHeader, + makeWorkerRows, + type ChunkSlotProgress, + type CommonCtx, + type DashboardPlugin, +} from '../../../../lib/pooling'; + +/** + * Header for chunk-csv (no extra totals block). + * + * @param ctx - Dashboard context. + * @returns Header lines. + */ +function renderHeader( + ctx: CommonCtx, +): string[] { + // no extra lines — reuse the shared header as-is + return makeHeader(ctx); +} + +/** + * Worker rows for chunk-csv — share the generic row renderer. + * + * @param ctx - Dashboard context. + * @returns Array of strings, each representing one worker row. + */ +function renderWorkers( + ctx: CommonCtx, +): string[] { + return makeWorkerRows(ctx); +} + +export const chunkCsvPlugin: DashboardPlugin = { + renderHeader, + renderWorkers, + // no extras +}; diff --git a/src/commands/admin/chunk-csv/ui/renderDashboard.ts b/src/commands/admin/chunk-csv/ui/renderDashboard.ts deleted file mode 100644 index b1102c92..00000000 --- a/src/commands/admin/chunk-csv/ui/renderDashboard.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as readline from 'node:readline'; -import { buildFrameModel, type RenderDashboardInput } from './buildFrameModel'; - -let lastFrame = ''; -let frameTick = 0; - -/** Simple unicode spinner for BUSY workers when total is unknown. */ -const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - -/** Left/right caps for bars; use ASCII if you prefer. */ -const BAR_LEFT = '│'; -const BAR_RIGHT = '│'; -const BAR_FULL = '█'; -const BAR_EMPTY = '░'; - -/** - * Render a flicker-free, text-based dashboard in the terminal (TTY) - * for monitoring CSV chunking workers. - * - * What you see: - * - Overall progress (completed / failed / total) - * - Pool/CPU metrics - * - Recent throughput (r10s / r60s) - * - Each worker: BUSY/IDLE (+WARN/ERROR), rows processed, file path - * - A progress bar per worker (if total known), or a spinner if total unknown - * - * Hotkeys (handled by the interactive switcher): - * 0–9 attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C quit - * - * @param input - Input object containing state and metrics to render - */ -export function renderDashboard(input: RenderDashboardInput): void { - frameTick = (frameTick + 1) % SPIN.length; - const m = buildFrameModel(input); - - // header - const head = - `Chunk CSV — ${m.filesCompleted}/${m.filesTotal} done, failed ${m.filesFailed}\n` + - `Pool ${m.poolSize} • CPU ${m.cpuCount} • r10s ${m.throughput.r10s.toFixed( - 1, - )} ` + - `r60s ${m.throughput.r60s.toFixed(1)}\n` + - '(hotkeys: 0–9 attach • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C quit)'; - - // per-worker line with progress bar / spinner - const rows = m.workers - .map((w) => { - const status = w.busy ? 'BUSY' : 'IDLE'; - const lvl = - w.level === 'ok' ? '' : w.level === 'warn' ? ' [WARN]' : ' [ERROR]'; - - const processed = w.processed ?? 0; - const total = w.total ?? undefined; - - let progressVisual = ''; - if (w.busy && typeof total === 'number' && total > 0) { - const pct = Math.max(0, Math.min(1, processed / total)); - const width = 28; // bar width - const filled = Math.round(pct * width); - const empty = width - filled; - progressVisual = `${BAR_LEFT}${BAR_FULL.repeat( - filled, - )}${BAR_EMPTY.repeat(empty)}${BAR_RIGHT} (${Math.floor(pct * 100)}%)`; - } else if (w.busy) { - progressVisual = `${SPIN[frameTick]} processing`; - } else { - progressVisual = '—'; - } - - const prog = `rows=${processed.toLocaleString()} ${progressVisual}`; - const file = w.file ? ` — ${w.file}` : ''; - - return `w${w.id - .toString() - .padStart(2, '0')}: ${status}${lvl} ${prog}${file}`; - }) - .join('\n'); - - const frame = `${head}\n\n${rows}\n`; - - if (!input.final && frame === lastFrame) return; - lastFrame = frame; - - if (!input.final) { - process.stdout.write('\x1b[?25l'); - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); - } else { - process.stdout.write('\x1b[?25h'); - } - process.stdout.write(`${frame}\n`); -} diff --git a/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts b/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts deleted file mode 100644 index 6fcbd7bd..00000000 --- a/src/commands/admin/chunk-csv/ui/tests/buildFrameModel.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -// buildFrameModel.test.ts -import { describe, it, expect } from 'vitest'; - -// Adjust the import path to match your repo layout: -// If this test lives in `src/commands/chunk-csv/ui/tests/`, then: -import { buildFrameModel, type RenderDashboardInput } from '../buildFrameModel'; - -function makeInput( - overrides: Partial = {}, -): RenderDashboardInput { - const workerState = new Map< - number, - { - file: string | null; - busy: boolean; - lastLevel: 'ok' | 'warn' | 'error'; - progress?: { processed: number; total?: number }; - } - >(); - - return { - poolSize: 3, - cpuCount: 8, - filesTotal: 10, - filesCompleted: 4, - filesFailed: 1, - workerState, - final: false, - throughput: { successSoFar: 1234, r10s: 12.3, r60s: 8.9 }, - exportsDir: '/tmp/exports', - exportStatus: { info: true }, - ...overrides, - }; -} - -describe('buildFrameModel', () => { - it('converts workerState map into a workers array with expected fields', () => { - const input = makeInput(); - input.workerState.set(0, { - file: '/data/a.csv', - busy: true, - lastLevel: 'ok', - progress: { processed: 100, total: 200 }, - }); - input.workerState.set(1, { - file: null, - busy: false, - lastLevel: 'warn', - // no progress - }); - - const model = buildFrameModel(input); - - // workers array length matches map size - expect(model.workers).toHaveLength(2); - - // preserves insertion order (Map is ordered) - expect(model.workers[0]).toMatchObject({ - id: 0, - file: '/data/a.csv', - busy: true, - level: 'ok', - processed: 100, - total: 200, - }); - - // defaults processed to 0 when no progress is present - expect(model.workers[1]).toMatchObject({ - id: 1, - file: null, - busy: false, - level: 'warn', - processed: 0, - total: undefined, - }); - }); - - it('passes through top-level fields unchanged', () => { - const input = makeInput({ - poolSize: 5, - cpuCount: 16, - filesTotal: 42, - filesCompleted: 21, - filesFailed: 2, - final: true, - throughput: { successSoFar: 999, r10s: 1.23, r60s: 0.45 }, - exportsDir: '/var/logs', - exportStatus: { done: true }, - }); - - const model = buildFrameModel(input); - - expect(model.poolSize).toBe(5); - expect(model.cpuCount).toBe(16); - expect(model.filesTotal).toBe(42); - expect(model.filesCompleted).toBe(21); - expect(model.filesFailed).toBe(2); - expect(model.final).toBe(true); - expect(model.throughput).toEqual({ - successSoFar: 999, - r10s: 1.23, - r60s: 0.45, - }); - expect(model.exportsDir).toBe('/var/logs'); - expect(model.exportStatus).toEqual({ done: true }); - }); - - it('does not mutate the input workerState', () => { - const input = makeInput(); - input.workerState.set(3, { - file: '/x.csv', - busy: true, - lastLevel: 'error', - progress: { processed: 7 }, - }); - - const before = JSON.stringify( - [...input.workerState.entries()].map(([id, st]) => [id, { ...st }]), - ); - - const model = buildFrameModel(input); - expect(model.workers[0]).toMatchObject({ - id: 3, - file: '/x.csv', - busy: true, - level: 'error', - processed: 7, - total: undefined, - }); - - const after = JSON.stringify( - [...input.workerState.entries()].map(([id, st]) => [id, { ...st }]), - ); - expect(after).toBe(before); // unchanged - }); - - it('handles empty workerState', () => { - const input = makeInput(); // workerState empty by default - const model = buildFrameModel(input); - expect(model.workers).toEqual([]); - }); -}); diff --git a/src/commands/admin/chunk-csv/ui/tests/plugin.test.ts b/src/commands/admin/chunk-csv/ui/tests/plugin.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts b/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts deleted file mode 100644 index 2a498745..00000000 --- a/src/commands/admin/chunk-csv/ui/tests/renderDashboard.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -// renderDashboard.test.ts -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; - -// Import SUT AFTER mocks -import { renderDashboard } from '../renderDashboard'; -import type { RenderDashboardInput } from '../buildFrameModel'; - -// Mock readline helpers used by renderDashboard -const H = vi.hoisted(() => ({ - cursorTo: vi.fn(), - clearScreenDown: vi.fn(), -})); - -vi.mock('node:readline', () => ({ - cursorTo: H.cursorTo, - clearScreenDown: H.clearScreenDown, -})); - -function makeInput( - overrides: Partial = {}, -): RenderDashboardInput { - const workerState = new Map< - number, - { - file: string | null; - busy: boolean; - lastLevel: 'ok' | 'warn' | 'error'; - progress?: { processed: number; total?: number }; - } - >(); - - // Two workers: one busy, one idle - workerState.set(0, { - file: '/data/a.csv', - busy: true, - lastLevel: 'ok', - progress: { processed: 123 }, - }); - workerState.set(1, { - file: null, - busy: false, - lastLevel: 'warn', - }); - - return { - poolSize: 2, - cpuCount: 8, - filesTotal: 5, - filesCompleted: 1, - filesFailed: 0, - workerState, - final: false, - throughput: { successSoFar: 123, r10s: 1.23, r60s: 4.56 }, - exportsDir: '/tmp/out', - exportStatus: undefined, - ...overrides, - }; -} - -let writeSpy: ReturnType; - -beforeEach(() => { - writeSpy = vi - .spyOn(process.stdout, 'write') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => true) as any; - vi.clearAllMocks(); -}); - -afterEach(() => { - writeSpy.mockRestore(); - vi.restoreAllMocks(); -}); - -describe('renderDashboard', () => { - it('renders an initial frame (hides cursor, clears screen, prints content)', () => { - const input = makeInput(); - - renderDashboard(input); - - // Should hide cursor and clear screen for live updates - expect(writeSpy).toHaveBeenCalledWith('\x1b[?25l'); - expect(H.cursorTo).toHaveBeenCalledWith(process.stdout, 0, 0); - expect(H.clearScreenDown).toHaveBeenCalledWith(process.stdout); - - // Should print a frame containing header and both workers - const calls = writeSpy.mock.calls.map((c) => String(c[0])); - const bigOut = calls.join(''); - - expect(bigOut).toContain('Chunk CSV — 1/5 done, failed 0'); - // r10s/r60s formatted to 1 decimal - expect(bigOut).toContain('r10s 1.2'); - expect(bigOut).toContain('r60s 4.6'); - - // Worker rows - // w00 BUSY on /data/a.csv with rows=123 - expect(bigOut).toMatch( - /w00: BUSY(?: \[WARN\])?\srows=123 — \/data\/a\.csv/, - ); - // w01 IDLE with warn flag and rows=0 - expect(bigOut).toContain('w01: IDLE [WARN] rows=0'); - }); - - it('skips redraw when frame is unchanged and final=false', () => { - const input = makeInput(); - - // First render — draws - renderDashboard(input); - - // Reset spies to measure only the second render - writeSpy.mockClear(); - H.cursorTo.mockClear(); - H.clearScreenDown.mockClear(); - - // Second render with the same input — should NO-OP - renderDashboard(input); - - expect(writeSpy).not.toHaveBeenCalled(); - expect(H.cursorTo).not.toHaveBeenCalled(); - expect(H.clearScreenDown).not.toHaveBeenCalled(); - }); - - it('final render shows cursor and still prints the frame (no clear)', () => { - const input = makeInput({ final: true }); - - renderDashboard(input); - - // Should show cursor on final frame - expect(writeSpy).toHaveBeenCalledWith('\x1b[?25h'); - - // Should NOT clear screen on final frame - expect(H.cursorTo).not.toHaveBeenCalled(); - expect(H.clearScreenDown).not.toHaveBeenCalled(); - - // Should print a frame - const calls = writeSpy.mock.calls.map((c) => String(c[0])); - const bigOut = calls.join(''); - expect(bigOut).toContain('Chunk CSV — 1/5 done, failed 0'); - }); -}); diff --git a/src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts b/src/commands/consent/upload-preferences/artifacts/receipts/applyReceiptSummary.ts similarity index 97% rename from src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/applyReceiptSummary.ts index 10dd07db..3a267c74 100644 --- a/src/commands/consent/upload-preferences/receipts/applyReceiptSummary.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/applyReceiptSummary.ts @@ -1,4 +1,4 @@ -import type { AnyTotals } from '../ui/buildFrameModel'; +import type { AnyTotals } from '../../ui'; import { readFailingUpdatesFromReceipt } from './readFailingUpdatesFromReceipt'; import { resolveReceiptPath } from './resolveReceiptPath'; import { summarizeReceipt } from './summarizeReceipt'; diff --git a/src/commands/consent/upload-preferences/receipts/index.ts b/src/commands/consent/upload-preferences/artifacts/receipts/index.ts similarity index 100% rename from src/commands/consent/upload-preferences/receipts/index.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/index.ts diff --git a/src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts b/src/commands/consent/upload-preferences/artifacts/receipts/readFailingUpdatesFromReceipt.ts similarity index 95% rename from src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/readFailingUpdatesFromReceipt.ts index 414ae593..ebdeaf1f 100644 --- a/src/commands/consent/upload-preferences/receipts/readFailingUpdatesFromReceipt.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/readFailingUpdatesFromReceipt.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'node:fs'; -import type { FailingUpdateRow } from '../artifacts'; +import type { FailingUpdateRow } from '..'; /** * Parse failing updates out of a receipts.json file. diff --git a/src/commands/consent/upload-preferences/receipts/receiptsState.ts b/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts similarity index 97% rename from src/commands/consent/upload-preferences/receipts/receiptsState.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts index fe61b5fb..2fdbd365 100644 --- a/src/commands/consent/upload-preferences/receipts/receiptsState.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts @@ -6,11 +6,11 @@ import { type PendingWithConflictPreferenceUpdates, type PreferenceUpdateMap, type SkippedPreferenceUpdates, -} from '../../../../lib/preference-management'; +} from '../../../../../lib/preference-management'; import { retrySamePromise, type RetryPolicy, -} from '../../../../lib/helpers/retrySamePromise'; +} from '../../../../../lib/helpers/retrySamePromise'; export type PreferenceReceiptsInterface = { /** Path to file */ diff --git a/src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts b/src/commands/consent/upload-preferences/artifacts/receipts/resolveReceiptPath.ts similarity index 95% rename from src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/resolveReceiptPath.ts index cfa234a5..478d48d2 100644 --- a/src/commands/consent/upload-preferences/receipts/resolveReceiptPath.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/resolveReceiptPath.ts @@ -1,5 +1,5 @@ import { join } from 'node:path'; -import { getFilePrefix } from '../computeFiles'; +import { getFilePrefix } from '../../computeFiles'; import { existsSync, readdirSync, statSync } from 'node:fs'; /** diff --git a/src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts b/src/commands/consent/upload-preferences/artifacts/receipts/summarizeReceipt.ts similarity index 97% rename from src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts rename to src/commands/consent/upload-preferences/artifacts/receipts/summarizeReceipt.ts index c69292f1..104d5b51 100644 --- a/src/commands/consent/upload-preferences/receipts/summarizeReceipt.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/summarizeReceipt.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'node:fs'; -import type { AnyTotals } from '../ui/buildFrameModel'; +import type { AnyTotals } from '../../ui'; /** * Summarize a receipts JSON into dashboard counters. diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/tests/applyReceiptSummary.test.ts b/src/commands/consent/upload-preferences/artifacts/receipts/tests/applyReceiptSummary.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/tests/readFailingUpdatesFromReceipt.test.ts b/src/commands/consent/upload-preferences/artifacts/receipts/tests/readFailingUpdatesFromReceipt.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/tests/receiptsState.test.ts b/src/commands/consent/upload-preferences/artifacts/receipts/tests/receiptsState.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/tests/resolveReceiptPath.test.ts b/src/commands/consent/upload-preferences/artifacts/receipts/tests/resolveReceiptPath.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/tests/summarizeReceipt.test.ts b/src/commands/consent/upload-preferences/artifacts/receipts/tests/summarizeReceipt.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index b327f043..d279fe48 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -157,6 +157,7 @@ export const uploadPreferencesCommand = buildCommand({ 'This is to avoid the receipt file getting too large for JSON.parse/stringify.', default: '10', }, + // FIXME allowedIdentifierNames: { kind: 'parsed', parse: (value: string) => value.split(',').map((s) => s.trim()), @@ -176,6 +177,13 @@ export const uploadPreferencesCommand = buildCommand({ 'Columns in the CSV that should be ignored. Comma-separated list of column names.', optional: true, }, + // FIXME + viewerMode: { + kind: 'boolean', + brief: + 'Run in non-interactive viewer mode (no attach UI, auto-artifacts)', + default: false, + }, }, }, docs: { @@ -192,6 +200,6 @@ Parallel preference uploader (Node 22+ ESM/TS) - Shows a live dashboard in the parent terminal with progress per worker. - Creates per-worker log files and (optionally) opens OS terminals to tail them. - Uses the same module as both parent and child; the child mode is toggled - by the presence of a CLI flag ('--child-upload-preferences').`, + by the presence of a CLI flag ('--as-child').`, }, }); diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 3896f649..00bb0789 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -1,4 +1,3 @@ -// impl.ts import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; @@ -6,51 +5,95 @@ import { join } from 'node:path'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; - -import { runChild } from './worker'; -import type { ChildProcess } from 'node:child_process'; +import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; import { computePoolSize, - getWorkerLogPaths, - isIpcOpen, - safeSend, - spawnWorkerProcess, - isWorkerProgressMessage, - isWorkerReadyMessage, - isWorkerResultMessage, - classifyLogLevel, - makeLineSplitter, - initLogDir, - buildExportStatus, - assignWorkToSlot, - refillIdleWorkers, - safeGetLogPathsForSlot, - WorkerLogPaths, - WorkerMaps, - WorkerState, - installInteractiveSwitcher, CHILD_FLAG, + type PoolHooks, + runPool, + dashboardPlugin, + buildExportStatus, } from '../../../lib/pooling'; -import { getCurrentModulePath, RateCounter } from '../../../lib/helpers'; -import { - renderDashboard, - AnyTotals, - isUploadModeTotals, - isCheckModeTotals, - makeOnKeypressExtra, -} from './ui'; + +import { runChild } from './worker'; import { - writeFailingUpdatesCsv, ExportManager, + writeFailingUpdatesCsv, type FailingUpdateRow, } from './artifacts'; - -import { applyReceiptSummary } from './receipts'; +import { applyReceiptSummary } from './artifacts/receipts'; import { buildCommonOpts } from './buildTaskOptions'; -import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; +import { + AnyTotals, + isUploadModeTotals, + isCheckModeTotals, + uploadPreferencesPlugin, +} from './ui'; +import { createExtraKeyHandler } from '../../../lib/pooling/extraKeys'; + +/** + * A unit of work: instructs a worker to upload (or check) a single CSV file. + */ +export type UploadPreferencesTask = { + /** Absolute path of the CSV file to process. */ + filePath: string; + /** Command/worker options shared across tasks (built from CLI flags). */ + options: ReturnType; +}; + +/** + * Per-worker progress snapshot emitted by the worker. + * This mirrors the previous IPC progress payload for this command. + */ +export type UploadPreferencesProgress = { + /** File currently being processed. */ + filePath: string; + /** New successes since the last progress message (used to compute rates). */ + successDelta?: number; + /** Cumulative successes so far for the current file. */ + successTotal?: number; + /** Optional total row count for the file (if known). */ + fileTotal?: number; +}; + +/** + * Final result for a single file. + */ +export type UploadPreferencesResult = { + /** Success flag for the file. */ + ok: boolean; + /** File this result pertains to. */ + filePath: string; + /** Optional path to the worker-generated receipt file. */ + receiptFilepath?: string; + /** Optional error string when `ok === false`. */ + error?: string; +}; + +/** + * Aggregate totals shown in the dashboard. + * This command supports two modes: + * - upload mode totals + * - check mode totals + * + * The union is already defined in `./ui` as `AnyTotals`. + */ +type Totals = AnyTotals; + +/** + * Returns the current module's path so the worker pool knows what file to re-exec. + * In Node ESM, __filename is undefined, so we fall back to argv[1]. + * + * @returns The current module's path as a string + */ +function getCurrentModulePath(): string { + if (typeof __filename !== 'undefined') { + return __filename as unknown as string; + } + return process.argv[1]; +} -/** CLI flags */ export interface UploadPreferencesCommandFlags { auth: string; partition: string; @@ -77,14 +120,34 @@ export interface UploadPreferencesCommandFlags { allowedIdentifierNames: string[]; identifierColumns: string[]; columnsToIgnore?: string[]; + viewerMode: boolean; } +/** + * Parent entrypoint for uploading/checking many preference CSVs in parallel. + * + * Flow: + * 1) Validate inputs & discover CSV files (exit if none). + * 2) Compute pool size from `--concurrency` or CPU heuristic. + * 3) Build `common` worker options and task queue (one task per file). + * 4) Define `PoolHooks` for task scheduling, progress, and results aggregation. + * 5) Launch the pool with `runPool`, rendering via `dashboardPlugin(uploadPreferencesPlugin)`. + * + * All log exporting / artifact work that used to be done in “viewer mode” can be handled + * in `postProcess` using the new log context from the runner. + * + * @param flags - CLI options for the run. + * @returns Promise that resolves when the pool completes. + */ export async function uploadPreferences( this: LocalContext, flags: UploadPreferencesCommandFlags, ): Promise { const { + auth, partition, + sombraAuth, + transcendUrl, directory, dryRun, skipExistingRecordCheck, @@ -92,8 +155,24 @@ export async function uploadPreferences( schemaFilePath, isSilent, concurrency, + attributes, + receiptFilepath, + uploadConcurrency, + maxChunkSize, + rateLimitRetryDelay, + uploadLogInterval, + downloadIdentifierConcurrency, + maxRecordsToReceipt, + allowedIdentifierNames, + identifierColumns, + columnsToIgnore, + skipWorkflowTriggers, + forceTriggerWorkflows, + skipConflictUpdates, + viewerMode, } = flags; + /* 1) Validate & find inputs */ const files = collectCsvFilesOrExit(directory, this); doneInputValidation(this.process.exit); @@ -102,371 +181,189 @@ export async function uploadPreferences( `Processing ${files.length} consent preferences files for partition: ${partition}`, ), ); - logger.debug(`Files to process:\n${files.join('\n')}`); + logger.debug( + `Files to process:\n${files.slice(0, 10).join('\n')}\n${ + files.length > 10 ? `... and ${files.length - 10} more` : '' + }`, + ); if (skipExistingRecordCheck) { - logger.info( - colors.bgYellow( - `Skipping existing record check: ${skipExistingRecordCheck}`, - ), - ); + logger.info(colors.bgYellow('Skipping existing record check: true')); } - // Throughput metering (records/s) - const meter = new RateCounter(); - let liveSuccessTotal = 0; - const receiptsFolder = computeReceiptsFolder(receiptFileDir, directory); const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); + /* 2) Pool size */ const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); - const common = buildCommonOpts(flags, schemaFile, receiptsFolder); - - // ---- Worker pool lifecycle ---- - const logDir = initLogDir(directory || receiptsFolder); - const modulePath = getCurrentModulePath(); - - const workers = new Map(); - const workerState = new Map(); - const slotLogPaths = new Map(); - const failingUpdatesMem: FailingUpdateRow[] = []; - - const pending = [...files]; - const totals = { completed: 0, failed: 0 }; - let activeWorkers = 0; - - const agg: AnyTotals = !common.dryRun - ? { - mode: 'upload', - success: 0, - skipped: 0, - error: 0, - errors: {} as Record, - } - : { - mode: 'check', - totalPending: 0, - pendingConflicts: 0, - pendingSafe: 0, - skipped: 0, - }; - - // Export status + manager - const exportStatus = buildExportStatus(logDir); - const exportMgr = new ExportManager(logDir); - - // Dashboard - let dashboardPaused = false; - const repaint = (final = false): void => { - if (dashboardPaused && !final) return; - renderDashboard({ - poolSize, - cpuCount, - filesTotal: files.length, - filesCompleted: totals.completed, - filesFailed: totals.failed, - workerState, - totals: agg, - final, - throughput: { - successSoFar: liveSuccessTotal, - r10s: meter.rate(10_000), - r60s: meter.rate(60_000), - }, - exportsDir: logDir, - exportStatus, - }); - }; - - const maps: WorkerMaps = { workers, workerState, slotLogPaths }; - const assign = (id: number): void => - assignWorkToSlot(id, pending, common, maps); - const refill = (): void => refillIdleWorkers(pending, maps, assign); - // Spawn pool - for (let i = 0; i < poolSize; i += 1) { - const child = spawnWorkerProcess({ - id: i, - modulePath, - logDir, - openLogWindows: true, + /* 3) Build shared worker options and queue */ + const common = buildCommonOpts( + { + ...flags, + // explicit for clarity (even if buildCommonOpts infers these): + auth, + partition, + sombraAuth, + transcendUrl, + dryRun, + skipExistingRecordCheck, + skipWorkflowTriggers, + forceTriggerWorkflows, + skipConflictUpdates, isSilent, - }); - workers.set(i, child); - workerState.set(i, { - busy: false, - file: null, - startedAt: null, - lastLevel: 'ok', - progress: undefined, - }); - slotLogPaths.set(i, getWorkerLogPaths(child)); - activeWorkers += 1; - - // Live WARN/ERROR status from stderr - const errLine = makeLineSplitter((line) => { - const lvl = classifyLogLevel(line); - if (!lvl) return; - const prev = workerState.get(i)!; - if (prev.lastLevel !== lvl) { - workerState.set(i, { ...prev, lastLevel: lvl }); - repaint(); - } - }); - child.stderr?.on('data', errLine); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - child.on('error', (err: any) => { - if ( - err?.code === 'ERR_IPC_CHANNEL_CLOSED' || - err?.code === 'EPIPE' || - err?.errno === -32 - ) { - return; // benign during shutdown/restarts - } - logger.error(colors.red(`Worker ${i} error: ${err?.stack || err}`)); - }); - - // eslint-disable-next-line no-loop-func - child.on('message', (msg: unknown): void => { - if (!msg || typeof msg !== 'object') return; - - if (isWorkerReadyMessage(msg)) { - refill(); - repaint(); - return; - } - - if (isWorkerProgressMessage(msg)) { - const { successDelta, successTotal, fileTotal, filePath } = - msg.payload || {}; - liveSuccessTotal += successDelta || 0; - - const prev = workerState.get(i)!; - const processed = successTotal ?? prev.progress?.processed ?? 0; - const total = fileTotal ?? prev.progress?.total ?? 0; - workerState.set(i, { - ...prev, - file: prev.file ?? filePath ?? prev.file, - progress: { processed, total }, - }); - - if (successDelta) meter.add(successDelta); - repaint(); - return; - } - - if (isWorkerResultMessage(msg)) { - const { ok, filePath, receiptFilepath } = msg.payload || {}; - if (ok) totals.completed += 1; - else totals.failed += 1; - - applyReceiptSummary({ - receiptsFolder: common.receiptsFolder, - filePath, - receiptFilepath, - agg, - dryRun, - failingUpdatesMem, - }); - - const prev = workerState.get(i)!; - workerState.set(i, { - ...prev, - busy: false, - file: null, - startedAt: null, - lastLevel: ok ? 'ok' : 'error', - progress: undefined, - }); + attributes, + receiptFilepath, + uploadConcurrency, + maxChunkSize, + rateLimitRetryDelay, + uploadLogInterval, + downloadIdentifierConcurrency, + maxRecordsToReceipt, + allowedIdentifierNames, + identifierColumns, + columnsToIgnore, + }, + schemaFile, + receiptsFolder, + ); - refill(); - repaint(); - } - }); + // FIFO queue: one task per file + const queue = files.map((filePath) => ({ + filePath, + options: common, + })); - // eslint-disable-next-line no-loop-func - child.on('exit', (code, signal) => { - activeWorkers -= 1; - const prev = workerState.get(i)!; - const abnormal = (typeof code === 'number' && code !== 0) || !!signal; + // Dashboard artifacts/export status (shown during renders) + // inside uploadPreferences() before runPool call: + const exportMgr = new ExportManager(receiptsFolder); + const exportStatus = buildExportStatus(receiptsFolder); + const failingUpdatesMem: FailingUpdateRow[] = []; - workerState.set(i, { - ...prev, - busy: false, - file: null, - startedAt: null, - lastLevel: abnormal ? 'error' : prev.lastLevel ?? 'ok', - progress: undefined, + /* 4) Hooks */ + const hooks: PoolHooks< + UploadPreferencesTask, + UploadPreferencesProgress, + UploadPreferencesResult, + Totals + > = { + nextTask: () => queue.shift(), + taskLabel: (t) => t.filePath, + initTotals: () => + !common.dryRun + ? ({ + mode: 'upload', + success: 0, + skipped: 0, + error: 0, + errors: {}, + } as Totals) + : ({ + mode: 'check', + totalPending: 0, + pendingConflicts: 0, + pendingSafe: 0, + skipped: 0, + } as Totals), + initSlotProgress: () => undefined, + onProgress: (totals) => totals, + onResult: (totals, res) => { + applyReceiptSummary({ + receiptsFolder: common.receiptsFolder, + filePath: res.filePath, + receiptFilepath: res.receiptFilepath, + agg: totals, + dryRun: common.dryRun, + failingUpdatesMem, }); - - repaint(); - }); - } - - const renderInterval = setInterval(() => repaint(false), 350); - - // graceful Ctrl+C - let cleanupSwitcher: () => void = () => { - // noop, will be replaced by installInteractiveSwitcher - }; - const onSigint = (): void => { - clearInterval(renderInterval); - cleanupSwitcher(); - process.stdout.write('\nStopping workers...\n'); - for (const [, w] of workers) { - if (isIpcOpen(w)) safeSend(w!, { type: 'shutdown' }); - try { - w?.kill('SIGTERM'); - } catch { - // noop - } - } - this.process.exit(130); - }; - process.once('SIGINT', onSigint); - - // attach/switch UI with replay - const detachScreen = (): void => { - dashboardPaused = false; - repaint(); - }; - const attachScreen = (id: number): void => { - dashboardPaused = true; - process.stdout.write('\x1b[2J\x1b[H'); - process.stdout.write( - `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+C SIGINT)\n`, - ); - }; - - cleanupSwitcher = installInteractiveSwitcher({ - workers, - onAttach: attachScreen, - onDetach: detachScreen, - onCtrlC: onSigint, - getLogPaths: (id: number) => - safeGetLogPathsForSlot(id, workers, slotLogPaths), - replayBytes: 200 * 1024, - replayWhich: ['out', 'err'], - onEnterAttachScreen: attachScreen, - }); - - // key handlers (viewer/export/dashboard) - const onKeypressExtra = makeOnKeypressExtra({ - slotLogPaths, - exportMgr, - exportStatus, - failingUpdates: failingUpdatesMem, - onRepaint: () => repaint(), - onPause: (p) => { - dashboardPaused = p; + return { totals, ok: !!res.ok }; }, - }); - - try { - process.stdin.setRawMode?.(true); - } catch { - // ignore if not supported (e.g. Windows) - } - process.stdin.resume(); - process.stdin.on('data', onKeypressExtra); - - // wait for completion of work (not viewer) - await new Promise((resolveWork) => { - const check = setInterval(async () => { - if (pending.length === 0) { - for (const [id, w] of workers) { - const st = workerState.get(id); - if (st && !st.busy && isIpcOpen(w)) { - safeSend(w!, { type: 'shutdown' }); - } - } - } - if (pending.length === 0 && activeWorkers === 0) { - clearInterval(check); - clearInterval(renderInterval); - - // Final “auto-export” of artifacts - try { - const e = exportMgr.exportCombinedLogs(slotLogPaths, 'error'); - exportStatus.error = { path: e, savedAt: Date.now(), exported: true }; - const w = exportMgr.exportCombinedLogs(slotLogPaths, 'warn'); - exportStatus.warn = { path: w, savedAt: Date.now(), exported: true }; - const i = exportMgr.exportCombinedLogs(slotLogPaths, 'info'); - exportStatus.info = { path: i, savedAt: Date.now(), exported: true }; - const a = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); - exportStatus.all = { path: a, savedAt: Date.now(), exported: true }; - const fPath = join(logDir, 'failing-updates.csv'); - await writeFailingUpdatesCsv(failingUpdatesMem, fPath); - exportStatus.failuresCsv = { - path: fPath, - savedAt: Date.now(), - exported: true, - }; - process.stdout.write( - `\nArtifacts:\n ${e}\n ${w}\n ${i}\n ${a}\n ${fPath}\n\n`, - ); - } catch (err) { - process.stdout.write( - colors.red(`Failed to download CSV:${err.stack}`), - ); - } - - // Final repaint with exportStatus visible & green - repaint(true); - - if (isUploadModeTotals(agg)) { - process.stdout.write( + exportStatus: () => exportStatus, + /** + * Finalization after all workers exit. + * With the new runner you also receive: + * - logDir + * - logsBySlot (Map) + * - startedAt / finishedAt + * - getLogPathsForSlot(id) + * - viewerMode (boolean) + * + * @param options - Options with logDir, logsBySlot, startedAt, finishedAt, etc. + */ + postProcess: async ({ totals, logDir /* , logsBySlot, viewerMode */ }) => { + try { + // Persist failing updates CSV next to receipts/logDir. + const fPath = join(logDir || receiptsFolder, 'failing-updates.csv'); + await writeFailingUpdatesCsv(failingUpdatesMem, fPath); + exportStatus.failuresCsv = { + path: fPath, + savedAt: Date.now(), + exported: true, + }; + + // (Optional) If you want to auto-export combined logs like the old viewer: + // - import and use ExportManager here, using `logsBySlot`. + // - e.g., exportManager.exportCombinedLogs(logsBySlot, 'error' | 'warn' | 'info' | 'all') + + // Summarize totals to stdout (parity with the old implementation) + if (isUploadModeTotals(totals)) { + logger.info( colors.green( - `\nAll done. Success:${agg.success.toLocaleString()} Skipped:${agg.skipped.toLocaleString()} Error:${agg.error.toLocaleString()}\n`, + `All done. Success:${totals.success.toLocaleString()} ` + + `Skipped:${totals.skipped.toLocaleString()} ` + + `Error:${totals.error.toLocaleString()}`, ), ); - } else if (isCheckModeTotals(agg)) { - process.stdout.write( + } else if (isCheckModeTotals(totals)) { + logger.info( colors.green( - `\nAll done. Pending:${agg.totalPending.toLocaleString()} PendingConflicts:${agg.pendingConflicts.toLocaleString()} ` + - `PendingSafe:${agg.pendingSafe.toLocaleString()} Skipped:${agg.skipped.toLocaleString()}\n`, + `All done. Pending:${totals.totalPending.toLocaleString()} ` + + `PendingConflicts:${totals.pendingConflicts.toLocaleString()} ` + + `PendingSafe:${totals.pendingSafe.toLocaleString()} ` + + `Skipped:${totals.skipped.toLocaleString()}`, ), ); - } else { - throw new Error( - `Unknown totals type, expected UploadModeTotals or CheckModeTotals. ${JSON.stringify( - agg, - )}`, - ); } - - process.stdout.write( - colors.dim( - '\nViewer mode — digits to view logs • Tab/Shift+Tab • Esc detach • press q to quit\n', - ), - ); - - resolveWork(); + } catch (err: unknown) { + logger.error(colors.red(`Failed to export artifacts: ${String(err)}`)); } - }, 300); - }); + }, + }; - // --- Viewer mode: leave switcher active until user presses 'q' --- - await new Promise((resolveViewer) => { - const onKeypress = (buf: Buffer): void => { - const s = buf.toString('utf8'); - if (s === 'q' || s === 'Q') { - process.stdin.off('data', onKeypress); - resolveViewer(); - } - }; - try { - process.stdin.setRawMode?.(true); - } catch { - // noop - } - process.stdin.resume(); - process.stdin.on('data', onKeypress); + /* 5) Launch the pool runner with our hooks and dashboard plugin. */ + await runPool< + UploadPreferencesTask, + UploadPreferencesProgress, + UploadPreferencesResult, + Totals + >({ + title: 'Upload Preferences', + baseDir: directory || receiptsFolder || process.cwd(), + childFlag: CHILD_FLAG, + childModulePath: getCurrentModulePath(), + poolSize, + cpuCount, + filesTotal: files.length, + hooks, + viewerMode, + render: (input) => dashboardPlugin(input, uploadPreferencesPlugin), + extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => + createExtraKeyHandler({ + logsBySlot, + repaint, + setPaused, + exportMgr, // enables E/W/I/A + exportStatus, // keeps the exports panel updated + custom: { + F: async ({ noteExport, say }) => { + const fPath = join(exportMgr.exportsDir, 'failing-updates.csv'); + await writeFailingUpdatesCsv(failingUpdatesMem, fPath); + say(`\nWrote failing updates CSV to: ${fPath}`); + noteExport('failuresCsv', fPath); + }, + }, + }), }); - - cleanupSwitcher(); - process.removeListener('SIGINT', onSigint); } /* ------------------------------------------------------------------------------------------------- diff --git a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts b/src/commands/consent/upload-preferences/ui/buildFrameModel.ts deleted file mode 100644 index 9d2aa5ad..00000000 --- a/src/commands/consent/upload-preferences/ui/buildFrameModel.ts +++ /dev/null @@ -1,215 +0,0 @@ -import type { ExportStatusMap, WorkerState } from '../../../../lib/pooling'; - -export type UploadModeTotals = { - /** The mode of the export, always 'upload' */ - mode: 'upload'; - /** Number of records successfully uploaded */ - success: number; - /** Number of records skipped */ - skipped: number; - /** Number of records failed */ - error: number; - /** Number of records that were not processed */ - errors: Record; -}; - -export type CheckModeTotals = { - /** The mode of the export, always 'check' */ - mode: 'check'; - /** Number of records pending */ - pendingConflicts: number; - /** Number of records pending safe */ - pendingSafe: number; - /** Number of records pending */ - totalPending: number; - /** Number of records skipped */ - skipped: number; -}; - -/** - * Type guard for UploadModeTotals - * - * @param totals - The totals object to check - * @returns True if the totals object is of type UploadModeTotals, false otherwise - */ -export function isUploadModeTotals( - totals: unknown, -): totals is UploadModeTotals { - return ( - typeof totals === 'object' && - totals !== null && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (totals as any).mode === 'upload' - ); -} - -/** - * Type guard for CheckModeTotals - * - * @param totals - The totals object to check - * @returns True if the totals object is of type CheckModeTotals, false otherwise - */ -export function isCheckModeTotals(totals: unknown): totals is CheckModeTotals { - return ( - typeof totals === 'object' && - totals !== null && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (totals as any).mode === 'check' - ); -} - -/** - * Represents the totals for either upload or check mode. - */ -export type AnyTotals = UploadModeTotals | CheckModeTotals; - -export interface RenderDashboardInput { - /** The size of the worker pool */ - poolSize: number; - /** The number of CPU cores available */ - cpuCount: number; - /** Total number of files to process */ - filesTotal: number; - /** Number of files that have been completed */ - filesCompleted: number; - /** Number of files that have failed */ - filesFailed: number; - /** Map of worker ID to WorkerState */ - workerState: Map; - /** Totals for the current operation */ - totals?: AnyTotals; - /** Throughput statistics */ - throughput?: { successSoFar: number; r10s: number; r60s: number }; - /** Whether this is the final render (e.g., for a summary) */ - final?: boolean; - /** Directory where export artifacts are stored */ - exportsDir?: string; - /** Status of the export artifacts */ - exportStatus?: ExportStatusMap; -} - -export type FrameModel = { - /** The input parameters for the dashboard */ - input: RenderDashboardInput; - /** Number of workers currently processing files */ - inProgress: number; - /** Total number of files that have been completed */ - completedFiles: number; - /** Percentage of completion */ - pct: number; - /** Estimated total jobs to be processed */ - estTotalJobs?: number; - /** Estimated time of arrival text */ - etaText: string; -}; - -/** - * Builds the frame model for the dashboard. - * This function calculates various statistics based on the input parameters - * and returns a FrameModel object that can be used to render the dashboard. - * - * - * @param input - The input parameters for the dashboard. - * @returns The frame model for the dashboard. - */ -export function buildFrameModel(input: RenderDashboardInput): FrameModel { - const { - filesTotal, - filesCompleted, - filesFailed, - workerState, - totals, - throughput, - } = input; - - const inProgress = [...workerState.values()].filter((s) => s.busy).length; - const completedFiles = filesCompleted + filesFailed; - const pct = - filesTotal === 0 - ? 100 - : Math.floor((completedFiles / Math.max(1, filesTotal)) * 100); - - // receipts-based estimation - const jobsFromReceipts = - totals && totals.mode === 'upload' - ? (totals as UploadModeTotals).success + - (totals as UploadModeTotals).skipped + - (totals as UploadModeTotals).error - : undefined; - - const inflightJobsKnown = [...workerState.values()].reduce((sum, s) => { - const t = s.progress?.total ?? 0; - return sum + (t > 0 && s.busy ? t : 0); - }, 0); - - const avgJobsPerCompletedFile = - jobsFromReceipts !== undefined && completedFiles > 0 - ? jobsFromReceipts / completedFiles - : undefined; - - const remainingFiles = Math.max(filesTotal - completedFiles - inProgress, 0); - - let estTotalJobs: number | undefined; - if (avgJobsPerCompletedFile !== undefined) { - estTotalJobs = - (jobsFromReceipts ?? 0) + - inflightJobsKnown + - remainingFiles * avgJobsPerCompletedFile; - } else if (inProgress > 0) { - const avgInFlight = - inflightJobsKnown > 0 ? inflightJobsKnown / inProgress : 0; - if (avgInFlight > 0) { - estTotalJobs = inflightJobsKnown + remainingFiles * avgInFlight; - } - } - - // ETA - let etaText = ''; - if (throughput && estTotalJobs !== undefined) { - // Prefer receipts totals; fall back to throughput.successSoFar if not available - const processedSoFar = - jobsFromReceipts !== undefined - ? jobsFromReceipts - : throughput.successSoFar; - - const remainingJobs = Math.max(estTotalJobs - processedSoFar, 0); - - // Pick stable per-second rate - const ratePerSec = throughput.r60s > 0 ? throughput.r60s : throughput.r10s; - const ratePerHour = ratePerSec * 3600; - - if (ratePerHour > 0 && remainingJobs > 0) { - // Formula: (estTotalJobs - (success + skipped + error)) / throughput per hour - const hoursLeft = remainingJobs / ratePerHour; - const secondsLeft = Math.max(1, Math.round(hoursLeft * 3600)); - const eta = new Date(Date.now() + secondsLeft * 1000); - - const days = Math.floor(secondsLeft / 86400); // 24 * 3600 - const hours = Math.floor((secondsLeft % 86400) / 3600); - const minutes = Math.floor((secondsLeft % 3600) / 60); - const seconds = secondsLeft % 60; - - let timeLeft = ''; - if (days > 0) { - timeLeft = `${days}d ${hours}h ${minutes}m`; - } else if (hours > 0) { - timeLeft = `${hours}h ${minutes}m`; - } else if (minutes > 0) { - timeLeft = `${minutes}m ${seconds}s`; - } else { - timeLeft = `${seconds}s`; - } - - etaText = `Expected completion: ${eta.toLocaleString()} (${timeLeft} left)`; - } - } - - return { - input, - inProgress, - completedFiles, - pct, - estTotalJobs, - etaText, - }; -} diff --git a/src/commands/consent/upload-preferences/ui/headerLines.ts b/src/commands/consent/upload-preferences/ui/headerLines.ts deleted file mode 100644 index b9abe68f..00000000 --- a/src/commands/consent/upload-preferences/ui/headerLines.ts +++ /dev/null @@ -1,227 +0,0 @@ -import colors from 'colors'; -import { basename, resolve, join } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import type { - CheckModeTotals, - FrameModel, - UploadModeTotals, -} from './buildFrameModel'; -import { osc8Link, ExportStatusMap } from '../../../../lib/pooling'; -import type { ExportArtifactStatus } from '../artifacts/artifactAbsPath'; - -const fmtNum = (n: number): string => n.toLocaleString(); -const fmtTime = (ts?: number): string => - ts ? new Date(ts).toLocaleTimeString() : '—'; - -/** - * Generates header lines for the dashboard. - * This includes the status of the upload, worker information, and throughput statistics. - * - * @param m - The frame model containing the dashboard input and statistics. - * @returns An array of strings representing the header lines. - */ -export function headerLines(m: FrameModel): string[] { - const { input, inProgress, pct, etaText } = m; - const { - poolSize, - cpuCount, - filesTotal, - filesCompleted, - filesFailed, - throughput, - exportsDir, - totals, - } = input; - - const redIf = (n: number, s: string): string => (n > 0 ? colors.red(s) : s); - const barWidth = 40; - const filled = Math.floor((pct / 100) * barWidth); - const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled); - - let estTotalJobsText = colors.dim('Est. total jobs: —'); - if (m.estTotalJobs !== undefined) { - estTotalJobsText = colors.dim( - `Est. total jobs: ${fmtNum(Math.round(m.estTotalJobs))}`, - ); - } - - const header = [ - `${colors.bold('Parallel uploader')} — ${poolSize} workers ${colors.dim( - `(CPU avail: ${cpuCount})`, - )}`, - `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim( - 'Completed', - )} ${fmtNum(filesCompleted)} ` + - `${colors.dim('Failed')} ${redIf( - filesFailed, - fmtNum(filesFailed), - )} ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, - `[${bar}] ${pct}% ${estTotalJobsText} ${ - etaText ? colors.magenta(etaText) : '' - }`, - ]; - if (exportsDir) header.push(colors.dim(`Exports dir: ${exportsDir}`)); - if (throughput) { - const perHour10 = Math.round(throughput.r10s * 3600); - const perHour60 = Math.round(throughput.r60s * 3600); - header.push( - colors.cyan( - `Throughput: ${fmtNum(perHour10)}/hr (1h: ${fmtNum( - perHour60, - )}/hr) Newly uploaded: ${fmtNum(throughput.successSoFar)}`, - ), - ); - } - if (totals) header.push(totalsBlock(totals)); - return header.filter(Boolean); -} - -/** - * Generates a block of text summarizing the upload or check mode totals. - * - * @param totals - The totals object containing upload or check mode totals. - * @returns A string representing the totals block. - */ -export function totalsBlock( - totals: UploadModeTotals | CheckModeTotals, -): string { - if (totals.mode === 'upload') { - const t = totals as UploadModeTotals; - const errorsList = Object.entries(t.errors || {}).map( - ([msg, count]) => - ` ${colors.red(`Count[${fmtNum(count)}]`)} ${colors.red(msg)}`, - ); - return [ - errorsList.length - ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` - : '', - `${colors.bold('Receipts totals')} — Success: ${fmtNum( - t.success, - )} Skipped: ${fmtNum(t.skipped)} Error: ${ - t.error ? colors.red(fmtNum(t.error)) : fmtNum(t.error) - }`, - ] - .filter(Boolean) - .join('\n\n'); - } - const t = totals as CheckModeTotals; - return ( - `${colors.bold('Receipts totals')} — Pending: ${fmtNum(t.totalPending)} ` + - `PendingConflicts: ${fmtNum(t.pendingConflicts)} PendingSafe: ${fmtNum( - t.pendingSafe, - )} ` + - `Skipped: ${fmtNum(t.skipped)}` - ); -} - -/** - * Generates worker lines for the dashboard. - * - * @param m - The frame model containing the dashboard input and statistics. - * @returns An array of strings representing the worker lines. - */ -export function workerLines(m: FrameModel): string[] { - const { workerState } = m.input; - const miniWidth = 18; - return [...workerState.entries()].map(([id, s]) => { - const badge = - s.lastLevel === 'error' - ? colors.red('ERROR ') - : s.lastLevel === 'warn' - ? colors.yellow('WARN ') - : s.busy - ? colors.green('WORKING') - : colors.dim('IDLE '); - const fname = s.file ? basename(s.file) : '-'; - const elapsed = s.startedAt - ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` - : '-'; - const processed = s.progress?.processed ?? 0; - const total = s.progress?.total ?? 0; - const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; - const ff = Math.floor((pctw / 100) * miniWidth); - const mini = - total > 0 - ? '█'.repeat(ff) + '░'.repeat(miniWidth - ff) - : ' '.repeat(miniWidth); - const miniTxt = - total > 0 - ? `${fmtNum(processed)}/${fmtNum(total)} (${pctw}%)` - : colors.dim('—'); - return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; - }); -} - -/** - * Generates a line for hotkeys and controls. - * This includes information on how to interact with the dashboard. - * - * @param poolSize - The size of the worker pool. - * @param final - Whether this is the final render (e.g., for a summary). - * @returns A string representing the hotkeys line. - */ -export function hotkeysLine(poolSize: number, final?: boolean): string { - const maxDigit = Math.min(poolSize - 1, 9); - const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; - const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; - return final - ? colors.dim( - 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', - ) - : colors.dim( - `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit`, - ); -} - -/** - * Generates a block of text summarizing the export artifacts. - * This includes links to open or copy the paths of the artifacts. - * - * @param exportsDir - The directory where export artifacts are stored. - * @param exportStatus - The status of the export artifacts. - * @returns A string representing the export block. - */ -export function exportBlock( - exportsDir: string | undefined, - exportStatus: ExportStatusMap, -): string { - const makeLine = ( - key: 'E' | 'W' | 'I' | 'A' | 'F', - label: string, - status?: ExportArtifactStatus, - fallback?: string, - ): string => { - const exported = !!status?.exported; - const raw = - status?.path || - (exportsDir - ? join(exportsDir, fallback ?? `${label.toLowerCase()}.log`) - : '(set exportsDir)'); - const abs = raw.startsWith('(') ? raw : resolve(raw); - const url = abs.startsWith('(') ? abs : pathToFileURL(abs).href; - const openText = exported ? colors.green('open') : colors.dim('open'); - const openLink = abs.startsWith('(') ? openText : osc8Link(abs, openText); - const time = fmtTime(status?.savedAt); - const dot = exported ? colors.green('●') : colors.dim('○'); - return `${dot} ${colors.bold(`${key}=export-${label}`)}: ${openLink} ${ - exported ? colors.green(abs) : colors.dim(abs) - } ${colors.dim(`(last saved: ${time})`)}\n ${colors.dim( - 'url:', - )} ${url}`; - }; - - return [ - colors.dim('Exports (Cmd/Ctrl-click “open” or copy the plain path):'), - ` ${makeLine('E', 'errors', exportStatus.error, 'combined-errors.log')}`, - ` ${makeLine('W', 'warns', exportStatus.warn, 'combined-warns.log')}`, - ` ${makeLine('I', 'info', exportStatus.info, 'combined-info.log')}`, - ` ${makeLine('A', 'all', exportStatus.all, 'combined-all.log')}`, - ` ${makeLine( - 'F', - 'failures-csv', - exportStatus.failuresCsv, - 'failing-updates.csv', - )}`, - colors.dim(' (Also written to exports.index.txt for easy copying.)'), - ].join('\n'); -} diff --git a/src/commands/consent/upload-preferences/ui/index.ts b/src/commands/consent/upload-preferences/ui/index.ts index c6c7b471..c4c2475b 100644 --- a/src/commands/consent/upload-preferences/ui/index.ts +++ b/src/commands/consent/upload-preferences/ui/index.ts @@ -1,4 +1,2 @@ -export * from './buildFrameModel'; -export * from './renderDashboard'; -export * from './headerLines'; -export * from './makeOnKeypressExtra'; +export * from './typeGuards'; +export * from './plugin'; diff --git a/src/commands/consent/upload-preferences/ui/makeOnKeypressExtra.ts b/src/commands/consent/upload-preferences/ui/makeOnKeypressExtra.ts deleted file mode 100644 index 9bf5e4ca..00000000 --- a/src/commands/consent/upload-preferences/ui/makeOnKeypressExtra.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { join } from 'node:path'; -import { - showCombinedLogs, - type ExportStatusMap, - type SlotPaths, -} from '../../../../lib/pooling'; -import { - writeFailingUpdatesCsv, - type ExportManager, - type FailingUpdateRow, -} from '../artifacts'; - -export interface MakeOnKeypressExtraArgs { - /** Rows that failed to update */ - failingUpdates: FailingUpdateRow[]; - /** Map of worker IDs to their log paths */ - slotLogPaths: SlotPaths; - /** Export manager for handling export operations */ - exportMgr: ExportManager; - /** Map of export statuses for different kinds of logs */ - exportStatus: ExportStatusMap; - /** Callback for repainting the UI */ - onRepaint: () => void; - /** Callback to pause the UI */ - onPause: (paused: boolean) => void; -} - -/** - * Handles keypress events for extra functionalities in the CLI. - * - * @param args - Configuration for the keypress handler. - * @returns A function that processes keypress events. - */ -export function makeOnKeypressExtra({ - slotLogPaths, - exportMgr, - failingUpdates, - exportStatus, - onRepaint, - onPause, -}: MakeOnKeypressExtraArgs): (buf: Buffer) => void { - const noteExport = (slot: keyof ExportStatusMap, p: string): void => { - const now = Date.now(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const current: any = exportStatus[slot] || { path: p }; - // eslint-disable-next-line no-param-reassign - exportStatus[slot] = { - path: p || current.path, - savedAt: now, - exported: true, - }; - onRepaint(); - }; - - const view = ( - sources: Array<'out' | 'err' | 'structured' | 'warn' | 'info'>, - level: 'error' | 'warn' | 'all', - ): void => { - onPause(true); - showCombinedLogs(slotLogPaths, sources, level); - }; - - return (buf: Buffer): void => { - const s = buf.toString('utf8'); - - // viewers (lowercase) - if (s === 'e') { - view(['err'], 'error'); - return; - } - if (s === 'w') { - view(['warn', 'err'], 'warn'); - return; - } - if (s === 'i') { - view(['info'], 'all'); - return; - } - if (s === 'l') { - view(['out', 'err', 'structured'], 'all'); - return; - } - - // exports (uppercase) - if (s === 'E') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'error'); - process.stdout.write(`\nWrote combined error logs to: ${p}\n`); - noteExport('error', p); - } catch { - process.stdout.write('\nFailed to write combined error logs\n'); - } - return; - } - if (s === 'W') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'warn'); - process.stdout.write(`\nWrote combined warn logs to: ${p}\n`); - noteExport('warn', p); - } catch { - process.stdout.write('\nFailed to write combined warn logs\n'); - } - return; - } - if (s === 'I') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'info'); - process.stdout.write(`\nWrote combined info logs to: ${p}\n`); - noteExport('info', p); - } catch { - process.stdout.write('\nFailed to write combined info logs\n'); - } - return; - } - if (s === 'A') { - try { - const p = exportMgr.exportCombinedLogs(slotLogPaths, 'all'); - process.stdout.write(`\nWrote combined ALL logs to: ${p}\n`); - noteExport('all', p); - } catch { - process.stdout.write('\nFailed to write combined ALL logs\n'); - } - return; - } - if (s === 'F') { - try { - const fPath = join(exportMgr.exportsDir, 'failing-updates.csv'); - writeFailingUpdatesCsv(failingUpdates, fPath); - process.stdout.write(`\nWrote failing updates CSV to: ${fPath}\n`); - noteExport('failuresCsv', fPath); - } catch (err) { - process.stdout.write( - `\nFailed to write failing updates CSV - ${err.stack}\n`, - ); - } - return; - } - - // back to dashboard - if (s === '\x1b' || s === '\x1d') { - onPause(false); - onRepaint(); - } - }; -} diff --git a/src/commands/consent/upload-preferences/ui/plugin.ts b/src/commands/consent/upload-preferences/ui/plugin.ts new file mode 100644 index 00000000..f6ba8783 --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/plugin.ts @@ -0,0 +1,324 @@ +import colors from 'colors'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { fmtNum, makeHeader, makeWorkerRows } from '../../../../lib/pooling'; +import type { CommonCtx, DashboardPlugin } from '../../../../lib/pooling'; +import { isUploadModeTotals, type AnyTotals } from './typeGuards'; + +/** + * Per-slot progress payload used by the upload-preferences dashboard. + * Workers may omit totals when unknown (e.g., streaming inputs). + */ +export type UploadPreferencesSlotProgress = { + /** Absolute file path for the slot’s current unit of work. */ + filePath?: string; + /** Records processed so far for this file (running count). */ + processed?: number; + /** Optional total records for the file (not always known). */ + total?: number; +}; + +/** + * Status for a single exported artifact (combined logs, CSV, etc.). + * Stored in the `exportStatus` map passed via {@link CommonCtx.exportStatus}. + */ +type ExportArtifactStatus = { + /** Absolute or relative path to the artifact on disk. */ + path?: string; + /** Unix millis of the last successful save for this artifact. */ + savedAt?: number; + /** Whether the artifact has been written at least once. */ + exported?: boolean; +}; + +/** Map of export slots (e.g., "error", "warn", "info", "all", "failuresCsv") to their status. */ +type ExportStatusMap = Record; + +/** + * Format a timestamp for display in the exports panel. + * + * @param ts - Unix epoch milliseconds. + * @returns Local time string or '—' if undefined. + */ +const fmtTime = (ts?: number): string => + ts ? new Date(ts).toLocaleTimeString() : '—'; + +/** + * Create an OSC-8 hyperlink escape sequence for compatible terminals. + * + * @param abs - Absolute filesystem path to link to. + * @param text - Link text to render. + * @returns An OSC-8 hyperlink string. + */ +const osc8 = (abs: string, text: string): string => + `\u001B]8;;${pathToFileURL(abs).href}\u0007${text}\u001B]8;;\u0007`; + +/** + * Build the multi-line “receipts totals” block that appears under the header. + * Upload mode shows success/skipped/error and an error breakdown; check mode + * shows pending counts. + * + * @param t - Union of upload/check totals. + * @returns A newline-joined string; empty string when no totals present. + */ +function totalsBlock(t?: AnyTotals): string { + if (!t) return ''; + + if (isUploadModeTotals(t)) { + const errorsList = Object.entries(t.errors || {}).map( + ([msg, count]) => + ` ${colors.red(`Count[${fmtNum(count)}]`)} ${colors.red(msg)}`, + ); + return [ + errorsList.length + ? `${colors.bold('Error breakdown:')}\n${errorsList.join('\n')}` + : '', + `${colors.bold('Receipts totals')} — Success: ${fmtNum( + t.success, + )} Skipped: ${fmtNum(t.skipped)} Error: ${ + t.error ? colors.red(fmtNum(t.error)) : fmtNum(t.error) + }`, + ] + .filter(Boolean) + .join('\n\n'); + } + + // Check mode + return ( + `${colors.bold('Receipts totals')} — Pending: ${fmtNum(t.totalPending)} ` + + `PendingConflicts: ${fmtNum(t.pendingConflicts)} ` + + `PendingSafe: ${fmtNum(t.pendingSafe)} ` + + `Skipped: ${fmtNum(t.skipped)}` + ); +} + +// --- helper: human ETA from seconds (keep yours if already present) +function fmtEta(seconds: number): string { + const s = Math.max(0, Math.round(seconds)); + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const sec = s % 60; + if (h > 0) return `${h}h ${m}m`; + if (m > 0) return `${m}m ${sec}s`; + return `${sec}s`; +} + +/** + * Compute avg jobs/file from receipts or inflight totals. + * + * @param ctx - Dashboard context with live totals and inflight state. + * @returns Average jobs per file or undefined if not computable. + */ +function avgJobsPerFile( + ctx: CommonCtx, +): number | undefined { + const completedFiles = ctx.filesCompleted + ctx.filesFailed; + const { totals } = ctx; + const jobsFromReceipts = isUploadModeTotals(totals) + ? totals.success + totals.skipped + totals.error + : undefined; + + if (jobsFromReceipts !== undefined && completedFiles > 0) { + return jobsFromReceipts / completedFiles; + } + + // fallback: infer from inflight known totals + const inProgress = [...ctx.workerState.values()].filter((s) => s.busy); + const inflightJobsKnown = inProgress.reduce((sum, s) => { + const t = s.progress?.total ?? 0; + return sum + (t > 0 ? t : 0); + }, 0); + if (inProgress.length > 0 && inflightJobsKnown > 0) { + return inflightJobsKnown / inProgress.length; + } + return undefined; +} + +/** + * Estimate total jobs = processed + inflightKnown + remainingFiles * avgJobsPerFile + * + * @param ctx - Dashboard context with live totals and throughput. + * @returns Estimated total jobs or undefined if not computable. + */ +function estimateTotalJobs( + ctx: CommonCtx, +): number | undefined { + const { totals } = ctx; + const completedFiles = ctx.filesCompleted + ctx.filesFailed; + + const jobsFromReceipts = isUploadModeTotals(totals) + ? totals.success + totals.skipped + totals.error + : undefined; + + const inProgress = [...ctx.workerState.values()].filter((s) => s.busy); + const inflightJobsKnown = inProgress.reduce((sum, s) => { + const t = s.progress?.total ?? 0; + return sum + (t > 0 ? t : 0); + }, 0); + + const avg = avgJobsPerFile(ctx); + const remainingFiles = Math.max( + 0, + ctx.filesTotal - completedFiles - inProgress.length, + ); + + if (avg !== undefined) { + const processedBaseline = jobsFromReceipts ?? completedFiles * avg; // when receipts missing, approximate + return processedBaseline + inflightJobsKnown + remainingFiles * avg; + } + return undefined; +} + +/** + * 1-hour *job* throughput = r60s (files/sec) × avgJobsPerFile × 3600. + * + * @param ctx - Dashboard context with live totals and throughput. + * @returns Estimated jobs per hour based on 1-hour throughput. + */ +function jobsPerHour1h( + ctx: CommonCtx, +): number { + const avg = avgJobsPerFile(ctx); + const filesPerSec1h = ctx.throughput.r60s || 0; + if (!avg || filesPerSec1h <= 0) return 0; + return filesPerSec1h * avg * 3600; +} + +/** + * Build the single-line “Est. total jobs + ETA” exactly per requested formula. + * + * @param ctx - Dashboard context with live totals and throughput. + * @returns A formatted string with estimated total jobs and ETA. + */ +function metricsLine( + ctx: CommonCtx, +): string { + const { totals } = ctx; + const est = estimateTotalJobs(ctx); + + const processedJobs = isUploadModeTotals(totals) + ? totals.success + totals.skipped + totals.error + : undefined; + + const estText = colors.dim( + `Est. total jobs: ${est !== undefined ? fmtNum(Math.round(est)) : '—'}`, + ); + + let eta = ''; + if (est !== undefined && processedJobs !== undefined) { + const jobsLeft = Math.max(0, est - processedJobs); + const perHour = jobsPerHour1h(ctx); // <-- exactly the “1h throughput” + if (perHour > 0 && jobsLeft > 0) { + const secs = Math.ceil((jobsLeft / perHour) * 3600); + eta = colors.magenta(`ETA ${fmtEta(secs)}`); + } + } + + return [estText, eta].filter(Boolean).join(' '); +} + +/** + * Render the dashboard header for the upload-preferences command. + * Delegates common header lines to {@link makeHeader} and injects the + * receipts totals block produced by {@link totalsBlock}. + * + * @param ctx - Dashboard context containing pool state and totals. + * @returns An array of header lines. + */ +function renderHeader( + ctx: CommonCtx, +): string[] { + const extras: string[] = [metricsLine(ctx)]; + const totals = totalsBlock(ctx.totals); + if (totals) extras.push(totals); + return makeHeader(ctx, extras); +} + +/** + * Render per-worker rows (one line per slot) using the common row builder. + * + * @param ctx - Dashboard context with live slot state. + * @returns Worker row strings. + */ +function renderWorkers( + ctx: CommonCtx, +): string[] { + return makeWorkerRows(ctx); +} + +/** + * Render the exports panel listing artifact links and last-saved timestamps. + * Keys E/W/I/A/F are shown with OSC-8 links when possible and dimmed when + * not yet exported. + * + * @param ctx - Dashboard context, optionally holding {@link ExportStatusMap}. + * @returns Extra lines to append below the hotkeys hint. + */ +function renderExtras( + ctx: CommonCtx, +): string[] { + const status = (ctx.exportStatus || {}) as ExportStatusMap; + + const line = ( + key: string, + label: string, + s?: ExportArtifactStatus, + ): string => { + const exported = !!s?.exported; + const raw = s?.path || '(not saved yet)'; + + const abs = + raw.startsWith('(') || raw.startsWith('file:') ? raw : resolve(raw); + + const openText = exported ? colors.green('open') : colors.dim('open'); + + // Build a file:// href (or keep sentinel/already-a-URL) + const href = abs.startsWith('(') + ? '' + : abs.startsWith('file:') + ? abs + : pathToFileURL(abs).href; + + // Clickable "open" points to the file:// URL (no link when sentinel) + const link = abs.startsWith('(') ? openText : osc8(href, openText); + + // Show ONLY the URL (no duplicate raw path) + const shown = abs.startsWith('(') + ? colors.dim(abs) + : exported + ? colors.green(href) + : colors.dim(href); + const dot = exported ? colors.green('●') : colors.dim('○'); + const time = fmtTime(s?.savedAt); + + return `${dot} ${colors.bold( + `${key}=export-${label}`, + )}: ${link} ${shown} ${colors.dim(`(last saved: ${time})`)}`; + }; + + return [ + colors.dim('Exports (Cmd/Ctrl-click “open” or copy the plain path):'), + ` ${line('E', 'errors', status.error)}`, + ` ${line('W', 'warns', status.warn)}`, + ` ${line('I', 'info', status.info)}`, + ` ${line('A', 'all', status.all)}`, + ` ${line('F', 'failures-csv', status.failuresCsv)}`, + colors.dim(' (Also written to exports.index.txt for easy copying.)'), + ]; +} + +/** + * Dashboard plugin for the upload-preferences command. + * + * - **Header:** common header + receipts totals block + * - **Workers:** compact per-slot progress rows + * - **Extras:** export artifacts panel (errors/warns/info/all/failures-csv) + */ +export const uploadPreferencesPlugin: DashboardPlugin< + AnyTotals, + UploadPreferencesSlotProgress +> = { + renderHeader, + renderWorkers, + renderExtras, +}; diff --git a/src/commands/consent/upload-preferences/ui/renderDashboard.ts b/src/commands/consent/upload-preferences/ui/renderDashboard.ts deleted file mode 100644 index 5ab210a2..00000000 --- a/src/commands/consent/upload-preferences/ui/renderDashboard.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as readline from 'node:readline'; -import { buildFrameModel, type RenderDashboardInput } from './buildFrameModel'; -import { - exportBlock, - headerLines, - hotkeysLine, - workerLines, -} from './headerLines'; -import { writeExportsIndex } from '../artifacts/writeExportsIndex'; - -let lastFrame = ''; - -/** - * Renders the dashboard for the upload preferences command. - * This function builds the frame model and writes the exports index. - * It then constructs the header, worker lines, hotkeys line, and export block, - * and outputs the complete frame to the console. - * - * @param input - The input parameters for rendering the dashboard. - */ -export function renderDashboard(input: RenderDashboardInput): void { - const m = buildFrameModel(input); - writeExportsIndex(input.exportsDir, input.exportStatus); - - const frame = [ - ...headerLines(m), - '', - ...workerLines(m), - '', - hotkeysLine(input.poolSize, input.final), - '', - exportBlock(input.exportsDir, input.exportStatus ?? {}), - ].join('\n'); - - if (!input.final && frame === lastFrame) return; - lastFrame = frame; - - if (!input.final) { - process.stdout.write('\x1b[?25l'); - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); - } else { - process.stdout.write('\x1b[?25h'); - } - process.stdout.write(`${frame}\n`); -} diff --git a/src/commands/consent/upload-preferences/ui/tests/buildFrameModel.test.ts b/src/commands/consent/upload-preferences/ui/tests/buildFrameModel.test.ts deleted file mode 100644 index 231061d5..00000000 --- a/src/commands/consent/upload-preferences/ui/tests/buildFrameModel.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; - -import { - buildFrameModel, - isUploadModeTotals, - isCheckModeTotals, - type UploadModeTotals, - type CheckModeTotals, - type RenderDashboardInput, -} from '../buildFrameModel'; -import type { WorkerState } from '../../../../../lib/pooling'; - -/* ----------------------------------------------------------------------------- - Local test-only types & helpers ------------------------------------------------------------------------------ */ - -/** - * Minimal shape used by buildFrameModel at runtime for a worker. - * We keep it local to avoid importing runtime-only types from external modules. - */ -type TWorkerState = { - /** Whether the worker is currently processing */ - busy: boolean; - /** Optional progress data */ - progress?: { - /** Total planned records for the current file */ - total?: number; - /** Cumulative successes for the current file (not required by SUT) */ - successTotal?: number; - /** File total (not required by SUT) */ - fileTotal?: number; - }; -}; - -/** - * Build a Map of worker states keyed by worker id. - * - * @param entries - Worker id to state tuples - * @returns A Map of worker states - */ -function workerMap( - entries: Array<[number, TWorkerState]>, -): Map { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return new Map(entries as any); -} - -/** - * Construct a baseline RenderDashboardInput with overridable fields. - * - * @param overrides - Partial overrides - * @returns A RenderDashboardInput - */ -function makeInput( - overrides: Partial = {}, -): RenderDashboardInput { - return { - poolSize: 4, - cpuCount: 8, - filesTotal: 0, - filesCompleted: 0, - filesFailed: 0, - // Cast to the SUT's expected Map to avoid leaking test-only types - workerState: workerMap([]), - ...overrides, - } as RenderDashboardInput; -} - -/* ----------------------------------------------------------------------------- - Type guards ------------------------------------------------------------------------------ */ - -describe('type guards', () => { - it('isUploadModeTotals returns true only for upload mode', () => { - const good: UploadModeTotals = { - mode: 'upload', - success: 1, - skipped: 2, - error: 3, - errors: {}, - }; - expect(isUploadModeTotals(good)).toBe(true); - - const badMode: unknown = { mode: 'check' }; - expect(isUploadModeTotals(badMode)).toBe(false); - - expect(isUploadModeTotals(null)).toBe(false); - expect(isUploadModeTotals(undefined)).toBe(false); - expect(isUploadModeTotals({})).toBe(false); - }); - - it('isCheckModeTotals returns true only for check mode', () => { - const good: CheckModeTotals = { - mode: 'check', - pendingConflicts: 1, - pendingSafe: 2, - totalPending: 3, - skipped: 4, - }; - expect(isCheckModeTotals(good)).toBe(true); - - const badMode: unknown = { mode: 'upload' }; - expect(isCheckModeTotals(badMode)).toBe(false); - - expect(isCheckModeTotals(null)).toBe(false); - expect(isCheckModeTotals(undefined)).toBe(false); - expect(isCheckModeTotals({})).toBe(false); - }); -}); - -/* ----------------------------------------------------------------------------- - buildFrameModel — core stats ------------------------------------------------------------------------------ */ - -describe('buildFrameModel — core stats', () => { - it('computes inProgress, completedFiles, pct (normal case)', () => { - const wm = workerMap([ - [1, { busy: true, progress: { total: 20 } }], - [2, { busy: false }], - [3, { busy: true, progress: { total: 5 } }], - ]); - - const input: RenderDashboardInput = makeInput({ - filesTotal: 10, - filesCompleted: 3, - filesFailed: 2, - workerState: wm as Map, - }); - - const fm = buildFrameModel(input); - - expect(fm.inProgress).toBe(2); - expect(fm.completedFiles).toBe(5); - expect(fm.pct).toBe(50); - }); - - it('pct is 100 when filesTotal is 0 (avoid div by zero)', () => { - const input: RenderDashboardInput = makeInput({ - filesTotal: 0, - filesCompleted: 0, - filesFailed: 0, - workerState: workerMap([]) as Map, - }); - - const fm = buildFrameModel(input); - expect(fm.pct).toBe(100); - }); -}); - -/* ----------------------------------------------------------------------------- - buildFrameModel — estTotalJobs with receipts path ------------------------------------------------------------------------------ */ - -describe('buildFrameModel — estTotalJobs (receipts-based)', () => { - it('uses receipts + inflight + remainingFiles * avg-per-completed', () => { - // jobsFromReceipts = 115 - const totals: UploadModeTotals = { - mode: 'upload', - success: 100, - skipped: 10, - error: 5, - errors: {}, - }; - - // completedFiles = 3 + 2 = 5 - // inProgress = 2, inflightJobsKnown = 20 + 30 = 50 - // remainingFiles = 10 - 5 - 2 = 3 - // avgJobsPerCompletedFile = 115 / 5 = 23 - // estTotalJobs = 115 + 50 + (3 * 23) = 234 - const wm = workerMap([ - [1, { busy: true, progress: { total: 20 } }], - [2, { busy: true, progress: { total: 30 } }], - [3, { busy: false }], - ]); - - const input: RenderDashboardInput = makeInput({ - filesTotal: 10, - filesCompleted: 3, - filesFailed: 2, - workerState: wm, - totals, - }); - - const fm = buildFrameModel(input); - expect(fm.estTotalJobs).toBe(234); - }); -}); - -/* ----------------------------------------------------------------------------- - buildFrameModel — ETA string (minutes/seconds branch) ------------------------------------------------------------------------------ */ - -describe('buildFrameModel — ETA text', () => { - let restoreTime: () => void; - - beforeEach((): void => { - const fixed = new Date('2025-01-01T12:00:00.000Z'); - vi.useFakeTimers(); - vi.setSystemTime(fixed); - restoreTime = (): void => { - vi.useRealTimers(); - }; - }); - - afterEach((): void => { - restoreTime(); - }); - - it('formats ETA when r60s > 0 (prefers 60s rate) and remainingJobs > 0', () => { - // From prior math: estTotalJobs = 234, jobsFromReceipts = 115 -> remainingJobs = 119 - // r60s = 1 job/sec -> 3600 jobs/hour -> ~119 seconds => "1m 59s" - const totals: UploadModeTotals = { - mode: 'upload', - success: 100, - skipped: 10, - error: 5, - errors: {}, - }; - - const wm = workerMap([ - [1, { busy: true, progress: { total: 20 } }], - [2, { busy: true, progress: { total: 30 } }], - [3, { busy: false }], - ]); - - const input: RenderDashboardInput = makeInput({ - filesTotal: 10, - filesCompleted: 3, - filesFailed: 2, - workerState: wm, - totals, - throughput: { successSoFar: 9999, r10s: 0.5, r60s: 1 }, - }); - - const fm = buildFrameModel(input); - - expect(fm.estTotalJobs).toBe(234); - expect(fm.etaText).toMatch(/^Expected completion: .+ \(1m 59s left\)$/); - }); - - it('omits ETA when throughput is missing or remaining is zero', () => { - const totals: UploadModeTotals = { - mode: 'upload', - success: 10, - skipped: 0, - error: 0, - errors: {}, - }; - - const inputNoThroughput: RenderDashboardInput = makeInput({ - filesTotal: 1, - filesCompleted: 0, - filesFailed: 0, - workerState: workerMap([[1, { busy: true, progress: { total: 10 } }]]), - totals, - }); - - const fm1 = buildFrameModel(inputNoThroughput); - expect(fm1.etaText).toBe(''); - - const inputZeroRemaining: RenderDashboardInput = makeInput({ - filesTotal: 1, - filesCompleted: 1, - filesFailed: 0, - workerState: workerMap([]), - totals, - throughput: { successSoFar: 10, r10s: 1, r60s: 1 }, - }); - - const fm2 = buildFrameModel(inputZeroRemaining); - expect(fm2.etaText).toBe(''); - }); -}); - -/* ----------------------------------------------------------------------------- - buildFrameModel — estTotalJobs without receipts (in-flight only path) ------------------------------------------------------------------------------ */ - -describe('buildFrameModel — estTotalJobs (in-flight path, no receipts)', () => { - it('falls back to in-flight average when no receipts are available', () => { - // inProgress = 2, inflightKnown = 12 + 38 = 50 - // remainingFiles = 7 - (2 completed + 1 failed) - 2 inProgress = 2 - // avgInFlight = 50 / 2 = 25 - // est = 50 + 2 * 25 = 100 - const wm = workerMap([ - [1, { busy: true, progress: { total: 12 } }], - [2, { busy: true, progress: { total: 38 } }], - [3, { busy: false }], - ]); - - const input: RenderDashboardInput = makeInput({ - filesTotal: 7, - filesCompleted: 2, - filesFailed: 1, - workerState: wm, - totals: undefined, - throughput: { successSoFar: 0, r10s: 0.8, r60s: 0 }, - }); - - const fm = buildFrameModel(input); - expect(fm.estTotalJobs).toBe(100); - }); -}); diff --git a/src/commands/consent/upload-preferences/ui/tests/headerLines.test.ts b/src/commands/consent/upload-preferences/ui/tests/headerLines.test.ts deleted file mode 100644 index 9888c0b0..00000000 --- a/src/commands/consent/upload-preferences/ui/tests/headerLines.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { pathToFileURL } from 'node:url'; - -import { - headerLines, - totalsBlock, - workerLines, - hotkeysLine, - exportBlock, -} from '../headerLines'; - -const H = vi.hoisted(() => ({ - osc8Spy: vi.fn((abs: string, label?: string) => `[OSC8:${label ?? abs}]`), -})); - -// Mock colors as identity fns for simpler assertions -vi.mock('colors', () => { - const id = (s: string): string => s; - return { - default: { - bold: id, - dim: id, - red: id, - green: id, - yellow: id, - cyan: id, - magenta: id, - }, - }; -}); - -// Mock osc8Link via the exact module path used by the SUT -vi.mock('../../../../../lib/pooling', () => ({ - osc8Link: H.osc8Spy, -})); - -/* -------------------------------- Test body -------------------------------- */ -describe('headerLines', () => { - beforeEach(() => { - vi.clearAllMocks(); - vi.useFakeTimers(); - vi.setSystemTime(new Date('2024-01-02T03:04:05.000Z')); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it('headerLines: renders uploader header, files stats, progress bar, exports dir, throughput, and est jobs', () => { - const model = { - input: { - poolSize: 3, - cpuCount: 8, - filesTotal: 10, - filesCompleted: 4, - filesFailed: 1, - exportsDir: '/exp', - throughput: { r10s: 1, r60s: 0.5, successSoFar: 7 }, - }, - inProgress: 2, - pct: 50, - etaText: '~1m', - estTotalJobs: 1234.4, - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const lines = headerLines(model as any); - - expect(lines[0]).toContain('Parallel uploader'); - expect(lines[0]).toContain('3 workers'); - expect(lines[0]).toContain('(CPU avail: 8)'); - - expect(lines[1]).toContain('Files 10'); - expect(lines[1]).toContain('Completed 4'); - expect(lines[1]).toContain('Failed 1'); - expect(lines[1]).toContain('In-flight 2'); - - // Progress bar line includes percentage and ETA - expect(lines[2]).toContain('50%'); - expect(lines[2]).toContain('~1m'); - - // Exports dir - expect(lines.some((l) => l.includes('Exports dir: /exp'))).toBe(true); - - // Throughput: 1 * 3600 = 3600/hr; 0.5 * 3600 = 1800/hr - expect( - lines.some((l) => l.includes('Throughput: 3,600/hr (1h: 1,800/hr)')), - ).toBe(true); - - // Est total jobs rounded - expect(lines[2]).toContain('Est. total jobs: 1,234'); - }); - - it('totalsBlock: upload mode shows error breakdown and receipts totals', () => { - const uploadTotals = { - mode: 'upload', - success: 2, - skipped: 3, - error: 1, - errors: { Oops: 2, Bad: 1 }, - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const out = totalsBlock(uploadTotals as any); - expect(out).toContain('Error breakdown:'); - expect(out).toContain('Count[2] Oops'); - expect(out).toContain('Count[1] Bad'); - expect(out).toContain('Receipts totals'); - expect(out).toContain('Success: 2'); - expect(out).toContain('Skipped: 3'); - expect(out).toContain('Error: 1'); - }); - - it('totalsBlock: check mode summarizes pending and skipped', () => { - const checkTotals = { - mode: 'check', - totalPending: 5, - pendingConflicts: 2, - pendingSafe: 3, - skipped: 1, - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const out = totalsBlock(checkTotals as any); - expect(out).toContain('Receipts totals'); - expect(out).toContain('Pending: 5'); - expect(out).toContain('PendingConflicts: 2'); - expect(out).toContain('PendingSafe: 3'); - expect(out).toContain('Skipped: 1'); - }); - - it('workerLines: shows status badges, filename, elapsed, and mini progress bar', () => { - const now = Date.now(); - const workerState = new Map([ - [ - 1, - { - busy: true, - file: '/path/to/file1.csv', - startedAt: now - 5000, - lastLevel: 'ok', - progress: { processed: 50, total: 100 }, - }, - ], - [ - 2, - { - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', - progress: undefined, - }, - ], - [ - 3, - { - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }, - ], - ]); - - const model = { - input: { workerState }, - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const lines = workerLines(model as any); - - // w1: WORKING, file basename, ~5s elapsed, 50/100 and 50% mini bar (18 cols => 9 filled) - const l1 = lines.find((s) => s.includes('[w1]'))!; - expect(l1).toContain('WORKING'); - expect(l1).toContain('file1.csv'); - expect(l1).toContain('5s'); - expect(l1).toContain('50/100 (50%)'); - expect(l1).toMatch(/\[█{9}░{9}\]/); - - // w2: WARN badge, idle mini text - const l2 = lines.find((s) => s.includes('[w2]'))!; - expect(l2).toContain('WARN'); - expect(l2).toContain('—'); // dim mini text for no totals - - // w3: ERROR badge - const l3 = lines.find((s) => s.includes('[w3]'))!; - expect(l3).toContain('ERROR'); - }); - - it('hotkeysLine: varies by pool size and final flag', () => { - expect(hotkeysLine(1)).toContain('Hotkeys: [0] attach'); - expect(hotkeysLine(5)).toContain('Hotkeys: [0-4] attach'); - expect(hotkeysLine(15)).toContain('(Tab/Shift+Tab for ≥10)'); - - expect(hotkeysLine(3, true)).toContain( - 'Run complete — digits to view logs', - ); - expect(hotkeysLine(3, true)).toContain('q to quit'); - }); - - it('exportBlock: renders lines with resolved paths, OSC8 open links, and file:// URLs', () => { - const exportStatus = { - error: { - path: '/exp/combined-errors.log', - savedAt: 1700000000000, - exported: true, - }, - warn: { - path: '/exp/combined-warns.log', - savedAt: 1700000001000, - exported: false, - }, - info: { - path: '/exp/combined-info.log', - savedAt: 1700000002000, - exported: true, - }, - all: { - path: '/exp/combined-all.log', - savedAt: 1700000003000, - exported: true, - }, - failuresCsv: { - path: '/exp/failing-updates.csv', - savedAt: 1700000004000, - exported: false, - }, - }; - - const out = exportBlock('/exp', exportStatus); - // Labels and keys present - expect(out).toContain('E=export-errors'); - expect(out).toContain('W=export-warns'); - expect(out).toContain('I=export-info'); - expect(out).toContain('A=export-all'); - expect(out).toContain('F=export-failures-csv'); - - // “open” hyperlinks generated via osc8Link for absolute paths - expect(H.osc8Spy).toHaveBeenCalled(); - expect(out).toContain('[OSC8:open]'); - - // Absolute paths and file:// URLs appear - const { href } = pathToFileURL('/exp/combined-errors.log'); - expect(out).toContain('/exp/combined-errors.log'); - expect(out).toContain(href); - }); - - it('exportBlock: when exportsDir is undefined and no status path, shows placeholder and does not call osc8Link', () => { - const exportStatus = { - error: undefined, - warn: undefined, - info: undefined, - all: undefined, - failuresCsv: undefined, - } as unknown as Record; - const out = exportBlock(undefined, exportStatus); - expect(out).toContain('(set exportsDir)'); - expect(H.osc8Spy).not.toHaveBeenCalled(); - }); -}); diff --git a/src/commands/consent/upload-preferences/ui/tests/makeOnKeypressExtra.test.ts b/src/commands/consent/upload-preferences/ui/tests/makeOnKeypressExtra.test.ts deleted file mode 100644 index 9c0895fc..00000000 --- a/src/commands/consent/upload-preferences/ui/tests/makeOnKeypressExtra.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; - -import type { ExportStatusMap, SlotPaths } from '../../../../../lib/pooling'; -import { - makeOnKeypressExtra, - type MakeOnKeypressExtraArgs, -} from '../makeOnKeypressExtra'; - -import { showCombinedLogs } from '../../../../../lib/pooling'; -import { - writeFailingUpdatesCsv, - type ExportManager, - type FailingUpdateRow, -} from '../../artifacts'; - -// ---- Mocks (must be defined before SUT side-effects are exercised) ---- -vi.mock('../../../../../lib/pooling', () => ({ - showCombinedLogs: vi.fn(), -})); -vi.mock('../../artifacts', () => ({ - writeFailingUpdatesCsv: vi.fn(), -})); - -// ---- Fixtures & helpers ------------------------------------------------ - -/** - * Create a minimal fake ExportManager with a controllable implementation. - * - * @param impl - Optional per-level return values or error - * @returns A fake ExportManager - */ -function makeFakeExportManager(impl?: { - /** When set, exportCombinedLogs will throw for this level */ - throwForLevel?: 'error' | 'warn' | 'info' | 'all'; - /** Optional explicit return path per level */ - paths?: Partial>; -}): ExportManager { - const em = { - exportsDir: '/exp', - exportCombinedLogs: ( - _: SlotPaths, - level: 'error' | 'warn' | 'info' | 'all', - ): string => { - if (impl?.throwForLevel === level) throw new Error(`boom:${level}`); - return impl?.paths?.[level] ?? `/exp/combined-${level}.log`; - }, - }; - return em as ExportManager; -} - -/** - * Build a default set of args for makeOnKeypressExtra. - * - * @returns Tuple of [args, spies, handler, exportStatus] - */ -function buildArgs(): { - args: Parameters[0]; - onPauseSpy: ReturnType void>>; - onRepaintSpy: ReturnType void>>; - handler: (buf: Buffer) => void; - exportStatus: ExportStatusMap; -} { - const onPauseSpy = vi.fn<(p: boolean) => void>(); - const onRepaintSpy = vi.fn<() => void>(); - - const exportStatus: ExportStatusMap = { - error: undefined, - warn: undefined, - info: undefined, - all: undefined, - failuresCsv: undefined, - }; - - const args: MakeOnKeypressExtraArgs = { - slotLogPaths: new Map() as unknown as SlotPaths, - exportMgr: makeFakeExportManager(), - failingUpdates: [ - { id: 'abc', reason: 'nope' }, - ] as unknown as Array, - exportStatus, - onRepaint: onRepaintSpy, - onPause: onPauseSpy, - }; - - const handler = makeOnKeypressExtra(args); - - return { args, onPauseSpy, onRepaintSpy, handler, exportStatus }; -} - -describe('makeOnKeypressExtra', () => { - const mShow = vi.mocked(showCombinedLogs); - const mWriteFailCsv = vi.mocked(writeFailingUpdatesCsv); - - let writeSpy: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - // Silence & capture stdout writes for assertions - writeSpy = vi - .spyOn(process.stdout, 'write') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => true as unknown as boolean) as any; - }); - - afterEach(() => { - writeSpy.mockRestore(); - }); - - it('returns a keypress handler function', () => { - const { handler } = buildArgs(); - expect(typeof handler).toBe('function'); - // Explicit runtime check that it accepts a Buffer and returns void - const out: void = handler(Buffer.from('x')); - expect(out).toBeUndefined(); - }); - - it('viewer keys call showCombinedLogs and pause the UI', () => { - const { handler, onPauseSpy } = buildArgs(); - - handler(Buffer.from('e')); // errors only - expect(mShow).toHaveBeenCalledWith(expect.anything(), ['err'], 'error'); - expect(onPauseSpy).toHaveBeenLastCalledWith(true); - - handler(Buffer.from('w')); // warn + err - expect(mShow).toHaveBeenCalledWith( - expect.anything(), - ['warn', 'err'], - 'warn', - ); - - handler(Buffer.from('i')); // info - expect(mShow).toHaveBeenCalledWith(expect.anything(), ['info'], 'all'); - - handler(Buffer.from('l')); // all logs - expect(mShow).toHaveBeenCalledWith( - expect.anything(), - ['out', 'err', 'structured'], - 'all', - ); - }); - - it('export keys write combined logs, update exportStatus, and repaint', () => { - const { handler, exportStatus, onRepaintSpy } = buildArgs(); - - handler(Buffer.from('E')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'Wrote combined error logs to: /exp/combined-error.log', - ), - ); - expect(exportStatus.error?.path).toBe('/exp/combined-error.log'); - expect(exportStatus.error?.exported).toBe(true); - expect(typeof exportStatus.error?.savedAt).toBe('number'); - expect(onRepaintSpy).toHaveBeenCalled(); - - handler(Buffer.from('W')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'Wrote combined warn logs to: /exp/combined-warn.log', - ), - ); - expect(exportStatus.warn?.path).toBe('/exp/combined-warn.log'); - - handler(Buffer.from('I')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'Wrote combined info logs to: /exp/combined-info.log', - ), - ); - expect(exportStatus.info?.path).toBe('/exp/combined-info.log'); - - handler(Buffer.from('A')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'Wrote combined ALL logs to: /exp/combined-all.log', - ), - ); - expect(exportStatus.all?.path).toBe('/exp/combined-all.log'); - }); - - it('handles export errors gracefully (writes failure message)', () => { - const { args, exportStatus } = buildArgs(); - // Rebuild handler with a manager that throws on "warn" - const throwing = makeFakeExportManager({ throwForLevel: 'warn' }); - const handler = makeOnKeypressExtra({ ...args, exportMgr: throwing }); - - handler(Buffer.from('W')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining('Failed to write combined warn logs'), - ); - // Should not set exportStatus.warn on failure - expect(exportStatus.warn).toBeUndefined(); - }); - - it('writes failing updates CSV on "F" and records the path', () => { - const { handler, exportStatus, onRepaintSpy } = buildArgs(); - - handler(Buffer.from('F')); - expect(mWriteFailCsv).toHaveBeenCalledTimes(1); - expect(mWriteFailCsv.mock.calls[0]?.[1]).toBe('/exp/failing-updates.csv'); - - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'Wrote failing updates CSV to: /exp/failing-updates.csv', - ), - ); - expect(exportStatus.failuresCsv?.path).toBe('/exp/failing-updates.csv'); - expect(exportStatus.failuresCsv?.exported).toBe(true); - expect(onRepaintSpy).toHaveBeenCalled(); - }); - - it('handles failing updates CSV error path', () => { - const { args } = buildArgs(); - mWriteFailCsv.mockImplementationOnce(() => { - throw Object.assign(new Error('nope'), { stack: 'STACK!' }); - }); - const handler = makeOnKeypressExtra(args); - - handler(Buffer.from('F')); - expect(writeSpy).toHaveBeenCalledWith( - expect.stringContaining('Failed to write failing updates CSV'), - ); - // includes stack - expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('STACK!')); - }); - - it('escape or ^] resumes (unpauses) and repaints', () => { - const { handler, onPauseSpy, onRepaintSpy } = buildArgs(); - handler(Buffer.from('\x1b')); // ESC - expect(onPauseSpy).toHaveBeenCalledWith(false); - expect(onRepaintSpy).toHaveBeenCalled(); - - onPauseSpy.mockClear(); - onRepaintSpy.mockClear(); - - handler(Buffer.from('\x1d')); // ^] (GS) - expect(onPauseSpy).toHaveBeenCalledWith(false); - expect(onRepaintSpy).toHaveBeenCalled(); - }); - - it('unknown keys perform no actions', () => { - const { handler } = buildArgs(); - - handler(Buffer.from('z')); - expect(showCombinedLogs).not.toHaveBeenCalled(); - expect(writeFailingUpdatesCsv).not.toHaveBeenCalled(); - // No success/failure strings written - const allWrites = writeSpy.mock.calls.map((c) => String(c[0])); - const meaningful = allWrites.filter((s) => /Wrote|Failed/.test(s)); - expect(meaningful.length).toBe(0); - }); -}); diff --git a/src/commands/consent/upload-preferences/ui/tests/plugin.test.ts b/src/commands/consent/upload-preferences/ui/tests/plugin.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/ui/tests/renderDashboard.test.ts b/src/commands/consent/upload-preferences/ui/tests/renderDashboard.test.ts deleted file mode 100644 index f2106b97..00000000 --- a/src/commands/consent/upload-preferences/ui/tests/renderDashboard.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - describe, - it, - expect, - vi, - beforeEach, - afterEach, - beforeAll, -} from 'vitest'; -import type { RenderDashboardInput } from '../buildFrameModel'; - -const H = vi.hoisted(() => ({ - cursorTo: vi.fn(), - clearScreenDown: vi.fn(), - writeIndex: vi.fn(), -})); - -/* ---- Mocks must be registered before importing the SUT ---- */ -vi.mock('node:readline', () => ({ - cursorTo: H.cursorTo, - clearScreenDown: H.clearScreenDown, -})); - -vi.mock('../buildFrameModel', () => ({ - buildFrameModel: vi.fn((input) => ({ - input, - inProgress: 0, - pct: 0, - etaText: '', - estTotalJobs: undefined, - })), -})); - -vi.mock('../headerLines', () => ({ - headerLines: vi.fn(() => ['H1', 'H2']), - workerLines: vi.fn(() => ['W1', 'W2']), - hotkeysLine: vi.fn(() => 'HOT'), - exportBlock: vi.fn(() => 'EXP'), -})); - -vi.mock('../../artifacts/writeExportsIndex', () => ({ - writeExportsIndex: H.writeIndex, -})); - -const expectedFrame = ['H1', 'H2', '', 'W1', 'W2', '', 'HOT', '', 'EXP'].join( - '\n', -); - -describe('renderDashboard', () => { - let writeSpy: ReturnType; - let renderDashboard: (input: RenderDashboardInput) => void; - - beforeAll(async () => { - // Ensure no previous module instance leaks - await vi.resetModules(); - // Give the writeIndex stub a do-nothing return - H.writeIndex.mockReturnValue(undefined as unknown as string); - // Dynamically import SUT AFTER mocks are in place - ({ renderDashboard } = await import('../renderDashboard')); - }); - - beforeEach(() => { - vi.clearAllMocks(); - writeSpy = vi - .spyOn(process.stdout, 'write') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockImplementation(() => true) as any; - }); - - afterEach(() => { - writeSpy.mockRestore(); - }); - - it('non-final render: writes exports index, hides cursor, clears screen, prints frame; skips redraw for identical frame', () => { - const input: RenderDashboardInput = { - poolSize: 2, - final: false, - exportsDir: '/exp', - exportStatus: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; - - renderDashboard(input); - - expect(H.writeIndex).toHaveBeenCalledWith('/exp', {}); - - expect(writeSpy).toHaveBeenCalledWith('\x1b[?25l'); - expect(H.cursorTo).toHaveBeenCalledWith(process.stdout, 0, 0); - expect(H.clearScreenDown).toHaveBeenCalledWith(process.stdout); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const writes = writeSpy.mock.calls.map((c: any) => String(c[0])); - expect(writes).toContain(`${expectedFrame}\n`); - - const beforeCalls = writeSpy.mock.calls.length; - renderDashboard(input); - expect(H.writeIndex).toHaveBeenCalledTimes(2); - expect(writeSpy.mock.calls.length).toBe(beforeCalls); // no additional writes - }); - - it('final render: writes exports index, shows cursor, prints frame, and does not move/clear the screen', () => { - const input = { - poolSize: 2, - final: true, - exportsDir: '/exp', - exportStatus: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; - - renderDashboard(input); - - expect(H.writeIndex).toHaveBeenCalledWith('/exp', {}); - - expect(writeSpy).toHaveBeenCalledWith('\x1b[?25h'); - expect(H.cursorTo).not.toHaveBeenCalled(); - expect(H.clearScreenDown).not.toHaveBeenCalled(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const writes = writeSpy.mock.calls.map((c: any) => String(c[0])); - expect(writes).toContain(`${expectedFrame}\n`); - }); -}); diff --git a/src/commands/consent/upload-preferences/ui/tests/typeGuards.test.ts b/src/commands/consent/upload-preferences/ui/tests/typeGuards.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/consent/upload-preferences/ui/typeGuards.ts b/src/commands/consent/upload-preferences/ui/typeGuards.ts new file mode 100644 index 00000000..25abc2eb --- /dev/null +++ b/src/commands/consent/upload-preferences/ui/typeGuards.ts @@ -0,0 +1,62 @@ +export type UploadModeTotals = { + /** The mode of the export, always 'upload' */ + mode: 'upload'; + /** Number of records successfully uploaded */ + success: number; + /** Number of records skipped */ + skipped: number; + /** Number of records failed */ + error: number; + /** Number of records that were not processed */ + errors: Record; +}; + +export type CheckModeTotals = { + /** The mode of the export, always 'check' */ + mode: 'check'; + /** Number of records pending */ + pendingConflicts: number; + /** Number of records pending safe */ + pendingSafe: number; + /** Number of records pending */ + totalPending: number; + /** Number of records skipped */ + skipped: number; +}; + +/** + * Represents the totals for either upload or check mode. + */ +export type AnyTotals = UploadModeTotals | CheckModeTotals; + +/** + * Type guard for UploadModeTotals + * + * @param totals - The totals object to check + * @returns True if the totals object is of type UploadModeTotals, false otherwise + */ +export function isUploadModeTotals( + totals: unknown, +): totals is UploadModeTotals { + return ( + typeof totals === 'object' && + totals !== null && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (totals as any).mode === 'upload' + ); +} + +/** + * Type guard for CheckModeTotals + * + * @param totals - The totals object to check + * @returns True if the totals object is of type CheckModeTotals, false otherwise + */ +export function isCheckModeTotals(totals: unknown): totals is CheckModeTotals { + return ( + typeof totals === 'object' && + totals !== null && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (totals as any).mode === 'check' + ); +} diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index 73be6577..1d1d84f7 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -7,7 +7,7 @@ import { loadReferenceData, type PreferenceUploadReferenceData, } from './loadReferenceData'; -import { type PreferenceReceiptsInterface } from '../receipts/receiptsState'; +import { type PreferenceReceiptsInterface } from '../artifacts/receipts/receiptsState'; import { type PreferenceSchemaInterface } from '../schemaState'; import { parsePreferenceManagementCsvWithCache } from '../../../../lib/preference-management'; import type { @@ -19,7 +19,7 @@ import type { import type { FormattedAttribute } from '../../../../lib/graphql/formatAttributeValues'; import type { GraphQLClient } from 'graphql-request'; import { limitRecords } from '../../../../lib/helpers'; -import { transformCsv } from '../transform'; +import { transformCsv } from './transform'; export interface InteractiveUploadPreferencePlan { /** CSV file path to load preference records from */ diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index b7c09781..53c2e3c2 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -3,13 +3,13 @@ import colors from 'colors'; import { map as pMap } from 'bluebird'; import { chunk, groupBy } from 'lodash-es'; import { logger } from '../../../../logger'; -import { buildPendingUpdates } from '../transform/buildPendingUpdates'; +import { buildPendingUpdates } from './transform'; import { uploadChunkWithSplit } from './batchUploader'; import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; import { RETRYABLE_BATCH_STATUSES } from '../../../../constants'; import { extractErrorMessage, limitRecords } from '../../../../lib/helpers'; import type { InteractiveUploadPreferencePlan } from './buildInteractiveUploadPlan'; -import type { PreferenceReceiptsInterface } from '../receipts'; +import type { PreferenceReceiptsInterface } from '../artifacts/receipts'; import type { Got } from 'got'; /** diff --git a/src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts b/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts similarity index 94% rename from src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts rename to src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts index 2b29d12b..39d6939e 100644 --- a/src/commands/consent/upload-preferences/transform/buildPendingUpdates.ts +++ b/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts @@ -5,7 +5,11 @@ * PreferenceUpdateItem payloads, ready for upload. */ import type { PreferenceUpdateItem } from '@transcend-io/privacy-types'; -import type { PreferenceTopic, Purpose } from '../../../../lib/graphql'; +import type { + PreferenceTopic, + FormattedAttribute, + Purpose, +} from '../../../../../lib/graphql'; import { getPreferenceIdentifiersFromRow, getPreferenceUpdatesFromRow, @@ -14,8 +18,7 @@ import { type ColumnPurposeMap, type PendingSafePreferenceUpdates, type PendingWithConflictPreferenceUpdates, -} from '../../../../lib/preference-management'; -import type { FormattedAttribute } from '../../../../lib/graphql/formatAttributeValues'; +} from '../../../../../lib/preference-management'; export interface BuildPendingParams { /** Safe updates keyed by user/primaryKey */ diff --git a/src/commands/consent/upload-preferences/transform/index.ts b/src/commands/consent/upload-preferences/upload/transform/index.ts similarity index 100% rename from src/commands/consent/upload-preferences/transform/index.ts rename to src/commands/consent/upload-preferences/upload/transform/index.ts diff --git a/src/commands/consent/upload-preferences/transform/transformCsv.ts b/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts similarity index 96% rename from src/commands/consent/upload-preferences/transform/transformCsv.ts rename to src/commands/consent/upload-preferences/upload/transform/transformCsv.ts index dcc6eac6..90c87e93 100644 --- a/src/commands/consent/upload-preferences/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts @@ -1,7 +1,7 @@ // FIXME import colors from 'colors'; -import { logger } from '../../../../logger'; +import { logger } from '../../../../../logger'; /** * Add Transcend ID to preferences if email_id is present diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index 5feeb79c..eae8d82b 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -4,7 +4,7 @@ import { getFilePrefix } from './computeFiles'; import { splitCsvToList } from '../../../lib/requests'; import { interactivePreferenceUploaderFromPlan } from './upload/interactivePreferenceUploaderFromPlan'; import { makeSchemaState } from './schemaState'; -import { makeReceiptsState } from './receipts/receiptsState'; +import { makeReceiptsState } from './artifacts/receipts/receiptsState'; import { buildTranscendGraphQLClient, createSombraGotInstance, diff --git a/src/constants.ts b/src/constants.ts index 150b34b7..1c5e462c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -200,3 +200,8 @@ export const SCOPE_TITLES = Object.keys(SCOPES_BY_TITLE); export const RETRYABLE_BATCH_STATUSES = new Set([ 429, 502, 500, 504, 329, ] as const); + +/** + * Debugging + */ +export const DEBUG = process.env.DEBUG === '1'; diff --git a/src/lib/graphql/index.ts b/src/lib/graphql/index.ts index c6f91bae..a4a4fc36 100644 --- a/src/lib/graphql/index.ts +++ b/src/lib/graphql/index.ts @@ -19,6 +19,7 @@ export * from './fetchAllDataCategories'; export * from './fetchAllDataFlows'; export * from './fetchAllMessages'; export * from './fetchAllPolicies'; +export * from './formatAttributeValues'; export * from './fetchAllPreferenceTopics'; export * from './fetchAllPrivacyCenters'; export * from './fetchAllProcessingActivities'; diff --git a/src/lib/helpers/getCurrentModulePath.ts b/src/lib/helpers/getCurrentModulePath.ts deleted file mode 100644 index d3e2c769..00000000 --- a/src/lib/helpers/getCurrentModulePath.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Returns the current module's path so the worker pool knows what file to re-exec. - * In Node ESM, __filename is undefined, so we fall back to argv[1]. - * - * @returns The current module's path as a string - */ -export function getCurrentModulePath(): string { - if (typeof __filename !== 'undefined') { - return __filename as unknown as string; - } - return process.argv[1]; -} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index 8ae1d5eb..acce1e3e 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -10,4 +10,3 @@ export * from './retrySamePromise'; export * from './limitRecords'; export * from './RateCounter'; export * from './readSafe'; -export * from './getCurrentModulePath'; diff --git a/src/lib/pooling/assignWorkToWorker.ts b/src/lib/pooling/assignWorkToWorker.ts deleted file mode 100644 index fd389756..00000000 --- a/src/lib/pooling/assignWorkToWorker.ts +++ /dev/null @@ -1,130 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; -import { isIpcOpen } from './spawnWorkerProcess'; - -export interface WorkerState { - /** True when the worker is executing a task */ - busy: boolean; - /** Absolute file path currently being processed (if any) */ - file: string | null; - /** Start timestamp (ms) when the current task began */ - startedAt: number | null; - /** - * Last log level observed on stderr. - * 'ok' = normal, 'warn' = warning, 'error' = error - */ - lastLevel: 'ok' | 'warn' | 'error'; - - /** - * Live progress reported by the child process. - * `total` is the total number of records for the current file. - * `processed` is the cumulative number processed so far. - */ - progress?: { - /** Cumulative items processed so far for this file */ - processed: number; - /** Total number of items to process for this file */ - total: number; - }; -} - -/** - * Assign a pending work item to a specified worker process. - * - * Checks if the worker is available and connected, then assigns the next file from the - * pending queue. Updates the worker's state (including `lastLevel` and `progress`), - * triggers a UI repaint, and sends the task to the worker via IPC. If no work is left, - * marks the worker as idle. If IPC is closed during send, requeues the work and idles. - * - * @param id - The worker's unique identifier. - * @param payload - Common payload/options to send with each task. - * @param options - Object containing pending, workers, workerState, and repaint. - */ -export function assignWorkToWorker( - id: number, - payload: T, - { - pending, - workerState, - workers, - repaint, - }: { - /** Pending files to be processed */ - pending: string[]; - /** Map of worker IDs to their corresponding ChildProcess instances. */ - workers: Map; - /** Map of worker IDs to their current WorkerState. */ - workerState: Map; - /** Function to repaint the UI/dashboard after state changes. */ - repaint: () => void; - }, -): void { - const w = workers.get(id); - const prev = workerState.get(id); - - // If worker IPC isn't open, mark idle (keep lastLevel so badge persists) - if (!isIpcOpen(w)) { - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - return; - } - - // Take next file - const filePath = pending.shift(); - if (!filePath) { - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - repaint(); - return; - } - - // Mark working (child will later send progress updates) - workerState.set(id, { - busy: true, - file: filePath, - startedAt: Date.now(), - lastLevel: 'ok', // becomes OK when a fresh task starts - progress: undefined, - }); - repaint(); - - // Send task - try { - w!.send?.({ - type: 'task', - payload: { - filePath, - options: payload, - }, - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - // If the pipe closed between the check and send: requeue + idle - if ( - err?.code === 'ERR_IPC_CHANNEL_CLOSED' || - err?.code === 'EPIPE' || - err?.errno === -32 - ) { - pending.unshift(filePath); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - repaint(); - return; - } - throw err; - } -} diff --git a/src/lib/pooling/attachWorkerHandlers.ts b/src/lib/pooling/attachWorkerHandlers.ts deleted file mode 100644 index e02e3733..00000000 --- a/src/lib/pooling/attachWorkerHandlers.ts +++ /dev/null @@ -1,213 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; -import { spawnWorkerProcess } from './spawnWorkerProcess'; -import { assignWorkToWorker, type WorkerState } from './assignWorkToWorker'; -import { - isWorkerReadyMessage, - isWorkerProgressMessage, - isWorkerResultMessage, - type ProgressInfo, -} from './ipc'; -import { wireStderrBadges, appendFailureLog } from './diagnostics'; - -/** - * Wire up a worker's lifecycle: - * - on "ready": hand it work - * - on "progress": update per-worker progress (and optional aggregator) - * - on "result": update counters, clear progress, queue next work - * - on "exit": optionally respawn if there is still work pending - * - * Keep this file focused on orchestration; protocol lived in ipc.ts and - * diagnostics (stderr badges, failure logs) live in diagnostics.ts. - * - * @param params - Options - */ -export function attachWorkerHandlers(params: { - /** Worker slot ID (used as key in `workers` and `workerState` maps) */ - id: number; - /** Child process instance representing this worker */ - child: ChildProcess; - /** Map of all active worker processes, keyed by worker ID */ - workers: Map; - /** Map of UI/visual state per worker (progress, file, last log level, etc.) */ - workerState: Map; - /** Global counters for processed files */ - state: { - /** Number of successfully completed files */ - completed: number; - /** Number of failed files */ - failed: number; - }; - /** Queue of pending file paths still to be processed */ - filesPending: string[]; - /** Function to trigger a dashboard re-render (reflects latest worker state) */ - repaint: () => void; - /** Common task options to send with each work assignment */ - common: T; - /** Callback fired once all workers have exited and the pool is drained */ - onAllWorkersExited: () => void; - /** Directory path where logs and failure reports are written */ - logDir: string; - /** Absolute path to the worker module script (used when respawning workers) */ - modulePath: string; - /** If true, respawned workers are created with silent stdio (no inherited stdout/stderr) */ - spawnSilent?: boolean; - /** - * Optional aggregator callback for live throughput metrics. - * Invoked on every progress tick from workers. - */ - onProgress?: (info: ProgressInfo) => void; -}): void { - const { - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common, - onAllWorkersExited, - logDir, - modulePath, - spawnSilent = false, - onProgress, - } = params; - - // Register worker & initialize visual state - workers.set(id, child); - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', // preserve previous badge (e.g., ERROR after crash) - progress: undefined, - }); - - // Live badge updates from stderr - wireStderrBadges(id, child, workerState, repaint); - - // IPC messages from child - child.on('message', (msg: unknown) => { - if (isWorkerReadyMessage(msg)) { - assignWorkToWorker(id, common, { - pending: filesPending, - workers, - workerState, - repaint, - }); - return; - } - - if (isWorkerProgressMessage(msg)) { - const { filePath, successDelta, successTotal, fileTotal } = msg.payload; - - // Update per-worker progress bar - const current = workerState.get(id)!; - workerState.set(id, { - ...current, - file: current.file ?? filePath, - progress: { - processed: successTotal ?? current.progress?.processed ?? 0, - total: fileTotal ?? current.progress?.total ?? 0, - }, - }); - - // Bubble to optional global aggregator (for throughput) - if (onProgress && successDelta) { - onProgress({ - workerId: id, - filePath, - successDelta, - successTotal, - fileTotal, - }); - } - - repaint(); - return; - } - - if (isWorkerResultMessage(msg)) { - const { ok, filePath, error } = msg.payload; - - // Update file-level counters - if (ok) state.completed += 1; - else state.failed += 1; - - // Mark idle & clear transient progress; keep ERROR badge if the task failed - const current = workerState.get(id)!; - workerState.set(id, { - ...current, - busy: false, - file: null, - startedAt: null, - lastLevel: ok ? 'ok' : 'error', - progress: undefined, - }); - repaint(); - - // If failure, append to a shared log for triage - if (!ok && error) { - appendFailureLog(logDir, id, filePath, error); - } - - // Keep the worker busy until the queue is empty - assignWorkToWorker(id, common, { - pending: filesPending, - workers, - workerState, - repaint, - }); - } - - // Ignore unknown messages to keep parent resilient to future protocol changes - }); - - // Process exit/crash handling - child.on('exit', (code, signal) => { - workers.delete(id); - - // Mark the slot visibly as ERROR (crashed) and keep it until a new task is assigned - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }); - repaint(); - - // If it crashed and there's still work to do, respawn a replacement - const abnormal = (typeof code === 'number' && code !== 0) || !!signal; - if (abnormal && filesPending.length > 0) { - const replacement = spawnWorkerProcess({ - id, - modulePath, - logDir, - openLogWindows: true, // attempt to open tail windows for respawns too - isSilent: spawnSilent, - }); - - attachWorkerHandlers({ - id, - child: replacement, - workers, - workerState, - state, - filesPending, - repaint, - common, - onAllWorkersExited, - logDir, - modulePath, - spawnSilent, - onProgress, - }); - return; - } - - // If all workers have exited, let the parent clean up - if (workers.size === 0) onAllWorkersExited(); - }); -} diff --git a/src/lib/pooling/dashboardPlugin.ts b/src/lib/pooling/dashboardPlugin.ts new file mode 100644 index 00000000..ae8f6011 --- /dev/null +++ b/src/lib/pooling/dashboardPlugin.ts @@ -0,0 +1,173 @@ +// lib/ui/dashboardPlugin.ts +import * as readline from 'node:readline'; +import colors from 'colors'; +import type { SlotState } from './types'; +import type { ObjByString } from '@transcend-io/type-utils'; + +/** + * A dashboard plugin defines how to render the worker pool UI. + * Commands can supply a plugin to customize: + * - The header block (summary stats, title, etc.) + * - Per-worker rows (one line per worker slot) + * - Optional extras (artifact exports, breakdowns, footers) + * + * @template TTotals - The shape of the aggregate totals object maintained by the command. + */ +export interface DashboardPlugin { + /** + * Render the header block of the dashboard. + * + * @param ctx - Context with pool/worker state, totals, and metadata. + * @returns An array of strings, each representing one line in the header. + */ + renderHeader: (ctx: CommonCtx) => string[]; + + /** + * Render per-worker rows, usually one line per worker slot. + * + * @param ctx - Context with pool/worker state, totals, and metadata. + * @returns An array of strings, each representing one row in the workers section. + */ + renderWorkers: (ctx: CommonCtx) => string[]; + + /** + * Render any optional extra blocks that appear after the worker rows. + * Useful for printing export paths, aggregated metrics, breakdowns, etc. + * + * @param ctx - Context with pool/worker state, totals, and metadata. + * @returns An array of strings, each representing one additional line. + */ + renderExtras?: (ctx: CommonCtx) => string[]; +} + +/** + * Shared context object passed into all render methods of a {@link DashboardPlugin}. + * + * @template TTotals - The shape of the aggregate totals object maintained by the command. + */ +export type CommonCtx = { + /** Human-readable title for the dashboard (e.g., "Parallel uploader"). */ + title: string; + + /** Number of worker processes spawned in the pool. */ + poolSize: number; + + /** Logical CPU count, included for informational display. */ + cpuCount: number; + + /** Total number of "files" or logical units the command expects to process. */ + filesTotal: number; + + /** Count of successfully completed files/tasks. */ + filesCompleted: number; + + /** Count of failed files/tasks. */ + filesFailed: number; + + /** + * State of each worker slot, keyed by worker id. + * Includes busy flag, file label, start time, last log badge, and progress. + */ + workerState: Map>; + + /** + * Aggregate totals maintained by the command’s hook logic. + * Domain-specific metrics (e.g., rows uploaded, bytes processed) can be surfaced here. + */ + totals: TTotals; + + /** + * Throughput metrics tracked by the runner: + * - successSoFar: convenience alias for completed count + * - r10s: completions/sec averaged over last 10s + * - r60s: completions/sec averaged over last 60s + */ + throughput: { + /** Cumulative count of successful completions so far. */ + successSoFar: number; + /** Recent throughput rate over the last 10 seconds. */ + r10s: number; + /** Recent throughput rate over the last 60 seconds. */ + r60s: number; + }; + + /** True when the pool has fully drained and all workers have exited. */ + final: boolean; + + /** + * Optional export status payload provided by the command. + * Useful for rendering artifact paths or "latest export" summaries. + */ + exportStatus?: Record; +}; + +/** The most recently rendered frame, cached to suppress flicker from duplicate renders. */ +let lastFrame = ''; + +/** + * Generate the hotkeys hint string that appears at the bottom of the dashboard. + * + * @param poolSize - The number of worker slots in the pool. + * @param final - Whether the run has completed. + * @returns A dimmed string listing the supported hotkeys for attach/detach/quit. + */ +export const hotkeysHint = (poolSize: number, final: boolean): string => { + const maxDigit = Math.min(poolSize - 1, 9); + const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`; + const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : ''; + return final + ? colors.dim( + 'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit', + ) + : colors.dim( + `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • ` + + 'Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit', + ); +}; + +/** + * Render the dashboard using a supplied {@link DashboardPlugin}. + * + * The frame is composed of: + * - Header lines + * - A blank separator + * - Worker rows + * - A blank separator + * - Hotkeys hint + * - Optional extras (if plugin supplies them) + * + * Optimizations: + * - Suppresses re-renders if the frame is identical to the previous frame (flicker-free). + * - Hides the terminal cursor during live updates, restoring it when final. + * + * @param ctx - Shared context containing pool state, worker state, totals, throughput, etc. + * @param plugin - The plugin that defines how to render the header, workers, and optional extras. + */ +export function dashboardPlugin( + ctx: CommonCtx, + plugin: DashboardPlugin, +): void { + const frame = [ + ...plugin.renderHeader(ctx), + '', + ...plugin.renderWorkers(ctx), + '', + hotkeysHint(ctx.poolSize, ctx.final), + ...(plugin.renderExtras ? [''].concat(plugin.renderExtras(ctx)) : []), + ].join('\n'); + + // Skip duplicate renders during live runs to avoid flicker. + if (!ctx.final && frame === lastFrame) return; + lastFrame = frame; + + if (!ctx.final) { + // Hide cursor and repaint in place + process.stdout.write('\x1b[?25l'); + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + } else { + // Restore cursor on final render + process.stdout.write('\x1b[?25h'); + } + process.stdout.write(`${frame}\n`); +} diff --git a/src/lib/pooling/diagnostics.ts b/src/lib/pooling/diagnostics.ts deleted file mode 100644 index 73b4cce3..00000000 --- a/src/lib/pooling/diagnostics.ts +++ /dev/null @@ -1,59 +0,0 @@ -// diagnostics.ts -import type { ChildProcess } from 'node:child_process'; -import { appendFileSync } from 'node:fs'; -import { join } from 'node:path'; -import type { WorkerState } from './assignWorkToWorker'; -import { classifyLogLevel, makeLineSplitter } from './logRotation'; - -/** - * Wire stderr to flip WARN/ERROR badges quickly. - * - * @param id - Worker ID - * @param child - Child process - * @param workerState - Map of worker states - * @param repaint - Repaint function - */ -export function wireStderrBadges( - id: number, - child: ChildProcess, - workerState: Map, - repaint: () => void, -): void { - if (!child.stderr) return; - - const onErrLine = makeLineSplitter((line) => { - // If there is an explicit tag, use it; otherwise since it came from stderr, treat as WARN. - const explicit = classifyLogLevel(line); // 'warn' | 'error' | null - const lvl = explicit ?? 'warn'; - const curr = workerState.get(id); - if (!curr || curr.lastLevel === lvl) return; - workerState.set(id, { ...curr, lastLevel: lvl }); - repaint(); - }); - - child.stderr.on('data', onErrLine); -} - -/** - * Best-effort shared failure log write. - * - * @param logDir - Directory to write logs - * @param workerId - Worker ID - * @param filePath - File path being processed - * @param error - Error message to log - */ -export function appendFailureLog( - logDir: string, - workerId: number, - filePath: string, - error: string, -): void { - try { - appendFileSync( - join(logDir, 'failures.log'), - `[${new Date().toISOString()}] worker ${workerId} file=${filePath}\n${error}\n\n`, - ); - } catch { - // ignore log write errors - } -} diff --git a/src/lib/pooling/extraKeys.ts b/src/lib/pooling/extraKeys.ts new file mode 100644 index 00000000..94c9215f --- /dev/null +++ b/src/lib/pooling/extraKeys.ts @@ -0,0 +1,241 @@ +/** + * Shared handler for "extra" keyboard shortcuts used by the interactive dashboard. + * + * It wires: + * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all) + * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all) + * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard + * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings + * + * Usage (inside `runPool({... extraKeyHandler })`): + * ```ts + * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => + * createExtraKeyHandler({ logsBySlot, repaint, setPaused }) + * ``` + * + * If you also want export hotkeys + an "Exports" panel: + * ```ts + * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => + * createExtraKeyHandler({ + * logsBySlot, repaint, setPaused, + * exportMgr, // enables E/W/I/A + * exportStatus, // keeps panel timestamps up to date + * custom: { // optional, e.g. 'F' to export a CSV + * F: async ({ say, noteExport }) => { ... } + * } + * }) + * ``` + */ + +import type { ExportStatusMap } from './logRotation'; +import { showCombinedLogs, type LogLocation } from './showCombinedLogs'; +import type { SlotPaths } from './spawnWorkerProcess'; + +/** Severity filter applied by the viewer. */ +type ViewLevel = 'error' | 'warn' | 'all'; + +/** + * Options for {@link createExtraKeyHandler}. + */ +export type CreateExtraKeyHandlerOpts = { + /** + * Per-slot log file paths maintained by the runner; used to stream or export logs. + */ + logsBySlot: SlotPaths; + + /** + * Request an immediate dashboard repaint (e.g., after updating export status). + */ + repaint: () => void; + + /** + * Pause/unpause dashboard repainting. The handler pauses while a viewer is open + * to prevent the dashboard from overwriting the viewer output, then resumes on exit. + */ + setPaused: (p: boolean) => void; + + /** + * Optional export manager to enable uppercase export keys: + * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all) + * + * Provide this only if your command supports writing combined log files. + */ + exportMgr?: { + /** Destination directory for exported artifacts. */ + exportsDir: string; + /** + * Write a combined log file for the selected severity and return the absolute path. + * + * @param logs - Log paths to combine. + * @param which - Severity selection. + * @returns Absolute path to the written file. + */ + exportCombinedLogs: ( + logs: SlotPaths, + which: 'error' | 'warn' | 'info' | 'all', + ) => string; + }; + + /** + * Optional “Exports” status map. If provided, the handler updates timestamps + * when exports are written so your dashboard panel can reflect “last saved” times. + */ + exportStatus?: ExportStatusMap; + + /** + * Optional custom key bindings for command-specific actions. + * Each handler receives helpers to print messages and to update the exports panel. + * + * Example: + * ```ts + * custom: { + * F: async ({ say, noteExport }) => { + * const p = await writeFailingUpdatesCsv(...); + * say(`Wrote failing updates to: ${p}`); + * noteExport('failuresCsv', p); + * } + * } + * ``` + */ + custom?: Record< + string, + (ctx: { + /** Update {@link exportStatus} (if present) and repaint the dashboard. */ + noteExport: (slot: keyof ExportStatusMap, absPath: string) => void; + /** Print a line to stdout, automatically newline-terminated. */ + say: (s: string) => void; + }) => void | Promise + >; +}; + +/** + * Create a keypress handler for interactive viewers/exports. + * + * @param opts - Configuration for viewers, exports, and custom keys. + * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`. + */ +export function createExtraKeyHandler( + opts: CreateExtraKeyHandlerOpts, +): (buf: Buffer) => void { + const { logsBySlot, repaint, setPaused, exportMgr, exportStatus, custom } = + opts; + + const say = (s: string): void => { + process.stdout.write(`${s}\n`); + }; + + /** + * Record that an export was written and trigger a repaint so the dashboard’s + * "Exports" panel shows the updated timestamp/path. + * + * @param slot - Slot name in {@link ExportStatusMap} (e.g., "error", "warn", etc.). + * @param p - Absolute path to the exported file. + */ + const noteExport = (slot: keyof ExportStatusMap, p: string): void => { + const now = Date.now(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cur: any = exportStatus?.[slot] ?? { path: p }; + if (exportStatus) { + exportStatus[slot] = { + path: p || cur.path, + savedAt: now, + exported: true, + }; + repaint(); + } + }; + + /** + * Show an inline combined log viewer for the selected sources/level. + * Pauses dashboard repaint to keep the viewer visible until the user exits. + * + * @param sources - Log sources to include (e.g., "err", "warn", "info"). + * @param level - Severity level to filter by (e.g., "error", "warn", "all"). + */ + const view = async ( + sources: LogLocation[], + level: ViewLevel, + ): Promise => { + setPaused(true); + try { + await showCombinedLogs(logsBySlot, sources, level); + } finally { + setPaused(false); + repaint(); + } + }; + + /** + * Export combined logs (if an export manager was provided). + * + * @param which - Severity to export (e.g., "error", "warn", "info", "all"). + * @param label - Human-readable label for the export (e.g., "error", "warn"). + */ + const exportCombined = ( + which: 'error' | 'warn' | 'info' | 'all', + label: string, + ): void => { + if (!exportMgr) return; + try { + const p = exportMgr.exportCombinedLogs(logsBySlot, which); + say(`\nWrote combined ${label} logs to: ${p}`); + noteExport(which as keyof ExportStatusMap, p); + } catch { + say(`\nFailed to write combined ${label} logs`); + } + }; + + // The keypress handler the runner will attach to stdin. + return (buf: Buffer): void => { + const s = buf.toString('utf8'); + + // Viewers (lowercase) + if (s === 'e') { + view(['err'], 'error'); + return; + } + if (s === 'w') { + view(['warn', 'err'], 'warn'); + return; + } + if (s === 'i') { + view(['info'], 'all'); + return; + } + if (s === 'l') { + view(['out', 'err', 'structured'], 'all'); + return; + } + + // Exports (uppercase) — enabled only when exportMgr is present + if (s === 'E') { + exportCombined('error', 'error'); + return; + } + if (s === 'W') { + exportCombined('warn', 'warn'); + return; + } + if (s === 'I') { + exportCombined('info', 'info'); + return; + } + if (s === 'A') { + exportCombined('all', 'ALL'); + return; + } + + // Command-specific bindings + const fn = custom?.[s]; + if (fn) { + fn({ noteExport, say }); + return; + } + + // Exit a viewer (Esc / Ctrl+]) — resume dashboard + if (s === '\x1b' || s === '\x1d') { + setPaused(false); + repaint(); + } + }; +} diff --git a/src/lib/pooling/index.ts b/src/lib/pooling/index.ts index 90e08d4a..14d4ea27 100644 --- a/src/lib/pooling/index.ts +++ b/src/lib/pooling/index.ts @@ -2,14 +2,9 @@ export * from './computePoolSize'; export * from './openTerminal'; export * from './ensureLogFile'; export * from './spawnWorkerProcess'; -export * from './attachWorkerHandlers'; export * from './showCombinedLogs'; -export * from './assignWorkToWorker'; -export * from './attachWorkerHandlers'; export * from './installInteractiveSwitcher'; -export * from './diagnostics'; export * from './exportCombinedLogs'; -export * from './ipc'; export * from './logRotation'; export * from './osc8Link'; export * from './keymap'; @@ -17,5 +12,8 @@ export * from './replayFileTailToStdout'; export * from './workerIds'; export * from './os'; export * from './spawnWorkerProcess'; -export * from './workerAssignment'; +export * from './dashboardPlugin'; export * from './safeGetLogPathsForSlot'; +export * from './uiPlugins'; +export * from './types'; +export * from './runPool'; diff --git a/src/lib/pooling/installInteractiveSwitcher.ts b/src/lib/pooling/installInteractiveSwitcher.ts index b05d1b15..50a7a8f9 100644 --- a/src/lib/pooling/installInteractiveSwitcher.ts +++ b/src/lib/pooling/installInteractiveSwitcher.ts @@ -5,6 +5,7 @@ import { replayFileTailToStdout } from './replayFileTailToStdout'; import { keymap } from './keymap'; import { cycleWorkers, getWorkerIds } from './workerIds'; import type { WhichLogs } from './showCombinedLogs'; +import { DEBUG } from '../../constants'; /** * Key action types for the interactive switcher @@ -62,6 +63,18 @@ export function installInteractiveSwitcher(opts: { const stdout = ports?.stdout ?? process.stdout; const stderr = ports?.stderr ?? process.stderr; + const d = (...a: unknown[]): void => { + if (DEBUG) { + try { + (ports?.stderr ?? process.stderr).write( + `[keys] ${a.map(String).join(' ')}\n`, + ); + } catch { + // noop + } + } + }; + if (!stdin.isTTY) { // Not a TTY; return a no-op cleanup return () => { @@ -112,6 +125,8 @@ export function installInteractiveSwitcher(opts: { } const attach = async (id: number): Promise => { + d('attach()', `id=${id}`); // at function entry + const w = workers.get(id); if (!w) return; @@ -124,11 +139,9 @@ export function installInteractiveSwitcher(opts: { // UX: clear + banner onEnterAttachScreen?.(id); - // 1) replay last bytes from logs so you see history - await replayLogs(id); + onAttach?.(id); // prints “Attached to worker …” and clears + await replayLogs(id); // now the tail stays visible - // 2) now mirror live child output to our terminal - onAttach?.(id); // eslint-disable-next-line @typescript-eslint/no-explicit-any outHandler = (chunk: any) => stdout.write(chunk); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -144,6 +157,8 @@ export function installInteractiveSwitcher(opts: { }; const detach = (): void => { + d('detach()', `id=${focus}`); // at function entry + if (focus == null) return; const id = focus; const w = workers.get(id); @@ -159,12 +174,27 @@ export function installInteractiveSwitcher(opts: { }; const onKey = (str: string, key: readline.Key): void => { + d( + 'keypress', + JSON.stringify({ + str, + name: key.name, + seq: key.sequence, + ctrl: key.ctrl, + meta: key.meta, + shift: key.shift, + mode, + }), + ); const act = keymap(str, key, mode); + d('mapped', JSON.stringify(act)); + if (!act) return; // eslint-disable-next-line default-case switch (act.type) { case 'CTRL_C': { + d('CTRL_C'); if (mode === 'attached' && focus != null) { const w = workers.get(focus); try { @@ -181,6 +211,8 @@ export function installInteractiveSwitcher(opts: { } case 'ATTACH': { + d('ATTACH', `id=${act.id}`, `has=${workers.has(act.id)}`); + if (mode !== 'dashboard') return; // eslint-disable-next-line no-void if (workers.has(act.id)) void attach(act.id); @@ -188,6 +220,7 @@ export function installInteractiveSwitcher(opts: { } case 'CYCLE': { + d('CYCLE', `delta=${act.delta}`); if (mode !== 'dashboard') return; const next = cycleWorkers(getWorkerIds(workers), focus, act.delta); // eslint-disable-next-line no-void @@ -202,6 +235,7 @@ export function installInteractiveSwitcher(opts: { } case 'DETACH': { + d('DETACH'); if (mode === 'attached') detach(); return; } diff --git a/src/lib/pooling/ipc.ts b/src/lib/pooling/ipc.ts deleted file mode 100644 index 7c3ccb72..00000000 --- a/src/lib/pooling/ipc.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/** Result message from child → parent */ -export interface WorkerResultMessage { - /** Result type from child → parent */ - type: 'result'; - /** Payload */ - payload: { - /** Whether the task was successful */ - ok: boolean; - /** Filepath processed */ - filePath: string; - /** Error message */ - error?: string; - /** Optional receipts path, if child provided it */ - receiptFilepath?: string; - }; -} - -/** Ready sentinel from child → parent */ -export interface WorkerReadyMessage { - /** Ready message */ - type: 'ready'; -} - -/** Live progress from child → parent (for per-worker bars & throughput) */ -export interface WorkerProgressMessage { - /** Progress message */ - type: 'progress'; - /** Payload */ - payload: { - /** File processed */ - filePath: string; - /** how many just succeeded in this tick */ - successDelta: number; - /** cumulative successes for the current file */ - successTotal: number; - /** total planned records for the current file */ - fileTotal: number; - }; -} - -/** Union of all worker IPC messages */ -export type WorkerMessage = - | WorkerResultMessage - | WorkerReadyMessage - | WorkerProgressMessage; - -/** - * Check if the message is a WorkerReadyMessage. - * - * @param msg - Message to check - * @returns True if the message is a WorkerReadyMessage - */ -export function isWorkerReadyMessage(msg: unknown): msg is WorkerReadyMessage { - return !!msg && typeof msg === 'object' && (msg as any).type === 'ready'; -} - -/** - * Check if the message is a WorkerProgressMessage. - * - * @param msg - Message to check - * @returns True if the message is a WorkerProgressMessage - */ -export function isWorkerProgressMessage( - msg: unknown, -): msg is WorkerProgressMessage { - return ( - !!msg && - typeof msg === 'object' && - (msg as any).type === 'progress' && - typeof (msg as any).payload?.filePath === 'string' - ); -} - -/** - * Check if the message is a WorkerResultMessage. - * - * @param msg - Message to check - * @returns True if the message is a WorkerResultMessage - */ -export function isWorkerResultMessage( - msg: unknown, -): msg is WorkerResultMessage { - return ( - !!msg && - typeof msg === 'object' && - (msg as any).type === 'result' && - typeof (msg as any).payload?.filePath === 'string' && - typeof (msg as any).payload?.ok === 'boolean' - ); -} - -/** Shape of the optional throughput callback */ -export type ProgressInfo = { - /** ID of the worker */ - workerId: number; - /** File path */ - filePath: string; - /** Number of success updates */ - successDelta: number; - /** Total number of success */ - successTotal: number; - /** Total number of items being processed */ - fileTotal: number; -}; -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts index 3847d971..00774b60 100644 --- a/src/lib/pooling/logRotation.ts +++ b/src/lib/pooling/logRotation.ts @@ -239,7 +239,10 @@ export interface ExportArtifactResult { exported?: boolean; } -export interface ExportStatusMap { +/** + * Status map for export artifacts. + */ +export type ExportStatusMap = { /** The absolute paths to the error log artifacts */ error?: ExportArtifactResult; /** The absolute paths to the warn log artifacts */ @@ -250,7 +253,7 @@ export interface ExportStatusMap { all?: ExportArtifactResult; /** The absolute paths to the failures CSV artifacts */ failuresCsv?: ExportArtifactResult; -} +}; /** * Return export statuses diff --git a/src/lib/pooling/runPool.ts b/src/lib/pooling/runPool.ts new file mode 100644 index 00000000..56afff8a --- /dev/null +++ b/src/lib/pooling/runPool.ts @@ -0,0 +1,620 @@ +/* eslint-disable max-lines */ +import colors from 'colors'; +import type { ChildProcess } from 'node:child_process'; +import { RateCounter } from '../helpers'; +import type { SlotState, FromWorker, ToWorker } from './types'; +import { + getWorkerLogPaths, + isIpcOpen, + safeSend, + spawnWorkerProcess, + type WorkerLogPaths, +} from './spawnWorkerProcess'; +import { classifyLogLevel, initLogDir, makeLineSplitter } from './logRotation'; +import { safeGetLogPathsForSlot } from './safeGetLogPathsForSlot'; +import { installInteractiveSwitcher } from './installInteractiveSwitcher'; +import type { ObjByString } from '@transcend-io/type-utils'; + +/** + * Callbacks used by the generic pool orchestrator to: + * - fetch tasks, + * - format labels for UI, + * - fold progress and results into aggregate totals, + * - run optional post-processing once the pool completes. + * + * Each command supplies concrete `TTask`, `TProg`, `TRes`, and optionally a + * custom totals type `TTotals`. + */ +export interface PoolHooks< + TTask extends ObjByString, + TProg extends ObjByString, + TRes extends ObjByString, + TTotals = unknown, +> { + /** + * Produce the next work item for a slot. + * + * @returns The next task or `undefined` if no tasks remain. + */ + nextTask: () => TTask | undefined; + + /** + * Human-readable label for a task, shown in dashboards. + * + * @param t - The task to label. + * @returns A short descriptor, typically a file path or identifier. + */ + taskLabel: (t: TTask) => string; + + /** + * Fold an incoming progress payload into aggregate totals. + * Should be pure (no side effects) and return the new totals object. + * + * @param prevTotals - The previous totals value. + * @param prog - The latest progress payload from a worker. + * @returns Updated totals. + */ + onProgress: (prevTotals: TTotals, prog: TProg) => TTotals; + + /** + * Handle a final result from a worker. + * Should be pure and return the new totals plus a boolean indicating if the + * unit succeeded (used to set per-slot level/metrics). + * + * @param prevTotals - The previous totals value. + * @param res - The result payload from a worker. + * @returns Object containing updated totals and success flag. + */ + onResult: ( + prevTotals: TTotals, + res: TRes, + ) => { + /** Updated totals after processing this result */ + totals: TTotals; + /** Whether the task was successful */ + ok: boolean; + }; + + /** + * Initialize per-slot progress state when a task is assigned. + * Useful when you want a non-undefined `progress` immediately. + * + * @param t - The task to be started in this slot. + * @returns Initial progress state or `undefined`. + */ + initSlotProgress?: (t: TTask) => TProg | undefined; + + /** + * Produce the initial totals value for the pool (defaults to `{}`). + * + * @returns A new totals object. + */ + initTotals?: () => TTotals; + + /** + * Provide an export status map for dashboards (optional). + * + * @returns A status object or `undefined` if not applicable. + */ + exportStatus?: () => Record | undefined; + + /** + * Optional post-processing step invoked after the pool finishes. + * Common use: writing combined logs/artifacts once all workers complete. + * + * When {@link RunPoolOptions.viewerMode} is enabled, the runner also passes + * the **log directory** and the **per-slot log file paths** so you can + * replicate the legacy “viewer mode” auto-exports (combined logs, indices, etc.). + */ + postProcess?: (ctx: { + /** Live snapshot of all worker slots at completion. */ + slots: Map>; + /** Final aggregate totals. */ + totals: TTotals; + /** Absolute path to the pool’s log directory. */ + logDir: string; + /** + * Mapping of slot id -> log paths (stdout/stderr/current, rotations may exist). + * Use this to collect and export artifacts after completion. + */ + logsBySlot: Map; + /** Unix millis when the pool started (first worker spawned). */ + startedAt: number; + /** Unix millis when the pool fully completed (after last worker exit). */ + finishedAt: number; + /** + * Helper to safely re-fetch a slot’s current log paths, accounting for respawns. + * Mirrors the dashboard’s attach/switcher behavior. + */ + getLogPathsForSlot: (id: number) => WorkerLogPaths | undefined; + /** True if the pool was run in viewerMode (non-interactive). */ + viewerMode: boolean; + }) => Promise | void; +} + +/** + * Options to run a generic worker pool. + * + * @template TTask - The payload sent to each worker as a "task". + * @template TProg - The progress payload emitted by workers. + * @template TRes - The result payload emitted by workers. + * @template TTotals - The aggregate totals object maintained by hooks. + */ +export interface RunPoolOptions< + TTask extends ObjByString, + TProg extends ObjByString, + TRes extends ObjByString, + TTotals extends ObjByString, +> { + /** Human-readable name for the pool, shown in headers (e.g., "Parallel uploader", "Chunk CSV"). */ + title: string; + + /** + * Directory for pool-local state (logs, discovery messages, artifacts). + * Usually the CLI's working directory for the command. + */ + baseDir: string; + + /** Absolute path of the module the child should execute (the command impl that calls runChild when CHILD_FLAG is present). */ + childModulePath: string; + + /** + * Number of worker processes to spawn. Typically derived via a helper like `computePoolSize`. + */ + poolSize: number; + + /** Logical CPU count used for display only (not required to equal `poolSize`). */ + cpuCount: number; + + /** + * Flag that the child module expects to see in `process.argv` to run in "worker" mode. + * This MUST match the flag the worker module checks (e.g., `--as-child`). + */ + childFlag: string; + + /** + * Renderer function injected by the command. The runner calls this on each "tick" + * and on significant state changes (progress, completion, attach/detach). + */ + render: (input: { + /** Header/title for the UI. */ + title: string; + /** Configured pool size (number of workers). */ + poolSize: number; + /** CPU count for informational display. */ + cpuCount: number; + /** Total number of files/tasks anticipated by the command. */ + filesTotal: number; + /** Number of files/tasks that have produced a successful result so far. */ + filesCompleted: number; + /** Number of files/tasks that have produced a failed result so far. */ + filesFailed: number; + /** + * Per-slot state for each worker, including busy flag, file label, start time, + * last log level badge, and optional progress payload. + */ + workerState: Map>; + /** + * Arbitrary totals object maintained by hooks. This is the primary place to surface + * domain-specific aggregate metrics in the UI. + */ + totals: TTotals; + /** + * Smoothed throughput metrics computed by the runner: + * - successSoFar: convenience mirror of completed count for the renderer + * - r10s: moving average of completions per second over ~10 seconds + * - r60s: moving average of completions per second over ~60 seconds + */ + throughput: { + /** Convenience mirror of `filesCompleted` for renderers that expect it in this block. */ + successSoFar: number; + /** Moving average completions/sec (10s window). */ + r10s: number; + /** Moving average completions/sec (60s window). */ + r60s: number; + }; + /** True when the pool has fully drained and all workers have exited. */ + final: boolean; + /** + * Optional export status payload surfaced by hooks; used by commands that generate + * multiple artifact files and want to show "latest paths" in the UI. + */ + exportStatus?: Record; + }) => void; + + /** + * Hook suite that adapts the pool to a specific command: + * - nextTask(): TTask | undefined + * - taskLabel(task): string + * - initTotals?(): TTotals + * - initSlotProgress?(task): TProg + * - onProgress(totals, prog): TTotals + * - onResult(totals, res): { totals: TTotals; ok: boolean } + * - postProcess?({ slots, totals, logDir, logsBySlot, ... }): Promise | void + * - exportStatus?(): Record + */ + hooks: PoolHooks; + + /** + * Total number of "files" or logical items the command expects to process. + * Used purely for UI/ETA; does not affect scheduling. + */ + filesTotal: number; + + /** Open worker logs in new terminals (macOS). Default true unless viewerMode=true. */ + openLogWindows?: boolean; + + /** Silence worker stdio (except logs). */ + isSilent?: boolean; + + /** + * When true, run in “viewer mode” (non-interactive): + * - Do NOT install the interactive attach/switcher. + * - Default `openLogWindows` to false. + * - Still render on a timer. + * - Provide `logDir`/`logsBySlot` to `postProcess` for auto-exports. + */ + viewerMode?: boolean; + + /** + * Optional factory for additional key bindings (e.g., log viewers/exports). + * Only used when viewerMode === false. + */ + extraKeyHandler?: (args: { + /** per-slot log paths (kept up-to-date across respawns) */ + logsBySlot: Map; + /** re-render dashboard now */ + repaint: () => void; + /** pause/unpause dashboard repaint while showing viewers */ + setPaused: (p: boolean) => void; + }) => (buf: Buffer) => void; +} + +/** + * Run a multi-process worker pool for a command. + * The runner owns: spawning workers, assigning tasks, collecting progress/results, + * basic log badging (WARN/ERROR), an interactive attach/switcher (unless viewerMode), + * and a render loop. + * + * The command injects "hooks" to customize scheduling and totals aggregation. + * + * @param opts - Options + */ +export async function runPool< + TTask extends ObjByString, + TProg extends ObjByString, + TRes extends ObjByString, + TTotals extends ObjByString, +>(opts: RunPoolOptions): Promise { + const { + title, + baseDir, + poolSize, + cpuCount, + render, + childModulePath, + hooks, + filesTotal, + childFlag, + viewerMode = false, + } = opts; + + // Default behaviors may change under viewerMode. + const openLogWindows = opts.openLogWindows ?? !viewerMode; + const isSilent = opts.isSilent ?? true; + + const startedAt = Date.now(); + const logDir = initLogDir(baseDir); + + /** Live worker processes keyed by slot id. */ + const workers = new Map(); + /** Per-slot state tracked for the UI and scheduling. */ + const workerState = new Map>(); + /** File paths for each worker’s stdout/stderr logs. */ + const slotLogs = new Map(); + /** Completion throughput meter. */ const meter = new RateCounter(); + const totalsInit = (hooks.initTotals?.() ?? {}) as TTotals; + + let totalsBox = totalsInit; + let activeWorkers = 0; + let completed = 0; + let failed = 0; + + // Repaint ticker starts on first READY to avoid double-first-render. + let ticker: NodeJS.Timeout | null = null; + let firstReady = false; + // Gate repaint during popup viewers/exports (driven by extraKeyHandler). + let paused = false; + // Keep a reference so we can unbind on exit. + let extraHandler: ((buf: Buffer) => void) | null = null; + + /** + * Paint the UI. The renderer is intentionally pure and receives + * a snapshot of current state. + * + * @param final - If true, render the final state and exit. + */ + const repaint = (final = false): void => { + if (paused) return; + render({ + title, + poolSize, + cpuCount, + filesTotal, + filesCompleted: completed, + filesFailed: failed, + workerState, + totals: totalsBox, + final, + exportStatus: hooks.exportStatus?.(), + throughput: { + successSoFar: completed, + r10s: meter.rate(10_000), + r60s: meter.rate(60_000), + }, + }); + }; + + /** + * Assign the next task to `id` if available. + * + * @param id - The worker slot id to assign a task to. + * @returns true if a task was assigned. + * + * NOTE: This is the critical fix. We **do not** "peek & put back" a task. + * We only consume via `nextTask()` inside this function. + */ + const assign = (id: number): boolean => { + const task = hooks.nextTask(); + if (!task) return false; + + const child = workers.get(id)!; + const label = hooks.taskLabel(task); + const initialProg = hooks.initSlotProgress?.(task); + + workerState.set(id, { + busy: true, + file: label, + startedAt: Date.now(), + lastLevel: 'ok', + progress: initialProg, + }); + + safeSend(child, { type: 'task', payload: task } as ToWorker); + repaint(); + return true; + }; + + /* Spawn workers */ + for (let i = 0; i < poolSize; i += 1) { + const child = spawnWorkerProcess({ + id: i, + modulePath: childModulePath, + logDir, + openLogWindows, + isSilent, + childFlag, + }); + workers.set(i, child); + workerState.set(i, { + busy: false, + file: null, + startedAt: null, + lastLevel: 'ok', + }); + slotLogs.set(i, getWorkerLogPaths(child)); + activeWorkers += 1; + + // badge WARN/ERROR quickly from stderr + const errLine = makeLineSplitter((line) => { + const lvl = classifyLogLevel(line); + if (!lvl) return; + const prev = workerState.get(i)!; + if (prev.lastLevel !== lvl) { + workerState.set(i, { ...prev, lastLevel: lvl }); + repaint(); + } + }); + child.stderr?.on('data', errLine); + + // messages from the worker + // eslint-disable-next-line no-loop-func + child.on('message', (msg: FromWorker) => { + if (!msg || typeof msg !== 'object') return; + + if (msg.type === 'ready') { + if (!firstReady) { + firstReady = true; + ticker = setInterval(() => repaint(false), 350); + } + assign(i); // try to start work immediately + return; + } + + if (msg.type === 'progress') { + totalsBox = hooks.onProgress(totalsBox, msg.payload); + const prev = workerState.get(i)!; + workerState.set(i, { ...prev, progress: msg.payload }); + repaint(); + return; + } + + if (msg.type === 'result') { + const prev = workerState.get(i)!; + const { totals: t2, ok } = hooks.onResult(totalsBox, msg.payload); + totalsBox = t2; + + if (ok) { + completed += 1; + meter.add(1); + } else { + failed += 1; + } + + workerState.set(i, { + ...prev, + busy: false, + file: null, + progress: undefined, + lastLevel: ok ? 'ok' : 'error', + }); + + // Just try to assign; if none left, shut this child down. + if (!assign(i) && isIpcOpen(child)) { + safeSend(child, { type: 'shutdown' } as ToWorker); + } + repaint(); + } + }); + + // eslint-disable-next-line no-loop-func + child.on('exit', () => { + activeWorkers -= 1; + if (activeWorkers === 0) { + if (ticker) clearInterval(ticker); + repaint(true); + } + }); + } + + /* Interactive attach/switcher */ + let cleanupSwitcher: () => void = () => { + /* noop */ + // no-op by default, overridden in non-viewerMode + }; + + const tearDownStdin = (): void => { + try { + process.stdin.setRawMode?.(false); + } catch { + /* noop */ + } + try { + process.stdin.pause(); + } catch { + /* noop */ + } + }; + + const onSigint = (): void => { + if (ticker) clearInterval(ticker); + cleanupSwitcher?.(); + if (extraHandler) { + try { + process.stdin.off('data', extraHandler); + } catch { + /* noop */ + } + } + tearDownStdin(); + + process.stdout.write('\nStopping workers...\n'); + for (const [, w] of workers) { + if (isIpcOpen(w)) safeSend(w, { type: 'shutdown' } as ToWorker); + try { + w?.kill('SIGTERM'); + } catch { + /* noop */ + } + } + process.exit(130); + }; + + const onAttach = (id: number): void => { + paused = true; // stop dashboard repaint while attached/viewing + process.stdout.write('\x1b[2J\x1b[H'); // clear + home + process.stdout.write( + `Attached to worker ${id}. (Esc/Ctrl+] detach • Ctrl+D EOF • Ctrl+C SIGINT)\n`, + ); + }; + const onDetach = (): void => { + paused = false; + repaint(); + }; + + process.once('SIGINT', onSigint); + + if (!viewerMode) { + if (process.stdin.isTTY) { + try { + process.stdin.setRawMode(true); + } catch { + process.stdout.write( + colors.yellow( + 'Warning: Unable to enable raw mode for interactive key handling.\n', + ), + ); + } + process.stdin.resume(); // keep stdin flowing (no encoding — raw Buffer) + } + + cleanupSwitcher = installInteractiveSwitcher({ + workers, + onAttach, + onDetach, + onCtrlC: onSigint, + getLogPaths: (id) => safeGetLogPathsForSlot(id, workers, slotLogs), + replayBytes: 200 * 1024, + replayWhich: ['out', 'err'], + onEnterAttachScreen: onAttach, + }); + + if (opts.extraKeyHandler) { + extraHandler = opts.extraKeyHandler({ + logsBySlot: slotLogs, + repaint: () => repaint(), + setPaused: (p) => { + paused = p; + }, + }); + process.stdin.on('data', extraHandler); + } + } + + /* Wait for full completion, then post-process (with log context if needed). */ + await new Promise((resolve) => { + const check = setInterval(async () => { + if (activeWorkers === 0) { + clearInterval(check); + if (ticker) clearInterval(ticker); + cleanupSwitcher(); + + if (extraHandler) { + try { + process.stdin.off('data', extraHandler); + } catch { + /* noop */ + } + } + tearDownStdin(); + + const finishedAt = Date.now(); + + try { + await hooks.postProcess?.({ + slots: workerState, + totals: totalsBox, + logDir, + logsBySlot: slotLogs, + startedAt, + finishedAt, + viewerMode, + getLogPathsForSlot: (id: number) => + safeGetLogPathsForSlot(id, workers, slotLogs), + }); + } catch (err: unknown) { + const msg = + ( + err as { + /** Error stack */ + stack?: string; + } + )?.stack ?? String(err); + process.stdout.write(colors.red(`postProcess error: ${msg}\n`)); + } + resolve(); + } + }, 300); + }); +} +/* eslint-enable max-lines */ diff --git a/src/lib/pooling/spawnWorkerProcess.ts b/src/lib/pooling/spawnWorkerProcess.ts index ff542e6e..f929a771 100644 --- a/src/lib/pooling/spawnWorkerProcess.ts +++ b/src/lib/pooling/spawnWorkerProcess.ts @@ -5,7 +5,8 @@ import { openLogTailWindowMulti } from './openTerminal'; import { ensureLogFile } from './ensureLogFile'; import { classifyLogLevel, makeLineSplitter } from './logRotation'; -export const CHILD_FLAG = '--child-upload-preferences'; +/** Default child-flag used if a caller doesn’t provide one. */ +export const CHILD_FLAG = '--as-child'; // Symbol key so we can stash/retrieve paths on the child proc safely const LOG_PATHS_SYM: unique symbol = Symbol('workerLogPaths'); @@ -180,7 +181,7 @@ export function spawnWorkerProcess(opts: SpawnWorkerOptions): ChildProcess { try { if (lvl === 'error') { errorStream.write(`${line}\n`); - } else if (lvl === 'warn' || lvl == null) { + } else { // Treat untagged stderr as WARN by default (common in libs) warnStream.write(`${line}\n`); } diff --git a/src/lib/pooling/tests/appendFailureLog.test.ts b/src/lib/pooling/tests/appendFailureLog.test.ts deleted file mode 100644 index 94ad5abb..00000000 --- a/src/lib/pooling/tests/appendFailureLog.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { join } from 'node:path'; - -import { appendFailureLog } from '../diagnostics'; -import { appendFileSync } from 'node:fs'; - -/** - * Mock fs BEFORE importing the SUT. - */ -vi.mock('node:fs', () => ({ - appendFileSync: vi.fn(), -})); - -const mockedAppend = vi.mocked(appendFileSync); - -describe('appendFailureLog', () => { - const FIXED = new Date('2025-01-02T03:04:05.000Z'); - - beforeEach(() => { - vi.clearAllMocks(); - vi.useFakeTimers(); - vi.setSystemTime(FIXED); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it('writes a formatted failure entry to /failures.log', () => { - const logDir = '/var/logs'; - const workerId = 7; - const filePath = '/tmp/file.csv'; - const errorMsg = 'Trace: boom\nat fn()'; - - appendFailureLog(logDir, workerId, filePath, errorMsg); - - const expectedPath = join(logDir, 'failures.log'); - const expectedBody = `[${FIXED.toISOString()}] worker ${workerId} file=${filePath}\n${errorMsg}\n\n`; - - expect(mockedAppend).toHaveBeenCalledTimes(1); - expect(mockedAppend).toHaveBeenCalledWith(expectedPath, expectedBody); - }); - - it('swallows append errors (does not throw)', () => { - mockedAppend.mockImplementationOnce(() => { - throw new Error('disk full'); - }); - - expect(() => appendFailureLog('/logs', 1, '/f.csv', 'err')).not.toThrow(); - }); -}); diff --git a/src/lib/pooling/tests/assignWorkToSlot.test.ts b/src/lib/pooling/tests/assignWorkToSlot.test.ts deleted file mode 100644 index fa6e9bee..00000000 --- a/src/lib/pooling/tests/assignWorkToSlot.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import type { ChildProcess } from 'node:child_process'; - -import { assignWorkToSlot, type WorkerMaps } from '../workerAssignment'; -import { isIpcOpen, safeSend } from '../spawnWorkerProcess'; - -/** - * Mock collaborators BEFORE importing the SUT. - * Use an inline factory to avoid Vitest hoisting pitfalls. - * - * IMPORTANT: The mock path MUST match the specifier used by the SUT after resolution. - * Since workerAssignment.ts imports from './spawnWorkerProcess', and this test lives - * in ../tests, the correct mock specifier here is '../spawnWorkerProcess'. - */ -vi.mock('../spawnWorkerProcess', () => ({ - isIpcOpen: vi.fn(), - safeSend: vi.fn(), -})); - -const mockedIsOpen = vi.mocked(isIpcOpen); -const mockedSafeSend = vi.mocked(safeSend); - -describe('assignWorkToSlot', () => { - const NOW = 1_700_000_000_000; - let nowSpy: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - nowSpy = vi.spyOn(Date, 'now').mockReturnValue(NOW); - }); - - afterEach(() => { - nowSpy.mockRestore(); - }); - - /** - * Create minimal WorkerMaps with a single worker slot. - * - * @param id - worker id - * @param prevLastLevel - lastLevel to pre-seed in workerState (useful to check preservation) - * @returns initialized maps for testing - */ - function makeMaps( - id = 1, - prevLastLevel: 'ok' | 'warn' | 'error' = 'warn', - ): WorkerMaps { - const workers = new Map(); - // minimal "child" — we only need a shape to put into the map - const child = {} as unknown as ChildProcess; - workers.set(id, child); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const workerState = new Map(); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prevLastLevel, - progress: undefined, - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const slotLogPaths = new Map(); - - return { - workers, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - workerState: workerState as unknown as Map, - slotLogPaths, - } as unknown as WorkerMaps; - } - - it('marks idle and preserves lastLevel when IPC is closed (does not shift queue or send)', () => { - mockedIsOpen.mockReturnValue(false); - - const filesQueue = ['a.csv']; - const maps = makeMaps(1, 'warn'); - - assignWorkToSlot(1, filesQueue, { opt: 1 }, maps); - - // lastLevel preserved from previous - expect(maps.workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', - progress: undefined, - }); - - // queue untouched, no send - expect(filesQueue).toEqual(['a.csv']); - expect(mockedSafeSend).not.toHaveBeenCalled(); - }); - - it('idles when no files are pending (IPC open), preserving lastLevel', () => { - mockedIsOpen.mockReturnValue(true); - - const filesQueue: string[] = []; - const maps = makeMaps(1, 'error'); - - assignWorkToSlot(1, filesQueue, {}, maps); - - expect(maps.workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }); - expect(mockedSafeSend).not.toHaveBeenCalled(); - }); - - it('assigns next file, sets busy with startedAt, and calls safeSend', () => { - mockedIsOpen.mockReturnValue(true); - mockedSafeSend.mockReturnValue(true); - - const filesQueue = ['/data/f1.csv', '/data/f2.csv']; - const maps = makeMaps(); - - const commonOpts = { skip: true }; - assignWorkToSlot(1, filesQueue, commonOpts, maps); - - // state set to busy with the first file - expect(maps.workerState.get(1)).toMatchObject({ - busy: true, - file: '/data/f1.csv', - startedAt: NOW, - lastLevel: 'ok', - progress: undefined, - }); - - // consumed first item - expect(filesQueue).toEqual(['/data/f2.csv']); - - // correct message sent via safeSend - expect(mockedSafeSend).toHaveBeenCalledTimes(1); - expect(mockedSafeSend).toHaveBeenCalledWith(expect.any(Object), { - type: 'task', - payload: { filePath: '/data/f1.csv', options: commonOpts }, - }); - }); - - it('requeues and idles if safeSend returns false (IPC closed mid-send)', () => { - mockedIsOpen.mockReturnValue(true); - mockedSafeSend.mockReturnValue(false); - - const filesQueue = ['/data/one.csv', '/data/two.csv']; - const maps = makeMaps(1, 'warn'); - - assignWorkToSlot(1, filesQueue, { anything: true }, maps); - - // file requeued to the front (queue restored) - expect(filesQueue).toEqual(['/data/one.csv', '/data/two.csv']); - - // worker ends idle; note: lastLevel becomes 'ok' because the busy state set it before failure - expect(maps.workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'ok', - progress: undefined, - }); - }); - - it('handles missing worker like closed IPC (marks idle, preserves lastLevel)', () => { - // isIpcOpen( undefined ) → false - mockedIsOpen.mockReturnValue(false); - - const filesQueue = ['x.csv']; - const maps = makeMaps(1, 'error'); - maps.workers.delete(1); // simulate missing worker - - assignWorkToSlot(1, filesQueue, {}, maps); - - expect(maps.workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }); - expect(filesQueue).toEqual(['x.csv']); - expect(mockedSafeSend).not.toHaveBeenCalled(); - }); -}); diff --git a/src/lib/pooling/tests/assignWorkToWorker.test.ts b/src/lib/pooling/tests/assignWorkToWorker.test.ts deleted file mode 100644 index 9af1ef1b..00000000 --- a/src/lib/pooling/tests/assignWorkToWorker.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -// assignWorkToWorker.test.ts -import { - describe, - it, - expect, - vi, - beforeEach, - afterEach, - type Mock, -} from 'vitest'; -import type { ChildProcess } from 'node:child_process'; - -import { assignWorkToWorker, type WorkerState } from '../assignWorkToWorker'; -import { isIpcOpen } from '../spawnWorkerProcess'; - -// Mock FIRST, using the path relative to THIS test file -vi.mock('../spawnWorkerProcess', () => ({ - isIpcOpen: vi.fn(), -})); - -const mockedIsIpcOpen = vi.mocked(isIpcOpen); - -describe('assignWorkToWorker', () => { - const NOW = 1_700_000_000_000; - let nowSpy: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - nowSpy = vi.spyOn(Date, 'now').mockReturnValue(NOW); - }); - - afterEach(() => { - nowSpy.mockRestore(); - }); - - /** - * Creates maps for workers and workerState, with optional previous state and send implementation. - * - * @param workerId - ID of the worker to create - * @param opts - Optional parameters: - * @returns An object containing: - */ - function makeMaps( - // eslint-disable-next-line default-param-last - workerId = 1, - opts?: { - /** Worker previous state */ - prevState?: WorkerState; - /** Function to mock the send method */ - sendImpl?: (msg: unknown) => unknown; - }, - ): { - /** Worker processes map */ - workers: Map; - /** Worker state map */ - workerState: Map; - /** Repaint function */ - repaint: Mock; - /** Send function */ - send: Mock; - } { - const workers = new Map(); - const workerState = new Map(); - const repaint = vi.fn(); - - const send = vi.fn(opts?.sendImpl ?? (() => true)); - const fakeProc = { send } as unknown as ChildProcess; - - workers.set(workerId, fakeProc); - if (opts?.prevState) workerState.set(workerId, opts.prevState); - - return { workers, workerState, repaint, send }; - } - - it('marks worker idle and preserves lastLevel when IPC is closed (no repaint, no send)', () => { - mockedIsIpcOpen.mockReturnValue(false); - - const pending = ['a.csv']; - const { workers, workerState, repaint, send } = makeMaps(1, { - prevState: { - busy: true, - file: '/tmp/old.csv', - startedAt: 42, - lastLevel: 'warn', - progress: { processed: 1, total: 10 }, - }, - }); - - assignWorkToWorker( - 1, - { some: 'payload' }, - { pending, workers, workerState, repaint }, - ); - - expect(workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', - progress: undefined, - }); - expect(repaint).not.toHaveBeenCalled(); - expect(send).not.toHaveBeenCalled(); - expect(pending).toEqual(['a.csv']); - }); - - it('idles and repaints when no pending work (IPC open), preserving lastLevel', () => { - mockedIsIpcOpen.mockReturnValue(true); - - const pending: string[] = []; - const { workers, workerState, repaint, send } = makeMaps(1, { - prevState: { - busy: true, - file: '/tmp/old.csv', - startedAt: 10, - lastLevel: 'error', - } as WorkerState, - }); - - assignWorkToWorker( - 1, - { foo: 'bar' }, - { pending, workers, workerState, repaint }, - ); - - expect(workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }); - expect(repaint).toHaveBeenCalledTimes(1); - expect(send).not.toHaveBeenCalled(); - }); - - it('assigns next file, repaints once, and sends task when IPC open and work pending', () => { - mockedIsIpcOpen.mockReturnValue(true); - - const pending = ['/data/f1.csv', '/data/f2.csv']; - const payload = { skipWorkflowTriggers: true }; - - const { workers, workerState, repaint, send } = makeMaps(); - - assignWorkToWorker(1, payload, { pending, workers, workerState, repaint }); - - expect(workerState.get(1)).toMatchObject({ - busy: true, - file: '/data/f1.csv', - startedAt: NOW, - lastLevel: 'ok', - progress: undefined, - }); - expect(pending).toEqual(['/data/f2.csv']); - expect(repaint).toHaveBeenCalledTimes(1); - expect(send).toHaveBeenCalledWith({ - type: 'task', - payload: { filePath: '/data/f1.csv', options: payload }, - }); - }); - - it.each([ - { - label: 'ERR_IPC_CHANNEL_CLOSED', - err: Object.assign(new Error('closed'), { - code: 'ERR_IPC_CHANNEL_CLOSED', - }), - }, - { - label: 'EPIPE', - err: Object.assign(new Error('broken'), { code: 'EPIPE' }), - }, - { - label: 'errno -32', - err: Object.assign(new Error('broken'), { errno: -32 }), - }, - ])( - 'requeues and idles if send fails with recoverable error (%s)', - ({ err }) => { - mockedIsIpcOpen.mockReturnValue(true); - - const pending = ['/data/f1.csv', '/data/f2.csv']; - const { workers, workerState, repaint, send } = makeMaps(1, { - prevState: { - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', - } as WorkerState, - sendImpl: () => { - throw err; - }, - }); - - assignWorkToWorker( - 1, - { any: 'opts' }, - { pending, workers, workerState, repaint }, - ); - - expect(pending).toEqual(['/data/f1.csv', '/data/f2.csv']); // requeued to front - expect(workerState.get(1)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', - progress: undefined, - }); - expect(repaint).toHaveBeenCalledTimes(2); - expect(send).toHaveBeenCalledTimes(1); - }, - ); - - it('throws unknown send errors (does not requeue)', () => { - mockedIsIpcOpen.mockReturnValue(true); - - const pending = ['/data/f1.csv', '/data/f2.csv']; - const unknownErr = new Error('kaboom'); - const { workers, workerState, repaint, send } = makeMaps(1, { - sendImpl: () => { - throw unknownErr; - }, - }); - - expect(() => - assignWorkToWorker(1, {}, { pending, workers, workerState, repaint }), - ).toThrow(unknownErr); - - expect(pending).toEqual(['/data/f2.csv']); // consumed, not requeued - expect(workerState.get(1)).toMatchObject({ - busy: true, - file: '/data/f1.csv', - startedAt: NOW, - lastLevel: 'ok', - }); - expect(repaint).toHaveBeenCalledTimes(1); - expect(send).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/lib/pooling/tests/attachWorkerHandlers.test.ts b/src/lib/pooling/tests/attachWorkerHandlers.test.ts deleted file mode 100644 index 18f74acd..00000000 --- a/src/lib/pooling/tests/attachWorkerHandlers.test.ts +++ /dev/null @@ -1,438 +0,0 @@ -/* eslint-disable max-lines */ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import type { ChildProcess } from 'node:child_process'; -import { EventEmitter } from 'node:events'; - -import { attachWorkerHandlers } from '../attachWorkerHandlers'; -import { spawnWorkerProcess } from '../spawnWorkerProcess'; -import { assignWorkToWorker } from '../assignWorkToWorker'; -import { - isWorkerReadyMessage, - isWorkerProgressMessage, - isWorkerResultMessage, -} from '../ipc'; -import { wireStderrBadges, appendFailureLog } from '../diagnostics'; - -/** - * IMPORTANT: - * - We must mock all collaborators BEFORE importing the SUT. - * - Mock paths are relative to THIS test file's location. - */ -vi.mock('../spawnWorkerProcess', () => ({ - spawnWorkerProcess: vi.fn(), -})); - -vi.mock('../assignWorkToWorker', () => ({ - assignWorkToWorker: vi.fn(), -})); - -vi.mock('../ipc', () => ({ - isWorkerReadyMessage: vi.fn(), - isWorkerProgressMessage: vi.fn(), - isWorkerResultMessage: vi.fn(), -})); - -vi.mock('../diagnostics', () => ({ - wireStderrBadges: vi.fn(), - appendFailureLog: vi.fn(), -})); - -const mockedSpawn = vi.mocked(spawnWorkerProcess); -const mockedAssign = vi.mocked(assignWorkToWorker); -const mockedReady = vi.mocked(isWorkerReadyMessage); -const mockedProgress = vi.mocked(isWorkerProgressMessage); -const mockedResult = vi.mocked(isWorkerResultMessage); -const mockedWireBadges = vi.mocked(wireStderrBadges); -const mockedAppendFailure = vi.mocked(appendFailureLog); - -/** - * Create a minimal ChildProcess-like object using EventEmitter - * - * @returns Child process mock - */ -function makeChild(): ChildProcess { - return new EventEmitter() as unknown as ChildProcess; -} - -describe('attachWorkerHandlers', () => { - const NOW = 1_700_000_000_000; - let nowSpy: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - nowSpy = vi.spyOn(Date, 'now').mockReturnValue(NOW); - }); - - afterEach(() => { - nowSpy.mockRestore(); - }); - - it('registers worker, initializes state (preserving lastLevel), and wires stderr badges', () => { - const id = 1; - const child = makeChild(); - - const workers = new Map(); - const workerState = new Map(); - // Simulate previous crash badge to verify preservation - workerState.set(id, { - busy: true, - file: '/tmp/old.csv', - startedAt: 123, - lastLevel: 'warn', - progress: { processed: 1, total: 10 }, - }); - - const state = { completed: 0, failed: 0 }; - const filesPending: string[] = []; - const repaint = vi.fn(); - const onAllWorkersExited = vi.fn(); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: { any: 'opts' }, - onAllWorkersExited, - logDir: '/logs', - modulePath: '/worker.js', - spawnSilent: false, - }); - - expect(workers.get(id)).toBe(child); - expect(workerState.get(id)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'warn', // preserved from previous - progress: undefined, - }); - expect(mockedWireBadges).toHaveBeenCalledWith( - id, - child, - workerState, - repaint, - ); - }); - - it('on "ready": assigns work to the worker', () => { - const id = 1; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending = ['/f1.csv']; - const repaint = vi.fn(); - - const common = { payload: true }; - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common, - onAllWorkersExited: vi.fn(), - logDir: '/logs', - modulePath: '/worker.js', - }); - - mockedReady.mockReturnValue(true); - mockedProgress.mockReturnValue(false); - mockedResult.mockReturnValue(false); - - // Emit a message that passes the "ready" guard - child.emit('message', { anything: true }); - - expect(mockedAssign).toHaveBeenCalledWith(id, common, { - pending: filesPending, - workers, - workerState, - repaint, - }); - }); - - it('on "progress": updates per-worker progress, calls aggregator if successDelta, and repaints', () => { - const id = 2; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending: string[] = []; - const repaint = vi.fn(); - const onProgress = vi.fn(); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: {}, - onAllWorkersExited: vi.fn(), - logDir: '/logs', - modulePath: '/worker.js', - onProgress, - }); - - mockedReady.mockReturnValue(false); - mockedProgress.mockReturnValue(true); - mockedResult.mockReturnValue(false); - - // First tick with deltas triggers aggregator - child.emit('message', { - payload: { - filePath: '/data/fileA.csv', - successDelta: 5, - successTotal: 10, - fileTotal: 100, - }, - }); - - expect(workerState.get(id)).toMatchObject({ - file: '/data/fileA.csv', - progress: { processed: 10, total: 100 }, - }); - expect(onProgress).toHaveBeenCalledWith({ - workerId: id, - filePath: '/data/fileA.csv', - successDelta: 5, - successTotal: 10, - fileTotal: 100, - }); - expect(repaint).toHaveBeenCalled(); - - // Second tick without delta should NOT call aggregator - onProgress.mockClear(); - child.emit('message', { - payload: { - filePath: '/data/fileA.csv', - successDelta: 0, - successTotal: 12, - fileTotal: 100, - }, - }); - expect(onProgress).not.toHaveBeenCalled(); - }); - - it('on "result" ok: increments completed, clears state with ok badge, repaints, and assigns next work', () => { - const id = 3; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending = ['/next.csv']; - const repaint = vi.fn(); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: { foo: 'bar' }, - onAllWorkersExited: vi.fn(), - logDir: '/logs', - modulePath: '/worker.js', - }); - - mockedReady.mockReturnValue(false); - mockedProgress.mockReturnValue(false); - mockedResult.mockReturnValue(true); - - child.emit('message', { - payload: { ok: true, filePath: '/data/fileA.csv' }, - }); - - expect(state.completed).toBe(1); - expect(state.failed).toBe(0); - - expect(workerState.get(id)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'ok', - progress: undefined, - }); - - expect(repaint).toHaveBeenCalled(); - expect(mockedAppendFailure).not.toHaveBeenCalled(); - - expect(mockedAssign).toHaveBeenCalled(); // keep worker busy while queue non-empty - }); - - it('on "result" failure: increments failed, sets error badge, logs failure, repaints, and assigns next work', () => { - const id = 4; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending = ['/todo.csv']; - const repaint = vi.fn(); - const logDir = '/logs'; - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: {}, - onAllWorkersExited: vi.fn(), - logDir, - modulePath: '/worker.js', - }); - - mockedReady.mockReturnValue(false); - mockedProgress.mockReturnValue(false); - mockedResult.mockReturnValue(true); - - child.emit('message', { - payload: { ok: false, filePath: '/data/fileB.csv', error: 'boom' }, - }); - - expect(state.completed).toBe(0); - expect(state.failed).toBe(1); - expect(workerState.get(id)).toEqual({ - busy: false, - file: null, - startedAt: null, - lastLevel: 'error', - progress: undefined, - }); - expect(repaint).toHaveBeenCalled(); - expect(mockedAppendFailure).toHaveBeenCalledWith( - logDir, - id, - '/data/fileB.csv', - 'boom', - ); - expect(mockedAssign).toHaveBeenCalled(); - }); - - it('ignores unknown messages (no guards match)', () => { - const id = 5; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending: string[] = []; - const repaint = vi.fn(); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: {}, - onAllWorkersExited: vi.fn(), - logDir: '/logs', - modulePath: '/worker.js', - }); - - mockedReady.mockReturnValue(false); - mockedProgress.mockReturnValue(false); - mockedResult.mockReturnValue(false); - - // Nothing should happen - mockedAssign.mockClear(); - repaint.mockClear(); - - child.emit('message', { unknown: true }); - - expect(mockedAssign).not.toHaveBeenCalled(); - expect(repaint).not.toHaveBeenCalled(); - }); - - it('on abnormal exit with pending work: respawns and re-attaches replacement', () => { - const id = 6; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending = ['/pending.csv']; // triggers respawn - const repaint = vi.fn(); - - const replacement = makeChild(); - mockedSpawn.mockReturnValue(replacement); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: { q: 1 }, - onAllWorkersExited: vi.fn(), - logDir: '/logs', - modulePath: '/worker.js', - spawnSilent: true, - }); - - // Abnormal exit: non-zero code - child.emit('exit', 1, null); - - expect(mockedSpawn).toHaveBeenCalledWith({ - id, - modulePath: '/worker.js', - logDir: '/logs', - openLogWindows: true, - isSilent: true, - }); - - // Replacement should be registered - expect(workers.get(id)).toBe(replacement); - // Badge should be set to error on the slot before respawn - expect(repaint).toHaveBeenCalled(); - }); - - it('on normal exit with no other workers: invokes onAllWorkersExited', () => { - const id = 7; - const child = makeChild(); - const workers = new Map(); - const workerState = new Map(); - const state = { completed: 0, failed: 0 }; - const filesPending: string[] = []; // no respawn - const repaint = vi.fn(); - const onAllWorkersExited = vi.fn(); - - attachWorkerHandlers({ - id, - child, - workers, - workerState, - state, - filesPending, - repaint, - common: {}, - onAllWorkersExited, - logDir: '/logs', - modulePath: '/worker.js', - }); - - // Normal exit: code 0, no signal - child.emit('exit', 0, null); - - // Worker removed and since none remain, callback fired - expect(workers.size).toBe(0); - expect(onAllWorkersExited).toHaveBeenCalledTimes(1); - }); -}); -/* eslint-enable max-lines */ diff --git a/src/lib/pooling/tests/dashboardPlugin.test.ts b/src/lib/pooling/tests/dashboardPlugin.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/pooling/tests/extraKeys.test.ts b/src/lib/pooling/tests/extraKeys.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/pooling/tests/isWorkerProgressMessage.test.ts b/src/lib/pooling/tests/isWorkerProgressMessage.test.ts deleted file mode 100644 index 8e51e290..00000000 --- a/src/lib/pooling/tests/isWorkerProgressMessage.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { isWorkerProgressMessage, type WorkerProgressMessage } from '../ipc'; - -/** - * Build a progress payload. - * - * @param overrides - fields to override - * @returns a progress message as unknown - */ -function makeProgress( - overrides: Partial = {}, -): unknown { - const base = { - type: 'progress' as const, - payload: { - filePath: 'file.csv', - successDelta: 1, - successTotal: 10, - fileTotal: 100, - ...overrides, - }, - }; - return base; -} - -describe('isWorkerProgressMessage', () => { - it('returns true for a valid progress message', () => { - const msg = makeProgress(); - expect(isWorkerProgressMessage(msg)).toBe(true); - }); - - it('requires only payload.filePath (numbers are not validated by the guard)', () => { - // Intentionally remove the numeric fields: still true per current implementation - const msg = makeProgress({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - successDelta: undefined as any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - successTotal: undefined as any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fileTotal: undefined as any, - }); - expect(isWorkerProgressMessage(msg)).toBe(true); - }); - - it('returns false when payload is missing or malformed', () => { - expect(isWorkerProgressMessage({ type: 'progress' })).toBe(false); - - // payload present but filePath not string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const bad = makeProgress({ filePath: 123 } as any); - expect(isWorkerProgressMessage(bad)).toBe(false); - }); - - it('returns false for other message types', () => { - expect(isWorkerProgressMessage({ type: 'ready' })).toBe(false); - expect(isWorkerProgressMessage({ type: 'result', payload: {} })).toBe( - false, - ); - expect(isWorkerProgressMessage(null)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(isWorkerProgressMessage('progress' as any)).toBe(false); - }); -}); diff --git a/src/lib/pooling/tests/isWorkerReadyMessage.test.ts b/src/lib/pooling/tests/isWorkerReadyMessage.test.ts deleted file mode 100644 index b4ef8a6d..00000000 --- a/src/lib/pooling/tests/isWorkerReadyMessage.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { isWorkerReadyMessage } from '../ipc'; - -/** - * Build a minimal "ready" message. - * - * @returns a valid WorkerReadyMessage as unknown - */ -function makeReady(): unknown { - return { type: 'ready' }; -} - -describe('isWorkerReadyMessage', () => { - it('returns true for a valid ready message', () => { - const msg = makeReady(); - expect(isWorkerReadyMessage(msg)).toBe(true); - }); - - it('returns true even with extra fields (lenient check)', () => { - const msg = { type: 'ready', extra: 123 }; - expect(isWorkerReadyMessage(msg)).toBe(true); - }); - - it('returns false for non-object or falsy', () => { - expect(isWorkerReadyMessage(null)).toBe(false); - expect(isWorkerReadyMessage(undefined)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(isWorkerReadyMessage('ready' as any)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(isWorkerReadyMessage(42 as any)).toBe(false); - }); - - it('returns false when type is not "ready"', () => { - expect(isWorkerReadyMessage({ type: 'result' })).toBe(false); - expect(isWorkerReadyMessage({ type: 'progress' })).toBe(false); - expect(isWorkerReadyMessage({})).toBe(false); - }); -}); diff --git a/src/lib/pooling/tests/isWorkerResultMessage.test.ts b/src/lib/pooling/tests/isWorkerResultMessage.test.ts deleted file mode 100644 index cefcfbcd..00000000 --- a/src/lib/pooling/tests/isWorkerResultMessage.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { isWorkerResultMessage, type WorkerProgressMessage } from '../ipc'; - -/** - * Build a result message. - * - * @param overrides - fields to override in payload - * @returns a result message as unknown - */ -function makeResult( - overrides: Partial = {}, -): unknown { - const base = { - type: 'result' as const, - payload: { - ok: true, - filePath: '/abs/path.csv', - error: undefined, - receiptFilepath: undefined, - ...overrides, - }, - }; - return base; -} - -describe('isWorkerResultMessage', () => { - it('returns true for a minimal valid result (ok + filePath)', () => { - const msg = makeResult(); - expect(isWorkerResultMessage(msg)).toBe(true); - }); - - it('returns true with optional fields (error/receiptFilepath) present', () => { - const msg = makeResult({ - error: 'boom', - receiptFilepath: '/receipts.json', - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); - expect(isWorkerResultMessage(msg)).toBe(true); - }); - - it('returns false when ok is not boolean', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const bad = makeResult({ ok: 'yes' } as any); - expect(isWorkerResultMessage(bad)).toBe(false); - }); - - it('returns false when filePath missing or not string', () => { - const noPath = { type: 'result', payload: { ok: true } }; - expect(isWorkerResultMessage(noPath)).toBe(false); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const wrongPath = makeResult({ filePath: 123 } as any); - expect(isWorkerResultMessage(wrongPath)).toBe(false); - }); - - it('returns false for other message types and non-objects', () => { - expect(isWorkerResultMessage({ type: 'ready' })).toBe(false); - expect(isWorkerResultMessage({ type: 'progress', payload: {} })).toBe( - false, - ); - expect(isWorkerResultMessage(null)).toBe(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(isWorkerResultMessage('result' as any)).toBe(false); - }); -}); diff --git a/src/lib/pooling/tests/refillIdleWorkers.test.ts b/src/lib/pooling/tests/refillIdleWorkers.test.ts deleted file mode 100644 index 26fb51d8..00000000 --- a/src/lib/pooling/tests/refillIdleWorkers.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import type { ChildProcess } from 'node:child_process'; - -import { refillIdleWorkers, type WorkerMaps } from '../workerAssignment'; - -/** - * Build WorkerMaps for refill tests. - * - * @param workerIds - ids to include in `maps.workers` (iteration order matters) - * @param busySet - set of ids that should be marked busy - * @returns initialized worker maps with given busy/idle states - */ -function buildMaps( - workerIds: number[], - busySet = new Set(), -): WorkerMaps { - const workers = new Map(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const workerState = new Map(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const slotLogPaths = new Map(); - - for (const id of workerIds) { - // minimal child - workers.set(id, {} as unknown as ChildProcess); - // if not present in map, treated as idle; here we set all explicitly - workerState.set(id, { - busy: busySet.has(id), - file: busySet.has(id) ? `/f-${id}.csv` : null, - startedAt: busySet.has(id) ? 123 : null, - lastLevel: 'ok', - progress: undefined, - }); - } - - return { - workers, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - workerState: workerState as unknown as Map, - slotLogPaths, - } as unknown as WorkerMaps; -} - -/** - * Create an assign spy that also simulates dequeuing work: - * - * @param queue - the shared files queue to mutate - * @returns assign(id) that shifts from queue and records call order - */ -function makeAssign(queue: string[]): { - /** - * Assign work to `id` and simulate dequeuing from the shared queue. - * - * @param id - worker id to assign - */ - assign(id: number): void; - /** - * Retrieve the call order. - * - * @returns array of ids in the order `assign` was invoked - */ - getCalls(): number[]; -} { - const calls: number[] = []; - return { - /** - * Assign work to `id` and simulate dequeuing from the shared queue. - * - * @param id - worker id to assign - */ - assign(id: number) { - calls.push(id); - queue.shift(); - }, - /** - * Retrieve the call order. - * - * @returns array of ids in the order `assign` was invoked - */ - getCalls(): number[] { - return calls.slice(); - }, - }; -} - -describe('refillIdleWorkers', () => { - it('assigns only idle workers until the queue is empty (respects iteration order)', () => { - const filesQueue = ['a', 'b']; - const maps = buildMaps([1, 2, 3, 4], new Set([2])); // 1 idle, 2 busy, 3 idle, 4 idle - const { assign, getCalls } = makeAssign(filesQueue); - - refillIdleWorkers(filesQueue, maps, assign); - - // Two items → two assignments, to the first two idle workers in order: 1 then 3 - expect(getCalls()).toEqual([1, 3]); - expect(filesQueue).toEqual([]); // both consumed by our assign stub - }); - - it('does nothing when all workers are busy', () => { - const filesQueue = ['a', 'b', 'c']; - const maps = buildMaps([1, 2], new Set([1, 2])); - const { assign, getCalls } = makeAssign(filesQueue); - - refillIdleWorkers(filesQueue, maps, assign); - - expect(getCalls()).toEqual([]); - expect(filesQueue).toEqual(['a', 'b', 'c']); - }); - - it('treats missing state as idle and assigns if there is work', () => { - const filesQueue = ['x']; - const maps = buildMaps([10], new Set()); - // Remove state to simulate "undefined" → treated as idle - maps.workerState.delete(10); - - const { assign, getCalls } = makeAssign(filesQueue); - refillIdleWorkers(filesQueue, maps, assign); - - expect(getCalls()).toEqual([10]); - expect(filesQueue).toEqual([]); // consumed - }); - - it('breaks early when the queue becomes empty during iteration', () => { - const filesQueue = ['only-one']; - const maps = buildMaps([1, 2, 3], new Set()); // all idle - const { assign, getCalls } = makeAssign(filesQueue); - - refillIdleWorkers(filesQueue, maps, assign); - - // Only one assignment should occur because our stub depletes the queue - expect(getCalls()).toEqual([1]); - expect(filesQueue).toEqual([]); - }); -}); diff --git a/src/lib/pooling/tests/runPool.test.ts b/src/lib/pooling/tests/runPool.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/pooling/tests/uiPlugins.test.ts b/src/lib/pooling/tests/uiPlugins.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/pooling/tests/wireStderrBadges.test.ts b/src/lib/pooling/tests/wireStderrBadges.test.ts deleted file mode 100644 index e10bc0a2..00000000 --- a/src/lib/pooling/tests/wireStderrBadges.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import type { ChildProcess } from 'node:child_process'; -import { EventEmitter } from 'node:events'; - -import { wireStderrBadges } from '../diagnostics'; -import { classifyLogLevel, makeLineSplitter } from '../logRotation'; - -/** - * Mock collaborators BEFORE importing the SUT. - * Path is relative to THIS test file. - */ -vi.mock('../logRotation', () => { - // Provide a splitter that simply forwards the chunk to the callback as a single "line" - const makeLineSplitter = vi.fn( - (cb: (line: string) => void) => (chunk: unknown) => cb(String(chunk)), - ); - return { - classifyLogLevel: vi.fn(), - makeLineSplitter, - }; -}); - -const mockedClassify = vi.mocked(classifyLogLevel); -const mockedMakeSplitter = vi.mocked(makeLineSplitter); - -/** - * Minimal ChildProcess-like object with an EventEmitter for stderr - * - * @returns - A mock child process with stderr - */ -function childWithStderr(): ChildProcess { - const stderr = new EventEmitter(); - return { stderr } as unknown as ChildProcess; -} - -/** - * ChildProcess-like without stderr to exercise early return - * - * @returns - A mock child process without stderr - */ -function childWithoutStderr(): ChildProcess { - return {} as unknown as ChildProcess; -} - -describe('wireStderrBadges', () => { - let repaint: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - repaint = vi.fn(); - }); - - afterEach(() => { - // no-op for now - }); - - it('early-returns when child has no stderr (no splitter, no listeners)', () => { - const id = 1; - const child = childWithoutStderr(); - const workerState = new Map(); - - wireStderrBadges(id, child, workerState, repaint); - - // Since stderr was falsy, makeLineSplitter should not be called at all - expect(mockedMakeSplitter).not.toHaveBeenCalled(); - expect(repaint).not.toHaveBeenCalled(); - }); - - it('treats lines as WARN when classifyLogLevel returns null; updates state and repaints', () => { - const id = 2; - const child = childWithStderr(); - const workerState = new Map< - number, - { - /** Last log level */ - lastLevel: 'ok' | 'warn' | 'error'; - } - >(); - workerState.set(id, { lastLevel: 'ok' }); - - mockedClassify.mockReturnValue(null); // no explicit tag → treat as 'warn' - // eslint-disable-next-line @typescript-eslint/no-explicit-any - wireStderrBadges(id, child, workerState as any, repaint); - - // Emit a stderr "data" chunk; splitter forwards to callback - (child.stderr as unknown as EventEmitter).emit( - 'data', - 'some line without tag', - ); - - expect(workerState.get(id)?.lastLevel).toBe('warn'); - expect(repaint).toHaveBeenCalledTimes(1); - - // Emit again with same effective level; should not repaint twice - (child.stderr as unknown as EventEmitter).emit('data', 'another line'); - expect(repaint).toHaveBeenCalledTimes(1); - }); - - it('updates to ERROR when classifyLogLevel returns "error"; repaints once', () => { - const id = 3; - const child = childWithStderr(); - const workerState = new Map< - number, - { - /** Last log level */ - lastLevel: 'ok' | 'warn' | 'error'; - } - >(); - workerState.set(id, { lastLevel: 'warn' }); - - mockedClassify.mockReturnValue('error'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - wireStderrBadges(id, child, workerState as any, repaint); - - (child.stderr as unknown as EventEmitter).emit('data', '[ERROR] boom!'); - expect(workerState.get(id)?.lastLevel).toBe('error'); - expect(repaint).toHaveBeenCalledTimes(1); - - // Emitting another error line should not repaint again if level unchanged - (child.stderr as unknown as EventEmitter).emit('data', 'still error'); - expect(repaint).toHaveBeenCalledTimes(1); - }); - - it('does nothing if worker state for id is missing', () => { - const id = 4; - const child = childWithStderr(); - const workerState = new Map(); - - mockedClassify.mockReturnValue('warn'); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - wireStderrBadges(id, child, workerState as any, repaint); - - (child.stderr as unknown as EventEmitter).emit('data', 'warn-ish line'); - expect(workerState.has(id)).toBe(false); - expect(repaint).not.toHaveBeenCalled(); - }); -}); diff --git a/src/lib/pooling/types.ts b/src/lib/pooling/types.ts new file mode 100644 index 00000000..44426cd3 --- /dev/null +++ b/src/lib/pooling/types.ts @@ -0,0 +1,80 @@ +import type { ObjByString } from '@transcend-io/type-utils'; + +/** Minimal per-slot state the runner keeps */ +export type PoolLevel = 'ok' | 'warn' | 'error'; + +export interface SlotState { + /** True if the worker is currently processing a task */ + busy: boolean; + /** The file being processed by the worker */ + file: string | null; + /** Timestamp when the worker started processing the task */ + startedAt: number | null; + /** Current log level of the worker */ + lastLevel: PoolLevel; + /** Progress */ + progress?: TProg; +} + +/** Shape of the optional throughput callback */ +export type ProgressInfo = { + /** ID of the worker */ + workerId: number; + /** File path */ + filePath: string; + /** Number of success updates */ + successDelta: number; + /** Total number of success */ + successTotal: number; + /** Total number of items being processed */ + fileTotal: number; +}; + +/** Message sent by a worker indicating it is ready to receive tasks. */ +export type WorkerReady = { + /** Type ready */ + type: 'ready'; +}; + +/** Message sent by a worker with a progress payload. */ +export type WorkerProgress = { + /** Discriminant. */ + type: 'progress'; + /** Implementation-defined progress payload. */ + payload: TProg; +}; + +/** Message sent by a worker with a final result payload for a single unit. */ +export type WorkerResult = { + /** Discriminant. */ + type: 'result'; + /** Implementation-defined result payload. */ + payload: TRes; +}; + +/** Union of all Worker → Parent messages. */ +export type FromWorker = + | WorkerReady + | WorkerProgress + | WorkerResult; + +/** + * Message sent by the parent to a worker to signal shutdown. + */ +export type ShutdownEvent = { + /** Shutdown */ + type: 'shutdown'; +}; + +/** + * Message sent by the parent to a worker to assign a task. + */ +export type TaskEvent = { + /** Task */ + type: 'task'; + /** Payload */ + payload: TTask; +}; + +/** Messages the parent can send to a worker. */ +export type ToWorker = ShutdownEvent | TaskEvent; diff --git a/src/lib/pooling/uiPlugins.ts b/src/lib/pooling/uiPlugins.ts new file mode 100644 index 00000000..e2dbc3f2 --- /dev/null +++ b/src/lib/pooling/uiPlugins.ts @@ -0,0 +1,158 @@ +import colors from 'colors'; +import { basename } from 'node:path'; +import type { CommonCtx } from './dashboardPlugin'; +import type { ObjByString } from '@transcend-io/type-utils'; + +/** + * Progress snapshot for a worker slot in the chunk-csv command. + */ +export type ChunkSlotProgress = { + /** Absolute path of the file being processed by this worker. */ + filePath?: string; + /** Number of rows processed so far in this file. */ + processed?: number; + /** Optional total number of rows in the file (if known). */ + total?: number; +}; + +/** + * Format a number safely for display. + * + * @param n - The number to format (or `undefined`). + * @returns A localized string representation, or "0". + */ +export function fmtNum(n: number | undefined): string { + return typeof n === 'number' ? n.toLocaleString() : '0'; +} + +/** + * Draw a horizontal bar of length `width` filled to `pct` percent. + * + * @param pct - Percentage 0..100. + * @param width - Number of characters in the bar. + * @returns A string like "████░░░░". + */ +export function pctBar(pct: number, width = 40): string { + const clamped = Math.max(0, Math.min(100, Math.floor(pct))); + const filled = Math.floor((clamped / 100) * width); + return '█'.repeat(filled) + '░'.repeat(width - filled); +} + +/** + * Compute pool-wide progress values needed by headers. + * + * @param ctx - Dashboard context containing pool state, worker state, totals, etc. + * @returns An object with `done`, `inProgress`, and `pct` properties. + */ +export function poolProgress( + ctx: CommonCtx, +): { + /** Count of successfully completed files/tasks. */ + done: number; + /** Count of currently in-progress files/tasks. */ + inProgress: number; + /** Percentage of completion (0-100). */ + pct: number; +} { + const inProgress = [...ctx.workerState.values()].filter((s) => s.busy).length; + const done = ctx.filesCompleted + ctx.filesFailed; + const pct = + ctx.filesTotal === 0 + ? 100 + : Math.floor((done / Math.max(1, ctx.filesTotal)) * 100); + return { done, inProgress, pct }; +} + +/** + * Compose the common header lines (title, pool stats, progress bar, throughput). + * + * @param ctx - Dashboard context. + * @param extraLines - Optional extra lines (e.g., totals block). + * @returns Header lines. + */ +export function makeHeader( + ctx: CommonCtx, + extraLines: string[] = [], +): string[] { + const { + title, + poolSize, + cpuCount, + filesTotal, + filesCompleted, + filesFailed, + throughput, + } = ctx; + const { inProgress, pct } = poolProgress(ctx); + + const lines: string[] = [ + `${colors.bold(title)} — ${poolSize} workers ${colors.dim( + `(CPU avail: ${cpuCount})`, + )}`, + `${colors.dim('Files')} ${fmtNum(filesTotal)} ${colors.dim( + 'Completed', + )} ${fmtNum(filesCompleted)} ${colors.dim('Failed')} ${ + filesFailed ? colors.red(fmtNum(filesFailed)) : fmtNum(filesFailed) + } ${colors.dim('In-flight')} ${fmtNum(inProgress)}`, + `[${pctBar(pct)}] ${pct}%`, + ]; + + if (throughput) { + const perHour10 = Math.round(throughput.r10s * 3600).toLocaleString(); + const perHour60 = Math.round(throughput.r60s * 3600).toLocaleString(); + const suffix = + ctx.throughput?.successSoFar != null + ? ` Newly uploaded: ${fmtNum(ctx.throughput.successSoFar)}` + : ''; + lines.push( + colors.cyan(`Throughput: ${perHour10}/hr (1h: ${perHour60}/hr)${suffix}`), + ); + } + + return extraLines.length ? lines.concat(extraLines) : lines; +} + +/** + * Render per-worker rows with a compact progress bar and status badge. + * + * @param ctx - Dashboard context (slot progress type must have processed/total?). + * @param getFileLabel - Optional: override how the filename is shown. + * @returns Array of strings, each representing one worker row. + */ +export function makeWorkerRows< + TTotals, + TSlot extends Omit, +>( + ctx: CommonCtx, + getFileLabel: (file: string | null | undefined) => string = (file) => + file ? basename(file) : '-', +): string[] { + const miniWidth = 18; + + return [...ctx.workerState.entries()].map(([id, s]) => { + const badge = + s.lastLevel === 'error' + ? colors.red('ERROR ') + : s.lastLevel === 'warn' + ? colors.yellow('WARN ') + : s.busy + ? colors.green('WORKING') + : colors.dim('IDLE '); + + const fname = getFileLabel(s.file); + const elapsed = s.startedAt + ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` + : '-'; + + const processed = s.progress?.processed ?? 0; + const total = s.progress?.total ?? 0; + const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0; + const mini = total > 0 ? pctBar(pctw, miniWidth) : ' '.repeat(miniWidth); + const miniTxt = + total > 0 + ? `${processed.toLocaleString()}/${total.toLocaleString()} (${pctw}%)` + : colors.dim('—'); + + return ` [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`; + }); +} diff --git a/src/lib/pooling/workerAssignment.ts b/src/lib/pooling/workerAssignment.ts deleted file mode 100644 index 73344103..00000000 --- a/src/lib/pooling/workerAssignment.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; -import { isIpcOpen, safeSend, type WorkerLogPaths } from './spawnWorkerProcess'; -import type { WorkerState } from './assignWorkToWorker'; - -/** - * Assigns work to a specific worker slot. - */ -export type WorkerMaps = { - /** Map of worker IDs to their ChildProcess instances */ - workers: Map; - /** Map of worker IDs to their state, including progress and last log level */ - workerState: Map; - /** Queue of file paths pending processing */ - slotLogPaths: Map; -}; - -/** - * Assigns work to a specific worker slot. - * - * @param id - The worker slot ID to assign work to - * @param filesQueue - The queue of file paths to process - * @param commonOpts - Common options to send with the work assignment - * @param maps - Maps containing workers and their states - */ -export function assignWorkToSlot( - id: number, - filesQueue: string[], - commonOpts: unknown, - maps: WorkerMaps, -): void { - const { workers, workerState } = maps; - const w = workers.get(id); - - // mark idle if IPC closed - if (!isIpcOpen(w)) { - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - return; - } - - const filePath = filesQueue.shift(); - if (!filePath) { - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - return; - } - - workerState.set(id, { - busy: true, - file: filePath, - startedAt: Date.now(), - lastLevel: 'ok', - progress: undefined, - }); - - if ( - !safeSend(w!, { type: 'task', payload: { filePath, options: commonOpts } }) - ) { - // IPC closed between check and send; re-queue and mark idle - filesQueue.unshift(filePath); - const prev = workerState.get(id); - workerState.set(id, { - busy: false, - file: null, - startedAt: null, - lastLevel: prev?.lastLevel ?? 'ok', - progress: undefined, - }); - } -} - -/** - * Refill idle workers with pending files. - * - * @param filesQueue - The queue of file paths to process - * @param maps - Maps containing workers and their states - * @param assign - Function to assign work to a worker - */ -export function refillIdleWorkers( - filesQueue: string[], - maps: WorkerMaps, - assign: (id: number) => void, -): void { - for (const [id] of maps.workers) { - const st = maps.workerState.get(id); - if (!st || !st.busy) { - if (filesQueue.length === 0) break; - assign(id); - } - } -} From 4de659797f98195e7735100b82dce96ca96a63cc Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 18 Aug 2025 08:34:23 -0700 Subject: [PATCH 48/72] Fixes --- README.md | 1314 +---------------- src/commands/admin/chunk-csv/impl.ts | 48 +- src/commands/admin/chunk-csv/worker.ts | 96 +- .../artifacts/receipts/receiptsState.ts | 2 +- .../consent/upload-preferences/command.ts | 1 - .../interactivePreferenceUploaderFromPlan.ts | 6 + .../consent/upload-preferences/worker.ts | 228 ++- src/lib/helpers/chunkOneCsvFile.ts | 37 +- src/lib/pooling/extraKeys.ts | 34 +- src/lib/pooling/types.ts | 14 - .../parsePreferenceManagementCsv.ts | 2 +- 11 files changed, 221 insertions(+), 1561 deletions(-) diff --git a/README.md b/README.md index c9aefe0d..0ba82539 100644 --- a/README.md +++ b/README.md @@ -61,40 +61,6 @@ - [`transcend consent upload-data-flows-from-csv`](#transcend-consent-upload-data-flows-from-csv) - [Examples](#examples-24) - [`transcend consent upload-preferences`](#transcend-consent-upload-preferences) - - [Examples](#examples-25) - - [`transcend inventory pull`](#transcend-inventory-pull) - - [Scopes](#scopes) - - [Examples](#examples-26) - - [`transcend inventory push`](#transcend-inventory-push) - - [Scopes](#scopes-1) - - [Examples](#examples-27) - - [CI Integration](#ci-integration) - - [Dynamic Variables](#dynamic-variables) - - [`transcend inventory scan-packages`](#transcend-inventory-scan-packages) - - [Examples](#examples-28) - - [`transcend inventory discover-silos`](#transcend-inventory-discover-silos) - - [Examples](#examples-29) - - [`transcend inventory pull-datapoints`](#transcend-inventory-pull-datapoints) - - [Examples](#examples-30) - - [`transcend inventory pull-unstructured-discovery-files`](#transcend-inventory-pull-unstructured-discovery-files) - - [Examples](#examples-31) - - [`transcend inventory derive-data-silos-from-data-flows`](#transcend-inventory-derive-data-silos-from-data-flows) - - [Examples](#examples-32) - - [`transcend inventory derive-data-silos-from-data-flows-cross-instance`](#transcend-inventory-derive-data-silos-from-data-flows-cross-instance) - - [Examples](#examples-33) - - [`transcend inventory consent-manager-service-json-to-yml`](#transcend-inventory-consent-manager-service-json-to-yml) - - [Examples](#examples-34) - - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) - - [Examples](#examples-35) - - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) - - [Examples](#examples-36) - - [`transcend admin chunk-csv`](#transcend-admin-chunk-csv) - - [Examples](#examples-37) - - [`transcend migration sync-ot`](#transcend-migration-sync-ot) - - [Authentication](#authentication) - - [Examples](#examples-38) -- [Prompt Manager](#prompt-manager) -- [Proxy usage](#proxy-usage) @@ -2081,1283 +2047,5 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [--viewerMode] - transcend consent upload-preferences --help - -Upload preference management data to your Preference Store. - -This 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. - -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. - -Parallel preference uploader (Node 22+ ESM/TS) ------------------------------------------------------------------------------ -- Spawns a pool of child *processes* (not threads) to run uploads in parallel. -- Shows a live dashboard in the parent terminal with progress per worker. -- Creates per-worker log files and (optionally) opens OS terminals to tail them. -- Uses the same module as both parent and child; the child mode is toggled - by the presence of a CLI flag ('--as-child'). - -FLAGS - --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" - --partition The partition key to download consent preferences to - [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - --directory Path to the directory of CSV files to load preferences from - [--dryRun] Whether to do a dry run only - will write results to receiptFilepath without updating Transcend [default = false] - [--skipExistingRecordCheck] Whether to skip the check for existing records. SHOULD ONLY BE USED FOR INITIAL UPLOAD [default = false] - [--receiptFileDir] Directory path where the response receipts should be saved. Defaults to ./receipts if a "file" is provided, or /../receipts if a "directory" is provided. - [--schemaFilePath] The path to where the schema for the file should be saved. If file is provided, it will default to ./-preference-upload-schema.json If directory is provided, it will default to /../preference-upload-schema.json - [--skipWorkflowTriggers] Whether to skip workflow triggers when uploading to preference store [default = false] - [--forceTriggerWorkflows] Whether to force trigger workflows for existing consent records [default = false] - [--skipConflictUpdates] Whether to skip uploading of any records where the preference store and file have a hard conflict [default = false] - [--isSilent/--noIsSilent] Whether to skip sending emails in workflows [default = true] - [--attributes] Attributes to add to any DSR request if created. Comma-separated list of key:value pairs. [default = Tags:transcend-cli,Source:transcend-cli] - [--receiptFilepath] Store resulting, continuing where left off [default = ./preference-management-upload-receipts.json] - [--concurrency] The number of concurrent processes to use to upload the files. When this is not set, it defaults to the number of CPU cores available on the machine. e.g. if there are 5 concurrent processes for 15 files, each parallel job would get 3 files to process. - [--uploadConcurrency] When uploading preferences to v1/preferences - this is the number of concurrent requests made at any given time by a single process.This is NOT the batch size—it's how many batch *tasks* run in parallel. The number of total concurrent requests is maxed out at concurrency * uploadConcurrency. [default = 75] - [--maxChunkSize] When uploading preferences to v1/preferences - this is the maximum number of records to put in a single request.The number of total concurrent records being put in at any one time is is maxed out at maxChunkSize * concurrency * uploadConcurrency. [default = 25] - [--rateLimitRetryDelay] When uploading preferences to v1/preferences - this is the number of milliseconds to wait before retrying a request that was rate limited. This is only used if the request is rate limited by the Transcend API. If the request fails for any other reason, it will not be retried. [default = 3000] - [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] - [--downloadIdentifierConcurrency] When downloading identifiers for the upload - this is the number of concurrent requests to make. This is only used if the records are not already cached in the preference store. [default = 30] - [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 10] - --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. - --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. - [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. - [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] - -h --help Print help information and exit -``` - -A sample CSV can be found [here](./examples/cli-upload-preferences-example.csv). In this example, `Sales` and `Marketing` are two custom Purposes, and `SalesCommunications` and `MarketingCommunications` are Preference Topics. During the interactive CLI prompt, you can map these columns to the slugs stored in Transcend! - -#### Examples - -**Upload consent preferences to partition key `4d1c5daa-90b7-4d18-aa40-f86a43d2c726`** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --directory=./ \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 -``` - -**Upload consent preferences with additional options** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --directory=./csvs \ - --dryRun \ - --skipWorkflowTriggers \ - --skipConflictUpdates \ - --isSilent=false \ - --attributes=Tags:transcend-cli,Source:transcend-cli \ - --receiptFilepath=./preference-management-upload-receipts.json -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend consent upload-preferences \ - --auth="$TRANSCEND_API_KEY" \ - --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --directory=./folder \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend inventory pull` - -```txt -USAGE - transcend inventory pull (--auth value) [--resources all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes] [--file value] [--transcendUrl value] [--dataSiloIds value]... [--integrationNames value]... [--trackerStatuses LIVE|NEEDS_REVIEW] [--pageSize value] [--skipDatapoints] [--skipSubDatapoints] [--includeGuessedCategories] [--debug] - transcend inventory pull --help - -Generates a transcend.yml by pulling the configuration from your Transcend instance. - -The API key needs various scopes depending on the resources being pulled (see the CLI's README for more details). - -This command can be helpful if you are looking to: - -- Copy your data into another instance -- Generate a transcend.yml file as a starting point to maintain parts of your data inventory in code. - -FLAGS - --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. - [--resources] The different resource types to pull in. Defaults to dataSilos,enrichers,templates,apiKeys. [all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes, separator = ,] - [--file] Path to the YAML file to pull into [default = ./transcend.yml] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... The UUIDs of the data silos that should be pulled into the YAML file [separator = ,] - [--integrationNames]... The types of integrations to pull down [separator = ,] - [--trackerStatuses] The statuses of consent manager trackers to pull down. Defaults to all statuses. [LIVE|NEEDS_REVIEW, separator = ,] - [--pageSize] The page size to use when paginating over the API [default = 50] - [--skipDatapoints] When true, skip pulling in datapoints alongside data silo resource [default = false] - [--skipSubDatapoints] When true, skip pulling in subDatapoints alongside data silo resource [default = false] - [--includeGuessedCategories] When true, included guessed data categories that came from the content classifier [default = false] - [--debug] Set to true to include debug logs while pulling the configuration [default = false] - -h --help Print help information and exit -``` - -#### Scopes - -The API key permissions for this command vary based on the `resources` argument: - -| Resource | Description | Scopes | Link | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| apiKeys | API Key definitions assigned to Data Silos. API keys cannot be created through the CLI, but you can map API key usage to Data Silos. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) | -| customFields | Custom field definitions that define extra metadata for each table in the Admin Dashboard. | View Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) | -| templates | Email templates. Only template titles can be created and mapped to other resources. | View Email Templates | [DSR Automation -> Email Templates](https://app.transcend.io/privacy-requests/email-templates) | -| dataSilos | The Data Silo/Integration definitions. | View Data Map, View Data Subject Request Settings | [Data Inventory -> Data Silos](https://app.transcend.io/data-map/data-inventory/) and [Infrastucture -> Integrations](https://app.transcend.io/infrastructure/integrationsdata-silos) | -| enrichers | The Privacy Request enricher configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | -| dataFlows | Consent Manager Data Flow definitions. | View Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) | -| businessEntities | The business entities in the data inventory. | View Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) | -| processingActivities | The processing activities in the data inventory. | View Data Inventory | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) | -| actions | The Privacy Request action settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | -| dataSubjects | The Privacy Request data subject settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings](https://app.transcend.io/privacy-requests/settings) | -| identifiers | The Privacy Request identifier configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) | -| cookies | Consent Manager Cookie definitions. | View Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) | -| consentManager | Consent Manager general settings, including domain list. | View Consent Manager | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) | -| partitions | The partitions in the account (often representative of separate data controllers). | View Consent Manager | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) | -| prompts | The Transcend AI prompts | View Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) | -| promptPartials | The Transcend AI prompt partials | View Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partialss) | -| promptGroups | The Transcend AI prompt groups | View Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) | -| agents | The agents in the prompt manager. | View Prompts | [Prompt Manager -> Agents](https://app.transcend.io/prompts/agents) | -| agentFunctions | The agent functions in the prompt manager. | View Prompts | [Prompt Manager -> Agent Functions](https://app.transcend.io/prompts/agent-functions) | -| agentFiles | The agent files in the prompt manager. | View Prompts | [Prompt Manager -> Agent Files](https://app.transcend.io/prompts/agent-files) | -| vendors | The vendors in the data inventory. | View Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) | -| dataCategories | The data categories in the data inventory. | View Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) | -| processingPurposes | The processing purposes in the data inventory. | View Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) | -| actionItems | Onboarding related action items | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | -| actionItemCollections | Onboarding related action item group names | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) | -| teams | Team definitions of users and scope groupings | View Scopes | [Administration -> Teams](https://app.transcend.io/admin/teams) | -| privacyCenters | The privacy center configurations. | View Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) | -| policies | The privacy center policies. | View Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) | -| messages | Message definitions used across consent, privacy center, email templates and more. | View Internationalization Messages | [Privacy Center -> Messages](https://app.transcend.io/privacy-center/messages-internationalization), [Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) | -| assessments | Assessment responses. | View Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) | -| assessmentTemplates | Assessment template configurations. | View Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) | -| purposes | Consent purposes and related preference management topics. | View Consent Manager, View Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) | - -#### Examples - -**Write out file to ./transcend.yml** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" -``` - -**Write out file to custom location** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml -``` - -**Pull specific data silo by ID** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --dataSiloIds=710fec3c-7bcc-4c9e-baff-bf39f9bec43e -``` - -**Pull specific types of data silos** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --integrationNames=salesforce,snowflake -``` - -**Pull specific resource types** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=apiKeys,templates,dataSilos,enrichers -``` - -**Pull data flows and cookies with specific tracker statuses (see [this example](./examples/data-flows-cookies.yml))** - -```sh -transcend inventory pull \ - --auth="$TRANSCEND_API_KEY" \ - --resources=dataFlows,cookies \ - --trackerStatuses=NEEDS_REVIEW,LIVE -``` - -**Pull data silos without datapoint information** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipDatapoints -``` - -**Pull data silos without subdatapoint information** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --skipSubDatapoints -``` - -**Pull data silos with guessed categories** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSilos --includeGuessedCategories -``` - -**Pull custom field definitions only (see [this example](./examples/attributes.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields -``` - -**Pull business entities only (see [this example](./examples/business-entities.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=businessEntities -``` - -**Pull processing activities only (see [this example](./examples/processing-activities.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=processingActivities -``` - -**Pull enrichers and identifiers (see [this example](./examples/enrichers.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=enrichers,identifiers -``` - -**Pull onboarding action items (see [this example](./examples/action-items.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actionItems,actionItemCollections -``` - -**Pull consent manager domain list (see [this example](./examples/consent-manager-domains.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=consentManager -``` - -**Pull identifier configurations (see [this example](./examples/identifiers.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=identifiers -``` - -**Pull request actions configurations (see [this example](./examples/actions.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=actions -``` - -**Pull consent manager purposes and preference management topics (see [this example](./examples/purposes.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=purposes -``` - -**Pull data subject configurations (see [this example](./examples/data-subjects.yml))** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataSubjects -``` - -**Pull assessments and assessment templates** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=assessments,assessmentTemplates -``` - -**Pull everything** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=all -``` - -**Pull configuration files across multiple instances** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Consent Manager" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth=./transcend-api-keys.json --resources=consentManager --file=./transcend/ -``` - -Note: This command will overwrite the existing transcend.yml file that you have locally. - -### `transcend inventory push` - -```txt -USAGE - transcend inventory push (--auth value) [--file value] [--transcendUrl value] [--pageSize value] [--variables value] [--publishToPrivacyCenter] [--classifyService] [--deleteExtraAttributeValues] - transcend inventory push --help - -Given a transcend.yml file, sync the contents up to your Transcend instance. - -FLAGS - --auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work. - [--file] Path to the YAML file to push from [default = ./transcend.yml] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--pageSize] The page size to use when paginating over the API [default = 50] - [--variables] The variables to template into the YAML file when pushing configuration. Comma-separated list of key:value pairs. [default = ""] - [--publishToPrivacyCenter] When true, publish the configuration to the Privacy Center [default = false] - [--classifyService] When true, automatically assign the service for a data flow based on the domain that is specified [default = false] - [--deleteExtraAttributeValues] When true and syncing attributes, delete any extra attributes instead of just upserting [default = false] - -h --help Print help information and exit -``` - -#### Scopes - -The scopes for `transcend inventory push` are the same as the scopes for [`transcend inventory pull`](#transcend-inventory-pull). - -#### Examples - -**Looks for file at ./transcend.yml** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" -``` - -**Looks for file at custom location ./custom/location.yml** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./custom/location.yml -``` - -**Apply service classifier to all data flows** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService -``` - -**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues -``` - -**Use dynamic variables to fill out parameters in YAML files (see [./examples/multi-instance.yml](./examples/multi-instance.yml))** - -```sh -transcend inventory push --auth="$TRANSCEND_API_KEY" --variables=domain:acme.com,stage:staging -``` - -**Push a single .yml file configuration into multiple Transcend instances** - -This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth="$TRANSCEND_API_KEY" -transcend inventory push --auth=./transcend-api-keys.json -``` - -**Push multiple .yml file configurations into multiple Transcend instances** - -This uses the output of [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys). - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./transcend-api-keys.json -transcend inventory pull --auth=./transcend-api-keys.json --file=./transcend/ -# -transcend inventory push --auth=./transcend-api-keys.json --file=./transcend/ -``` - -**Apply service classifier to all data flows** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=dataFlows -transcend inventory push --auth="$TRANSCEND_API_KEY" --classifyService -``` - -**Push up attributes, deleting any attributes that are not specified in the transcend.yml file** - -```sh -transcend inventory pull --auth="$TRANSCEND_API_KEY" --resources=customFields -transcend inventory push --auth="$TRANSCEND_API_KEY" --deleteExtraAttributeValues -``` - -Some things to note about this sync process: - -1. Any field that is defined in your .yml file will be synced up to app.transcend.io. If any change was made on the Admin Dashboard, it will be overwritten. -2. If you omit a field from the .yml file, this field will not be synced. This gives you the ability to define as much or as little configuration in your transcend.yml file as you would like, and let the remainder of fields be labeled through the Admin Dashboard -3. If you define new data subjects, identifiers, data silos or datapoints that were not previously defined on the Admin Dashboard, the CLI will create these new resources automatically. -4. Currently, this CLI does not handle deleting or renaming of resources. If you need to delete or rename a data silo, identifier, enricher or API key, you should make the change on the Admin Dashboard. -5. The only resources that this CLI will not auto-generate are: - -- a) Data silo owners: If you assign an email address to a data silo, you must first make sure that user is invited into your Transcend instance (https://app.transcend.io/admin/users). -- b) API keys: This CLI will not create new API keys. You will need to first create the new API keys on the Admin Dashboard (https://app.transcend.io/infrastructure/api-keys). You can then list out the titles of the API keys that you generated in your transcend.yml file, after which the CLI is capable of updating that API key to be able to respond to different data silos in your Data Map - -#### CI Integration - -Once you have a workflow for creating your transcend.yml file, you will want to integrate your `transcend inventory push` command on your CI. - -Below is an example of how to set this up using a Github action: - -```yaml -name: Transcend Data Map Syncing -# See https://app.transcend.io/privacy-requests/connected-services - -on: - push: - branches: - - 'main' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: '16' - - - name: Install Transcend CLI - run: npm install --global @transcend-io/cli - - # If you have a script that generates your transcend.yml file from - # an ORM or infrastructure configuration, add that step here - # Leave this step commented out if you want to manage your transcend.yml manually - # - name: Generate transcend.yml - # run: ./scripts/generate_transcend_yml.py - - - name: Push Transcend config - run: transcend inventory push --auth=${{ secrets.TRANSCEND_API_KEY }} -``` - -#### Dynamic Variables - -If you are using this CLI to sync your Data Map between multiple Transcend instances, you may find the need to make minor modifications to your configurations between environments. The most notable difference would be the domain where your webhook URLs are hosted on. - -The `transcend inventory push` command takes in a parameter `variables`. This is a CSV of `key:value` pairs. - -This command could fill out multiple parameters in a YAML file like [./examples/multi-instance.yml](./examples/multi-instance.yml), copied below: - -```yml -api-keys: - - title: Webhook Key -enrichers: - - title: Basic Identity Enrichment - description: Enrich an email address to the userId and phone number - # The data silo webhook URL is the same in each environment, - # except for the base domain in the webhook URL. - url: https://example.<>/transcend-enrichment-webhook - input-identifier: email - output-identifiers: - - userId - - phone - - myUniqueIdentifier - - title: Fraud Check - description: Ensure the email address is not marked as fraudulent - url: https://example.<>/transcend-fraud-check - input-identifier: email - output-identifiers: - - email - privacy-actions: - - ERASURE -data-silos: - - title: Redshift Data Warehouse - integrationName: server - description: The mega-warehouse that contains a copy over all SQL backed databases - <> - url: https://example.<>/transcend-webhook - api-key-title: Webhook Key -``` - -### `transcend inventory scan-packages` - -```txt -USAGE - transcend inventory scan-packages (--auth value) [--scanPath value] [--ignoreDirs value]... [--repositoryName value] [--transcendUrl value] - transcend inventory scan-packages --help - -Transcend scans packages and dependencies for the following frameworks: - -- package.json -- requirements.txt & setup.py -- Podfile -- Package.resolved -- build.gradle -- pubspec.yaml -- Gemfile & .gemspec -- composer.json - -This 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: - -- repository name -- package names -- dependency names and versions -- package descriptions - -FLAGS - --auth The Transcend API key. Requires scopes: "Manage Code Scanning" - [--scanPath] File path in the project to scan [default = ./] - [--ignoreDirs]... List of directories to ignore in scan [separator = ,] - [--repositoryName] Name of the git repository that the package should be tied to - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Scan the current directory** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" -``` - -**Scan a specific directory** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --scanPath=./examples/ -``` - -**Ignore certain folders** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --ignoreDirs=./test,./build -``` - -**Specify the name of the repository** - -```sh -transcend inventory scan-packages --auth="$TRANSCEND_API_KEY" --repositoryName=transcend-io/test -``` - -### `transcend inventory discover-silos` - -```txt -USAGE - transcend inventory discover-silos (--scanPath value) (--dataSiloId value) (--auth value) [--fileGlobs value] [--ignoreDirs value] [--transcendUrl value] - transcend inventory discover-silos --help - -We support scanning for new data silos in JavaScript, Python, Gradle, and CocoaPods projects. - -To 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. - -FLAGS - --scanPath File path in the project to scan - --dataSiloId The UUID of the corresponding data silo - --auth The Transcend API key. This key must be associated with the data silo(s) being operated on. Requires scopes: "Manage Assigned Data Inventory" - [--fileGlobs] You can pass a glob syntax pattern(s) to specify additional file paths to scan. Comma-separated list of globs. [default = ""] - [--ignoreDirs] Comma-separated list of directories to ignore. [default = ""] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Scan a JavaScript package.json** - -```sh -transcend inventory discover-silos \ - --scanPath=./myJavascriptProject \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=445ee241-5f2a-477b-9948-2a3682a43d0e -``` - -**Scan multiple file types (Podfile, Gradle, etc.) in examples directory** - -```sh -transcend inventory discover-silos \ - --scanPath=./examples/ \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloId=b6776589-0b7d-466f-8aad-4378ffd3a321 -``` - -This call will look for all the package.json files in the scan path `./myJavascriptProject`, parse each of the dependencies into their individual package names, and send it to our Transcend backend for classification. These classifications can then be viewed [here](https://app.transcend.io/data-map/data-inventory/silo-discovery/triage). The process is the same for scanning requirements.txt, podfiles and build.gradle files. - -Here are some examples of a [Podfile](./examples/code-scanning/test-cocoa-pods/Podfile) and [Gradle file](./examples/code-scanning/test-gradle/build.gradle). - -### `transcend inventory pull-datapoints` - -```txt -USAGE - transcend inventory pull-datapoints (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--includeAttributes] [--includeGuessedCategories] [--parentCategories FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER] [--subCategories value]... - transcend inventory pull-datapoints --help - -Export the datapoints from your Data Inventory into a CSV. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Data Inventory" - [--file] The file to save datapoints to [default = ./datapoints.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] - [--includeAttributes] Whether to include attributes in the output [default = false] - [--includeGuessedCategories] Whether to include guessed categories in the output [default = false] - [--parentCategories] List of parent categories to filter by [FINANCIAL|HEALTH|CONTACT|LOCATION|DEMOGRAPHIC|ID|ONLINE_ACTIVITY|USER_PROFILE|SOCIAL_MEDIA|CONNECTION|TRACKING|DEVICE|SURVEY|OTHER|UNSPECIFIED|NOT_PERSONAL_DATA|INTEGRATION_IDENTIFIER, separator = ,] - [--subCategories]... List of subcategories to filter by [separator = ,] - -h --help Print help information and exit -``` - -#### Examples - -**All arguments** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --includeGuessedCategories \ - --parentCategories=CONTACT,ID,LOCATION \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Pull datapoints for specific data silos** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Include attributes in the output** - -```sh -transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeAttributes + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [-- ``` - -**Include guessed categories in the output** - -```sh -transcend inventory pull-datapoints --auth="$TRANSCEND_API_KEY" --file=./datapoints.csv --includeGuessedCategories -``` - -**Filter by parent categories** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --parentCategories=ID,LOCATION -``` - -**Filter by subcategories** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 -``` - -**Specify the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend inventory pull-datapoints \ - --auth="$TRANSCEND_API_KEY" \ - --file=./datapoints.csv \ - --transcendUrl=https://api.us.transcend.io -``` - -### `transcend inventory pull-unstructured-discovery-files` - -```txt -USAGE - transcend inventory pull-unstructured-discovery-files (--auth value) [--file value] [--transcendUrl value] [--dataSiloIds value]... [--subCategories value]... [--status MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED] [--includeEncryptedSnippets] - transcend inventory pull-unstructured-discovery-files --help - -This command allows for pulling Unstructured Discovery into a CSV. - -FLAGS - --auth The Transcend API key. Requires scopes: "View Data Inventory" - [--file] The file to save datapoints to [default = ./unstructured-discovery-files.csv] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--dataSiloIds]... List of data silo IDs to filter by [separator = ,] - [--subCategories]... List of data categories to filter by [separator = ,] - [--status] List of classification statuses to filter by [MANUALLY_ADDED|CORRECTED|VALIDATED|CLASSIFIED|REJECTED, separator = ,] - [--includeEncryptedSnippets] Whether to include encrypted snippets of the entries classified [default = false] - -h --help Print help information and exit -``` - -#### Examples - -**All arguments** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --file=./unstructured-discovery-files.csv \ - --transcendUrl=https://api.us.transcend.io \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 \ - --status=VALIDATED,MANUALLY_ADDED,CORRECTED \ - --includeEncryptedSnippets -``` - -**Specify the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --transcendUrl=https://api.us.transcend.io -``` - -**Pull entries for specific data silos** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --dataSiloIds=f956ccce-5534-4328-a78d-3a924b1fe429 -``` - -**Filter by data categories** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --subCategories=79d998b7-45dd-481c-ae3a-856fd93458b2,9ecc213a-cd46-46d6-afd9-46cea713f5d1 -``` - -**Filter by classification status (exclude unconfirmed recommendations)** - -```sh -transcend inventory pull-unstructured-discovery-files \ - --auth="$TRANSCEND_API_KEY" \ - --status=VALIDATED,MANUALLY_ADDED,CORRECTED -``` - -**Filter by classification status (include rejected recommendations)** - -```sh -transcend inventory pull-unstructured-discovery-files --auth="$TRANSCEND_API_KEY" --status=REJECTED -``` - -### `transcend inventory derive-data-silos-from-data-flows` - -```txt -USAGE - transcend inventory derive-data-silos-from-data-flows (--auth value) (--dataFlowsYmlFolder value) (--dataSilosYmlFolder value) [--ignoreYmls value]... [--transcendUrl value] - transcend inventory derive-data-silos-from-data-flows --help - -Given a folder of data flow transcend.yml configurations, convert those configurations to set of data silo transcend.yml configurations. - -FLAGS - --auth The Transcend API key. No scopes are required for this command. - --dataFlowsYmlFolder The folder that contains data flow yml files - --dataSilosYmlFolder The folder that contains data silo yml files - [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to data silo configurations in folder** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ -``` - -**Use with US backend** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ \ - --transcendUrl=https://api.us.transcend.io -``` - -**Skip a set of yml files** - -```sh -transcend inventory derive-data-silos-from-data-flows \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --dataSilosYmlFolder=./working/data-silos/ \ - --ignoreYmls=Skip.yml,Other.yml -``` - -### `transcend inventory derive-data-silos-from-data-flows-cross-instance` - -```txt -USAGE - transcend inventory derive-data-silos-from-data-flows-cross-instance (--auth value) (--dataFlowsYmlFolder value) [--output value] [--ignoreYmls value]... [--transcendUrl value] - transcend inventory derive-data-silos-from-data-flows-cross-instance --help - -Given a folder of data flow transcend.yml configurations, convert those configurations to a single transcend.yml configurations of all related data silos. - -FLAGS - --auth The Transcend API key. No scopes are required for this command. - --dataFlowsYmlFolder The folder that contains data flow yml files - [--output] The output transcend.yml file containing the data silo configurations [default = ./transcend.yml] - [--ignoreYmls]... The set of yml files that should be skipped when uploading [separator = ,] - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to data silo configurations in file** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ -``` - -**Use with US backend** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --transcendUrl=https://api.us.transcend.io -``` - -**Skip a set of yml files** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --ignoreYmls=Skip.yml,Other.yml -``` - -**Convert data flow configurations in folder to data silo configurations in file** - -```sh -transcend inventory derive-data-silos-from-data-flows-cross-instance \ - --auth="$TRANSCEND_API_KEY" \ - --dataFlowsYmlFolder=./working/data-flows/ \ - --output=./output.yml -``` - -### `transcend inventory consent-manager-service-json-to-yml` - -```txt -USAGE - transcend inventory consent-manager-service-json-to-yml [--file value] [--output value] - transcend inventory consent-manager-service-json-to-yml --help - -Import the services from an airgap.js file into a Transcend instance. - -1. Run `await airgap.getMetadata()` on a site with airgap -2. Right click on the printed object, and click `Copy object` -3. Place output of file in a file named `services.json` -4. Run: - - transcend inventory consent-manager-service-json-to-yml --file=./services.json --output=./transcend.yml - -5. Run: - - transcend inventory push --auth="$TRANSCEND_API_KEY" --file=./transcend.yml --classifyService - -FLAGS - [--file] Path to the services.json file, output of await airgap.getMetadata() [default = ./services.json] - [--output] Path to the output transcend.yml to write to [default = ./transcend.yml] - -h --help Print help information and exit -``` - -#### Examples - -**Convert data flow configurations in folder to yml in ./transcend.yml** - -```sh -transcend inventory consent-manager-service-json-to-yml -``` - -**With file locations** - -```sh -transcend inventory consent-manager-service-json-to-yml --file=./folder/services.json --output=./folder/transcend.yml -``` - -### `transcend inventory consent-managers-to-business-entities` - -```txt -USAGE - transcend inventory consent-managers-to-business-entities (--consentManagerYmlFolder value) [--output value] - transcend inventory consent-managers-to-business-entities --help - -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. - -FLAGS - --consentManagerYmlFolder Path to the folder of Consent Manager transcend.yml files to combine - [--output] Path to the output transcend.yml with business entity configuration [default = ./combined-business-entities.yml] - -h --help Print help information and exit -``` - -#### Examples - -**Combine files in folder to file ./combined-business-entities.yml** - -```sh -transcend inventory consent-managers-to-business-entities --consentManagerYmlFolder=./working/consent-managers/ -``` - -**Specify custom output file** - -```sh -transcend inventory consent-managers-to-business-entities \ - --consentManagerYmlFolder=./working/consent-managers/ \ - --output=./custom.yml -``` - -### `transcend admin generate-api-keys` - -```txt -USAGE - transcend admin generate-api-keys (--email value) (--password value) (--apiKeyTitle value) (--file value) (--scopes View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories) [--deleteExistingApiKey] [--createNewApiKey] [--parentOrganizationId value] [--transcendUrl value] - transcend admin generate-api-keys --help - -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. - -Unlike 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. - -Authentication 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". - -FLAGS - --email The email address that you use to log into Transcend - --password The password for your account login - --apiKeyTitle The title of the API key being generated or destroyed - --file The file where API keys should be written to - --scopes The list of scopes that should be given to the API key [View Only|Full Admin|Rotate Hosted Sombra keys|Manage Global Attributes|Manage Access Controls|Manage Billing|Manage SSO|Manage API Keys|Manage Organization Information|Manage Email Domains|Manage Data Sub Categories|View Customer Data in Privacy Requests|View Customer Data in Data Mapping|View API Keys|View Audit Events|View SSO|View Scopes|View All Action Items|Manage All Action Items|View Employees|View Email Domains|View Global Attributes|View Legal Hold|Manage Legal Holds|Manage Request Security|Manage Request Compilation|Manage Assigned Privacy Requests|Submit New Data Subject Request|Manage Data Subject Request Settings|Manage Email Templates|Manage Request Identity Verification|Publish Privacy Center|Manage Data Map|Manage Privacy Center Layout|Manage Policies|View Policies|Manage Internationalization Messages|View Internationalization Messages|Request Approval and Communication|View Data Subject Request Settings|View the Request Compilation|View Identity Verification Settings|View Incoming Requests|View Assigned Privacy Requests|View Privacy Center Layout|View Email Templates|Connect Data Silos|Manage Data Inventory|Manage Assigned Data Inventory|Manage Assigned Integrations|View Data Map|View Assigned Integrations|View Assigned Data Inventory|View Data Inventory|Manage Consent Manager|Manage Consent Manager Developer Settings|Manage Consent Manager Display Settings|Deploy Test Consent Manager|Deploy Consent Manager|Manage Assigned Consent Manager|Manage Data Flows|View Data Flows|View Assigned Consent Manager|View Consent Manager|View Assessments|Manage Assessments|View Assigned Assessments|Manage Assigned Assessments|View Pathfinder|Manage Pathfinder|View Contract Scanning|Manage Contract Scanning|View Prompts|Manage Prompts|View Prompt Runs|Manage Prompt Runs|View Code Scanning|Manage Code Scanning|Execute Prompt|View Auditor Runs|Manage Auditor Runs and Schedules|Execute Auditor|Approve Prompts|Manage Action Item Collections|View Managed Consent Database Admin API|Modify User Stored Preferences|Manage Preference Store Settings|View Preference Store Settings|LLM Log Transfer|Manage Workflows|View Data Sub Categories, separator = ,] - [--deleteExistingApiKey/--noDeleteExistingApiKey] When true, if an API key exists with the specified apiKeyTitle, the existing API key is deleted [default = true] - [--createNewApiKey/--noCreateNewApiKey] When true, new API keys will be created. Set to false if you simply want to delete all API keys with a title [default = true] - [--parentOrganizationId] Filter for only a specific organization by ID, returning all child accounts associated with that organization - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - -h --help Print help information and exit -``` - -#### Examples - -**Generate API keys for cross-instance usage** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json -``` - -**Specifying the backend URL, needed for US hosted backend infrastructure** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --transcendUrl=https://api.us.transcend.io -``` - -**Filter for only a specific organization by ID, returning all child accounts associated with that organization** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --parentOrganizationId=7098bb38-070d-4f26-8fa4-1b61b9cdef77 -``` - -**Delete all API keys with a certain title** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --createNewApiKey=false -``` - -**Throw error if an API key already exists with that title, default behavior is to delete the existing API key and create a new one with that same title** - -```sh -transcend admin generate-api-keys \ - --email=test@transcend.io \ - --password="$TRANSCEND_PASSWORD" \ - --scopes="View Email Templates,View Data Map" \ - --apiKeyTitle="CLI Usage Cross Instance Sync" \ - --file=./working/auth.json \ - --deleteExistingApiKey=false -``` - -**Find your organization ID** - -You can use the following GQL query on the [EU GraphQL Playground](https://api.us.transcend.io/graphql) or [US GraphQL Playground](https://api.us.transcend.io/graphql) to get your organization IDs and their parent/child relationships. - -```gql -query { - user { - organization { - id - parentOrganizationId - } - } -} -``` - -### `transcend admin chunk-csv` - -```txt -USAGE - transcend admin chunk-csv (--directory value) [--outputDir value] [--clearOutputDir] [--chunkSizeMB value] [--concurrency value] [--viewerMode] - transcend admin chunk-csv --help - -Streams every CSV in --directory and writes chunked files of approximately N MB each. -- Runs files in parallel across worker processes (configurable via --concurrency). -- Validates row-length consistency against the header row; logs periodic progress and memory usage. - -FLAGS - --directory Directory containing CSV files to split (required) - [--outputDir] Directory to write chunk files (defaults to each input file's directory) - [--clearOutputDir/--noClearOutputDir] Clear the output directory before writing chunks [default = true] - [--chunkSizeMB] Approximate chunk size in megabytes. Keep well under JS string size limits [default = 10] - [--concurrency] Max number of worker processes (defaults based on CPU and file count) - [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] - -h --help Print help information and exit -``` - -#### Examples - -**Chunk a file into smaller CSV files** - -```sh -transcend admin chunk-csv --directory=./working/files --outputDir=./working/chunks -``` - -**Specify chunk size in MB** - -```sh -transcend admin chunk-csv --directory=./working/files --outputDir=./working/chunks --chunkSizeMB=250 -``` - -### `transcend migration sync-ot` - -```txt -USAGE - transcend migration sync-ot [--hostname value] [--oneTrustAuth value] [--source oneTrust|file] [--transcendAuth value] [--transcendUrl value] [--file value] [--resource assessments] [--dryRun] [--debug] - transcend migration sync-ot --help - -Pulls resources from a OneTrust and syncs them to a Transcend instance. For now, it only supports retrieving OneTrust Assessments. - -This command can be helpful if you are looking to: -- Pull resources from your OneTrust account. -- Migrate your resources from your OneTrust account to Transcend. - -OneTrust authentication requires an OAuth Token with scope for accessing the assessment endpoints. -If syncing the resources to Transcend, you will also need to generate an API key on the Transcend Admin Dashboard. - -FLAGS - [--hostname] The domain of the OneTrust environment from which to pull the resource - [--oneTrustAuth] The OAuth access token with the scopes necessary to access the OneTrust Public APIs - [--source] Whether to read the assessments from OneTrust or from a file [oneTrust|file, default = oneTrust] - [--transcendAuth] The Transcend API key. Requires scopes: "Manage Assessments" - [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] - [--file] Path to the file to pull the resource into. Must be a json file! - [--resource] The resource to pull from OneTrust. For now, only assessments is supported [assessments, default = assessments] - [--dryRun] Whether to export the resource to a file rather than sync to Transcend [default = false] - [--debug] Whether to print detailed logs in case of error [default = false] - -h --help Print help information and exit -``` - -#### Authentication - -In order to use this command, you will need to generate a OneTrust OAuth Token with scope for accessing the following endpoints: - -- [GET /v2/assessments](https://developer.onetrust.com/onetrust/reference/getallassessmentbasicdetailsusingget) -- [GET /v2/assessments/{assessmentId}/export](https://developer.onetrust.com/onetrust/reference/exportassessmentusingget) -- [GET /risks/{riskId}](https://developer.onetrust.com/onetrust/reference/getriskusingget) -- [GET /v2/Users/{userId}](https://developer.onetrust.com/onetrust/reference/getuserusingget) - -To learn how to generate the token, see the [OAuth 2.0 Scopes](https://developer.onetrust.com/onetrust/reference/oauth-20-scopes) and [Generate Access Token](https://developer.onetrust.com/onetrust/reference/getoauthtoken) pages. - -#### Examples - -**Syncs all assessments from the OneTrust instance to Transcend** - -```sh -transcend migration sync-ot \ - --hostname=trial.onetrust.com \ - --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ - --transcendAuth="$TRANSCEND_API_KEY" -``` - -**Set dryRun to true and sync the resource to disk (writes out file to ./oneTrustAssessments.json)** - -```sh -transcend migration sync-ot \ - --hostname=trial.onetrust.com \ - --oneTrustAuth="$ONE_TRUST_OAUTH_TOKEN" \ - --dryRun \ - --file=./oneTrustAssessments.json -``` - -**Sync to Transcend by reading from file instead of OneTrust** - -```sh -transcend migration sync-ot --source=file --file=./oneTrustAssessments.json --transcendAuth="$TRANSCEND_API_KEY" -``` - - - -## Prompt Manager - -If you are integrating Transcend's Prompt Manager into your code, it may look like: - -```ts -import * as t from 'io-ts'; -import { TranscendPromptManager } from '@transcend-io/cli'; -import { - ChatCompletionMessage, - PromptRunProductArea, -} from '@transcend-io/privacy-types'; - -/** - * Example prompt integration - */ -export async function main(): Promise { - // Instantiate the Transcend Prompt Manager instance - const promptManager = new TranscendPromptManager({ - // API key - transcendApiKey: process.env.TRANSCEND_API_KEY, - // Define the prompts that are stored in Transcend - prompts: { - test: { - // identify by ID - id: '30bcaa79-889a-4af3-842d-2e8ba443d36d', - // no runtime variables - paramCodec: t.type({}), - // response is list of strings - outputCodec: t.array(t.string), - }, - json: { - // identify by title - title: 'test', - // one runtime variable "test" - paramCodec: t.type({ test: t.string }), - // runtime is json object - outputCodec: t.record(t.string, t.string), - // response is stored in atg - extractFromTag: 'json', - }, - predictProductLine: { - // identify by title - title: 'Predict Product Line', - // runtime parameter for slack channel name - paramCodec: t.type({ - slackChannelName: t.string, - }), - // response is specific JSON shape - outputCodec: t.type({ - product: t.union([t.string, t.null]), - clarification: t.union([t.string, t.null]), - }), - // response is stored in atg - extractFromTag: 'json', - }, - }, - // Optional arguments - // transcendUrl: 'https://api.us.transcend.io', // defaults to 'https://api.transcend.io' - // requireApproval: false, // defaults to true - // cacheDuration: 1000 * 60 * 60, // defaults to undefined, no cache - // defaultVariables: { myVariable: 'this is custom', other: [{ name: 'custom' }] }, // defaults to {} - // handlebarsOptions: { helpers, templates }, // defaults to {} - }); - - // Fetch the prompt from Transcend and template any variables - // in this case, we template the slack channel name in the LLM prompt - const systemPrompt = await promptManager.compilePrompt('predictProductLine', { - slackChannelName: channelName, - }); - - // Parameters to pass to the LLM - const input: ChatCompletionMessage[] = [ - { - role: 'system', - content: systemPrompt, - }, - { - role: 'user', - content: input, - }, - ]; - const largeLanguageModel = { - name: 'gpt-4', - client: 'openai' as const, - }; - const temperature = 1; - const topP = 1; - const maxTokensToSample = 1000; - - // Run prompt against LLM - let response: string; - const t0 = new Date().getTime(); - try { - response = await openai.createCompletion(input, { - temperature, - top_p: topP, - max_tokens: maxTokensToSample, - }); - } catch (err) { - // report error upon failure - await promptManager.reportPromptRunError('predictProductLine', { - promptRunMessages: input, - duration: new Date().getTime() - t0, - temperature, - topP, - error: err.message, - maxTokensToSample, - largeLanguageModel, - }); - } - const t1 = new Date().getTime(); - - // Parsed response as JSON and do not report to Transcend - // const parsedResponse = promptManager.parseAiResponse( - // 'predictProductLine', - // response, - // ); - - // Parsed response as JSON and report output to Transcend - const parsedResponse = await promptManager.reportAndParsePromptRun( - 'predictProductLine', - { - promptRunMessages: [ - ...input, - { - role: 'assistant', - content: response, - }, - ], - duration: t1 - t0, - temperature, - topP, - maxTokensToSample, - largeLanguageModel, - // Optional parameters - // name, // unique identifier for this run - // productArea, // Transcend product area that the prompt relates to - // runByEmployeeEmail, // Employee email that is executing the request - // promptGroupId, // The prompt group being reported - }, - ); -} -``` - -## Proxy usage - -If you are trying to use the CLI inside a corporate firewall and need to send traffic through a proxy, you can do so via the `http_proxy` environment variable,with a command like `http_proxy=http://localhost:5051 transcend inventory pull --auth=$TRANSCEND_API_KEY`. diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index e2be3b9e..cbb86803 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -9,7 +9,12 @@ import { runPool, dashboardPlugin, } from '../../../lib/pooling'; -import { runChild } from './worker'; +import { + runChild, + type ChunkProgress, + type ChunkResult, + type ChunkTask, +} from './worker'; import { chunkCsvPlugin } from './ui'; import { createExtraKeyHandler } from '../../../lib/pooling/extraKeys'; @@ -26,47 +31,6 @@ function getCurrentModulePath(): string { return process.argv[1]; } -/** - * A unit of work: instructs a worker to chunk a single CSV file. - */ -export type ChunkTask = { - /** Absolute path of the CSV file to chunk. */ - filePath: string; - /** Options controlling output and chunk size. */ - options: { - /** Optional directory where chunked output files should be written. */ - outputDir?: string; - /** Whether to clear any pre-existing output chunks before writing new ones. */ - clearOutputDir: boolean; - /** Approximate target chunk size in MB (well under Node’s string size limits). */ - chunkSizeMB: number; - }; -}; - -/** - * Per-worker progress snapshot for the chunk-csv command. - */ -export type ChunkProgress = { - /** File being processed by the worker. */ - filePath: string; - /** Number of rows processed so far. */ - processed: number; - /** Optional total rows in the file (not always known). */ - total?: number; -}; - -/** - * Worker result message once a file has finished processing. - */ -export type ChunkResult = { - /** Whether the file completed successfully. */ - ok: boolean; - /** File path for which this result applies. */ - filePath: string; - /** Optional error message if the file failed to chunk. */ - error?: string; -}; - /** * Totals aggregate for this command. * We don’t need custom counters since the runner already tracks diff --git a/src/commands/admin/chunk-csv/worker.ts b/src/commands/admin/chunk-csv/worker.ts index 005cf3fe..5edde5a7 100644 --- a/src/commands/admin/chunk-csv/worker.ts +++ b/src/commands/admin/chunk-csv/worker.ts @@ -1,71 +1,47 @@ +import { extractErrorMessage } from '../../../lib/helpers'; import { chunkOneCsvFile } from '../../../lib/helpers/chunkOneCsvFile'; +import type { ToWorker } from '../../../lib/pooling'; import { logger } from '../../../logger'; /** - * Parent → Worker task message. - * Instructs this worker to chunk a single CSV file according to the provided options. + * A unit of work: instructs a worker to chunk a single CSV file. */ -type TaskMsg = { - type: 'task'; - payload: { - /** Absolute or relative path to the CSV file this worker should process. */ - filePath: string; - options: { - /** - * Optional directory where chunk files are written. - * If omitted, chunks are written next to the input file. - */ - outputDir?: string; - /** - * When true, any existing files matching this input’s chunk filename pattern - * (e.g. `${base}_chunk_0001.csv`) are deleted before writing. - */ - clearOutputDir: boolean; - /** - * Target chunk size in megabytes. This is an approximate threshold used to - * roll to the next output file while streaming. - */ - chunkSizeMB: number; - }; +export type ChunkTask = { + /** Absolute path of the CSV file to chunk. */ + filePath: string; + /** Options controlling output and chunk size. */ + options: { + /** Optional directory where chunked output files should be written. */ + outputDir?: string; + /** Whether to clear any pre-existing output chunks before writing new ones. */ + clearOutputDir: boolean; + /** Approximate target chunk size in MB (well under Node’s string size limits). */ + chunkSizeMB: number; }; }; -/** Parent → Worker shutdown message. Signals the worker to exit cleanly. */ -type ShutdownMsg = { type: 'shutdown' }; - -/** Union of messages the worker can receive from the parent process. */ -type ParentMsg = TaskMsg | ShutdownMsg; - /** - * Worker → Parent progress message. - * Emitted periodically (and finally) as rows are processed from the input file. + * Per-worker progress snapshot for the chunk-csv command. */ -type ProgressMsg = { - type: 'progress'; - payload: { - /** Path of the file currently being processed (echoed back for easy correlation). */ - filePath: string; - /** Number of data rows processed so far (header not counted). */ - processed: number; - /** Optional total row count if known (usually undefined for streaming). */ - total?: number; - }; +export type ChunkProgress = { + /** File being processed by the worker. */ + filePath: string; + /** Number of rows processed so far. */ + processed: number; + /** Optional total rows in the file (not always known). */ + total?: number; }; /** - * Worker → Parent result message. - * Emitted once per task upon success or failure. + * Worker result message once a file has finished processing. */ -type ResultMsg = { - type: 'result'; - payload: { - /** Whether the task completed successfully. */ - ok: boolean; - /** Path of the file that was processed. */ - filePath: string; - /** Error string when ok === false. */ - error?: string; - }; +export type ChunkResult = { + /** Whether the file completed successfully. */ + ok: boolean; + /** File path for which this result applies. */ + filePath: string; + /** Optional error message if the file failed to chunk. */ + error?: string; }; /** @@ -92,7 +68,7 @@ export async function runChild(): Promise { process.send?.({ type: 'ready' }); // Main message loop: receive tasks and shutdown requests from the parent. - process.on('message', async (msg: ParentMsg) => { + process.on('message', async (msg: ToWorker) => { if (!msg || typeof msg !== 'object') return; // Graceful shutdown: let the parent control lifecycle. @@ -118,22 +94,22 @@ export async function runChild(): Promise { process.send?.({ type: 'progress', payload: { filePath, processed, total }, - } as ProgressMsg), + }), }); // Report success to the parent. process.send?.({ type: 'result', payload: { ok: true, filePath }, - } as ResultMsg); + }); } catch (err) { // Log locally and report failure upstream; exit the worker with error code. - const message = (err as Error)?.message ?? String(err); + const message = extractErrorMessage(err); logger.error(`[w${workerId}] ERROR ${filePath}: ${message}`); process.send?.({ type: 'result', - payload: { ok: false, filePath, error: String(err) }, - } as ResultMsg); + payload: { ok: false, filePath, error: message }, + }); } }); diff --git a/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts b/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts index 2fdbd365..e8a77863 100644 --- a/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts +++ b/src/commands/consent/upload-preferences/artifacts/receipts/receiptsState.ts @@ -91,7 +91,7 @@ export async function makeReceiptsState( }; // Exponential backoff cap to avoid unbounded waits. - const MAX_DELAY_MS = 2_000; + const MAX_DELAY_MS = 5_000; try { const s = await retrySamePromise( diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index d279fe48..41423d4a 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -177,7 +177,6 @@ export const uploadPreferencesCommand = buildCommand({ 'Columns in the CSV that should be ignored. Comma-separated list of column names.', optional: true, }, - // FIXME viewerMode: { kind: 'boolean', brief: diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 53c2e3c2..6715c198 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -130,6 +130,12 @@ export async function interactivePreferenceUploaderFromPlan( const filtered = allEntries.filter(([userId]) => !successful[userId]); const fileTotal = filtered.length; + onProgress?.({ + successDelta: 0, + successTotal: 0, + fileTotal, + }); + if (filtered.length === 0) { logger.warn( colors.yellow( diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index cfee7407..bb55c57b 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -12,20 +12,7 @@ import { import { logger } from '../../../logger'; import { buildInteractiveUploadPreferencePlan } from './upload/buildInteractiveUploadPlan'; import type { TaskCommonOpts } from './buildTaskOptions'; - -export interface TaskMessage { - type: 'task'; - payload: { - filePath: string; - options: TaskCommonOpts; - }; -} - -export interface ShutdownMessage { - type: 'shutdown'; -} - -export type ParentMessage = TaskMessage | ShutdownMessage; +import type { ToWorker } from '../../../lib/pooling'; /** * Run the child process for handling upload preferences. @@ -57,113 +44,120 @@ export async function runChild(): Promise { process.send?.({ type: 'ready' }); // Listen for messages from the parent process - process.on('message', async (msg: ParentMessage) => { - if (!msg || typeof msg !== 'object') return; - - // Handle 'task' messages to process a file - if (msg.type === 'task') { - const { filePath, options } = msg.payload as { + process.on( + 'message', + async ( + msg: ToWorker<{ + /** File path */ filePath: string; + /** Options */ options: TaskCommonOpts; - }; - // Compute the path for receipts file - const receiptFilepath = join( - options.receiptsFolder, - `${getFilePrefix(filePath)}-receipts.json`, - ); - try { - // Ensure receipts directory exists - mkdirSync(dirname(receiptFilepath), { recursive: true }); - logger.info(`[w${workerId}] START ${filePath}`); - log(`START ${filePath}`); - - // Construct common state objects for the task - const receipts = await makeReceiptsState(receiptFilepath); - const schema = await makeSchemaState(options.schemaFile); - const client = buildTranscendGraphQLClient( - options.transcendUrl, - options.auth, + }>, + ) => { + if (!msg || typeof msg !== 'object') return; + + // Handle 'task' messages to process a file + if (msg.type === 'task') { + const { filePath, options } = msg.payload; + // Compute the path for receipts file + const receiptFilepath = join( + options.receiptsFolder, + `${getFilePrefix(filePath)}-receipts.json`, ); - const sombra = await createSombraGotInstance( - options.transcendUrl, - options.auth, - options.sombraAuth, - ); - - // Step 1: Build the upload plan (validation-only) - const plan = await buildInteractiveUploadPreferencePlan({ - sombra, - client, - file: filePath, - partition: options.partition, - receipts, - schema, - identifierDownloadLogInterval: options.uploadLogInterval * 10, - downloadIdentifierConcurrency: options.downloadIdentifierConcurrency, - skipExistingRecordCheck: options.skipExistingRecordCheck, - forceTriggerWorkflows: options.forceTriggerWorkflows, - allowedIdentifierNames: options.allowedIdentifierNames, - maxRecordsToReceipt: options.maxRecordsToReceipt, - identifierColumns: options.identifierColumns, - columnsToIgnore: options.columnsToIgnore, - attributes: splitCsvToList(options.attributes), - }); - - // Step 2: Execute the upload using the plan - await interactivePreferenceUploaderFromPlan(plan, { - receipts, - sombra, - dryRun: options.dryRun, - isSilent: options.isSilent, - skipWorkflowTriggers: options.skipWorkflowTriggers, - skipConflictUpdates: options.skipConflictUpdates, - forceTriggerWorkflows: options.forceTriggerWorkflows, - uploadLogInterval: options.uploadLogInterval, - maxChunkSize: options.maxChunkSize, - uploadConcurrency: options.uploadConcurrency, - maxRecordsToReceipt: options.maxRecordsToReceipt, - // Report progress to parent process - onProgress: ({ successDelta, successTotal, fileTotal }) => { - process.send?.({ - type: 'progress', - payload: { - filePath, - successDelta, - successTotal, - fileTotal, - }, - }); - }, - }); - - // Log completion and send result to parent - logger.info(`[w${workerId}] DONE ${filePath}`); - log(`SUCCESS ${filePath}`); - - process.send?.({ - type: 'result', - payload: { ok: true, filePath, receiptFilepath }, - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - // Handle errors, log them, and send failure result to parent - const e = err?.stack || err?.message || String(err); - logger.error( - `[w${workerId}] ERROR ${filePath}: ${err?.message || err}\n\n${e}`, - ); - log(`FAIL ${filePath}\n${e}`); - process.send?.({ - type: 'result', - payload: { ok: false, filePath, error: e, receiptFilepath }, - }); + try { + // Ensure receipts directory exists + mkdirSync(dirname(receiptFilepath), { recursive: true }); + logger.info(`[w${workerId}] START ${filePath}`); + log(`START ${filePath}`); + + // Construct common state objects for the task + const receipts = await makeReceiptsState(receiptFilepath); + const schema = await makeSchemaState(options.schemaFile); + const client = buildTranscendGraphQLClient( + options.transcendUrl, + options.auth, + ); + const sombra = await createSombraGotInstance( + options.transcendUrl, + options.auth, + options.sombraAuth, + ); + + // Step 1: Build the upload plan (validation-only) + const plan = await buildInteractiveUploadPreferencePlan({ + sombra, + client, + file: filePath, + partition: options.partition, + receipts, + schema, + identifierDownloadLogInterval: options.uploadLogInterval * 10, + downloadIdentifierConcurrency: + options.downloadIdentifierConcurrency, + skipExistingRecordCheck: options.skipExistingRecordCheck, + forceTriggerWorkflows: options.forceTriggerWorkflows, + allowedIdentifierNames: options.allowedIdentifierNames, + maxRecordsToReceipt: options.maxRecordsToReceipt, + identifierColumns: options.identifierColumns, + columnsToIgnore: options.columnsToIgnore, + attributes: splitCsvToList(options.attributes), + }); + + // Step 2: Execute the upload using the plan + await interactivePreferenceUploaderFromPlan(plan, { + receipts, + sombra, + dryRun: options.dryRun, + isSilent: options.isSilent, + skipWorkflowTriggers: options.skipWorkflowTriggers, + skipConflictUpdates: options.skipConflictUpdates, + forceTriggerWorkflows: options.forceTriggerWorkflows, + uploadLogInterval: options.uploadLogInterval, + maxChunkSize: options.maxChunkSize, + uploadConcurrency: options.uploadConcurrency, + maxRecordsToReceipt: options.maxRecordsToReceipt, + // Report progress to parent process + onProgress: ({ successTotal, fileTotal }) => { + process.send?.({ + type: 'progress', + payload: { + filePath, + processed: successTotal, + total: fileTotal, + }, + }); + }, + }); + + // Log completion and send result to parent + logger.info(`[w${workerId}] DONE ${filePath}`); + log(`SUCCESS ${filePath}`); + + process.send?.({ + type: 'result', + payload: { ok: true, filePath, receiptFilepath }, + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + // Handle errors, log them, and send failure result to parent + const e = err?.stack || err?.message || String(err); + logger.error( + `[w${workerId}] ERROR ${filePath}: ${err?.message || err}\n\n${e}`, + ); + log(`FAIL ${filePath}\n${e}`); + process.send?.({ + type: 'result', + payload: { ok: false, filePath, error: e, receiptFilepath }, + }); + } + } else if (msg.type === 'shutdown') { + // Handle shutdown message: log and exit gracefully + logger.info(`[w${workerId}] shutdown`); + log('Shutting down.'); + logStream.end(() => process.exit(0)); } - } else if (msg.type === 'shutdown') { - // Handle shutdown message: log and exit gracefully - logger.info(`[w${workerId}] shutdown`); - log('Shutting down.'); - logStream.end(() => process.exit(0)); - } - }); + }, + ); // Handle uncaught exceptions: log and exit process.on('uncaughtException', (err) => { diff --git a/src/lib/helpers/chunkOneCsvFile.ts b/src/lib/helpers/chunkOneCsvFile.ts index 9d90ae74..71984f2a 100644 --- a/src/lib/helpers/chunkOneCsvFile.ts +++ b/src/lib/helpers/chunkOneCsvFile.ts @@ -1,5 +1,5 @@ import { createReadStream, createWriteStream } from 'node:fs'; -import { mkdir, readdir, unlink } from 'node:fs/promises'; +import { mkdir, readdir, unlink, stat } from 'node:fs/promises'; import { pipeline } from 'node:stream/promises'; import { Transform } from 'node:stream'; import { once } from 'node:events'; @@ -21,6 +21,8 @@ export type ChunkOpts = { clearOutputDir: boolean; /** Chunk size in MB */ chunkSizeMB: number; + /** Optional report interval in milliseconds for progress updates */ + reportEveryMs?: number; /** Callback for progress updates */ onProgress: (processed: number, total?: number) => void; }; @@ -108,7 +110,17 @@ function approxRowBytes(obj: Record): number { * @returns Promise that resolves when done */ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { - const { filePath, outputDir, clearOutputDir, chunkSizeMB, onProgress } = opts; + const { + filePath, + outputDir, + clearOutputDir, + chunkSizeMB, + onProgress, + reportEveryMs = 500, + } = opts; + const { size: fileBytes } = await stat(filePath); // total bytes on disk + let lastTick = 0; + logger.info( colors.magenta(`Chunking ${filePath} into ~${chunkSizeMB}MB files...`), ); @@ -141,6 +153,21 @@ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { skip_empty_lines: true, }); + // running sample to estimate avg row bytes + let sampleBytes = 0; + let sampleRows = 0; + + const emit = (): void => { + const avg = sampleRows > 0 ? sampleBytes / sampleRows : 0; + const estTotal = + avg > 0 ? Math.max(totalLines, Math.ceil(fileBytes / avg)) : undefined; + onProgress(totalLines, estTotal); // <-- now has total + lastTick = Date.now(); + }; + + // seed an initial 0/N as soon as we start + emit(); + // Current active chunk writer; created after we know headers let writer: { /** Write a row object to the current chunk file */ @@ -195,6 +222,11 @@ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { // Determine the row size up-front const rowBytes = approxRowBytes(obj); + sampleBytes += rowBytes; + sampleRows += 1; + + // time-based throttle for UI updates + if (Date.now() - lastTick >= reportEveryMs) emit(); // If adding this row would exceed the threshold, roll first, // so this row becomes the first row in the next chunk. @@ -241,6 +273,7 @@ export async function chunkOneCsvFile(opts: ChunkOpts): Promise { await writer.end(); writer = null; } + emit(); // Final progress tick cb(); } catch (e) { cb(e as Error); diff --git a/src/lib/pooling/extraKeys.ts b/src/lib/pooling/extraKeys.ts index 94c9215f..9ac22ea6 100644 --- a/src/lib/pooling/extraKeys.ts +++ b/src/lib/pooling/extraKeys.ts @@ -145,6 +145,8 @@ export function createExtraKeyHandler( } }; + let viewing = false; // optional guard to prevent stacking viewers + /** * Show an inline combined log viewer for the selected sources/level. * Pauses dashboard repaint to keep the viewer visible until the user exits. @@ -152,17 +154,28 @@ export function createExtraKeyHandler( * @param sources - Log sources to include (e.g., "err", "warn", "info"). * @param level - Severity level to filter by (e.g., "error", "warn", "all"). */ - const view = async ( - sources: LogLocation[], - level: ViewLevel, - ): Promise => { + const view = (sources: LogLocation[], level: ViewLevel): void => { + if (viewing) return; + viewing = true; setPaused(true); - try { - await showCombinedLogs(logsBySlot, sources, level); - } finally { - setPaused(false); - repaint(); - } + + // optional UX: clear screen and show a hint + process.stdout.write('\x1b[2J\x1b[H'); // clear+home + process.stdout.write( + 'Combined logs viewer (press Esc or Ctrl+] to return)\n\n', + ); + + (async () => { + try { + await showCombinedLogs(logsBySlot, sources, level); + // NOTE: do NOT unpause here; ESC will handle it. + } catch { + // If showCombinedLogs throws, recover and unpause + viewing = false; + setPaused(false); + repaint(); + } + })(); }; /** @@ -234,6 +247,7 @@ export function createExtraKeyHandler( // Exit a viewer (Esc / Ctrl+]) — resume dashboard if (s === '\x1b' || s === '\x1d') { + viewing = false; setPaused(false); repaint(); } diff --git a/src/lib/pooling/types.ts b/src/lib/pooling/types.ts index 44426cd3..7631e289 100644 --- a/src/lib/pooling/types.ts +++ b/src/lib/pooling/types.ts @@ -16,20 +16,6 @@ export interface SlotState { progress?: TProg; } -/** Shape of the optional throughput callback */ -export type ProgressInfo = { - /** ID of the worker */ - workerId: number; - /** File path */ - filePath: string; - /** Number of success updates */ - successDelta: number; - /** Total number of success */ - successTotal: number; - /** Total number of items being processed */ - fileTotal: number; -}; - /** Message sent by a worker indicating it is ready to receive tasks. */ export type WorkerReady = { /** Type ready */ diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 3291dae4..654fb2c8 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -167,7 +167,7 @@ export async function parsePreferenceManagementCsvWithCache( Object.keys(currentColumnToIdentifierMap).length } identifiers and ${ Object.keys(currentColumnToPurposeName).length - } purposes`, + } purposes.`, ), ); preferences.forEach((pref, ind) => { From 702709eebbd1135102702678c01d04ff021aa92f Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 18 Aug 2025 17:36:02 -0700 Subject: [PATCH 49/72] rm --- src/commands/admin/chunk-csv/test/index.ts | 1 - .../consent/upload-preferences/impl.ts | 2 +- src/lib/pooling/extraKeys.ts | 255 ------------------ src/lib/pooling/tests/extraKeys.test.ts | 0 4 files changed, 1 insertion(+), 257 deletions(-) delete mode 100644 src/commands/admin/chunk-csv/test/index.ts delete mode 100644 src/lib/pooling/extraKeys.ts delete mode 100644 src/lib/pooling/tests/extraKeys.test.ts diff --git a/src/commands/admin/chunk-csv/test/index.ts b/src/commands/admin/chunk-csv/test/index.ts deleted file mode 100644 index c6a53ae3..00000000 --- a/src/commands/admin/chunk-csv/test/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../ui/plugin'; diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 00bb0789..a68643ef 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -14,6 +14,7 @@ import { runPool, dashboardPlugin, buildExportStatus, + createExtraKeyHandler, } from '../../../lib/pooling'; import { runChild } from './worker'; @@ -30,7 +31,6 @@ import { isCheckModeTotals, uploadPreferencesPlugin, } from './ui'; -import { createExtraKeyHandler } from '../../../lib/pooling/extraKeys'; /** * A unit of work: instructs a worker to upload (or check) a single CSV file. diff --git a/src/lib/pooling/extraKeys.ts b/src/lib/pooling/extraKeys.ts deleted file mode 100644 index 9ac22ea6..00000000 --- a/src/lib/pooling/extraKeys.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Shared handler for "extra" keyboard shortcuts used by the interactive dashboard. - * - * It wires: - * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all) - * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all) - * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard - * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings - * - * Usage (inside `runPool({... extraKeyHandler })`): - * ```ts - * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => - * createExtraKeyHandler({ logsBySlot, repaint, setPaused }) - * ``` - * - * If you also want export hotkeys + an "Exports" panel: - * ```ts - * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => - * createExtraKeyHandler({ - * logsBySlot, repaint, setPaused, - * exportMgr, // enables E/W/I/A - * exportStatus, // keeps panel timestamps up to date - * custom: { // optional, e.g. 'F' to export a CSV - * F: async ({ say, noteExport }) => { ... } - * } - * }) - * ``` - */ - -import type { ExportStatusMap } from './logRotation'; -import { showCombinedLogs, type LogLocation } from './showCombinedLogs'; -import type { SlotPaths } from './spawnWorkerProcess'; - -/** Severity filter applied by the viewer. */ -type ViewLevel = 'error' | 'warn' | 'all'; - -/** - * Options for {@link createExtraKeyHandler}. - */ -export type CreateExtraKeyHandlerOpts = { - /** - * Per-slot log file paths maintained by the runner; used to stream or export logs. - */ - logsBySlot: SlotPaths; - - /** - * Request an immediate dashboard repaint (e.g., after updating export status). - */ - repaint: () => void; - - /** - * Pause/unpause dashboard repainting. The handler pauses while a viewer is open - * to prevent the dashboard from overwriting the viewer output, then resumes on exit. - */ - setPaused: (p: boolean) => void; - - /** - * Optional export manager to enable uppercase export keys: - * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all) - * - * Provide this only if your command supports writing combined log files. - */ - exportMgr?: { - /** Destination directory for exported artifacts. */ - exportsDir: string; - /** - * Write a combined log file for the selected severity and return the absolute path. - * - * @param logs - Log paths to combine. - * @param which - Severity selection. - * @returns Absolute path to the written file. - */ - exportCombinedLogs: ( - logs: SlotPaths, - which: 'error' | 'warn' | 'info' | 'all', - ) => string; - }; - - /** - * Optional “Exports” status map. If provided, the handler updates timestamps - * when exports are written so your dashboard panel can reflect “last saved” times. - */ - exportStatus?: ExportStatusMap; - - /** - * Optional custom key bindings for command-specific actions. - * Each handler receives helpers to print messages and to update the exports panel. - * - * Example: - * ```ts - * custom: { - * F: async ({ say, noteExport }) => { - * const p = await writeFailingUpdatesCsv(...); - * say(`Wrote failing updates to: ${p}`); - * noteExport('failuresCsv', p); - * } - * } - * ``` - */ - custom?: Record< - string, - (ctx: { - /** Update {@link exportStatus} (if present) and repaint the dashboard. */ - noteExport: (slot: keyof ExportStatusMap, absPath: string) => void; - /** Print a line to stdout, automatically newline-terminated. */ - say: (s: string) => void; - }) => void | Promise - >; -}; - -/** - * Create a keypress handler for interactive viewers/exports. - * - * @param opts - Configuration for viewers, exports, and custom keys. - * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`. - */ -export function createExtraKeyHandler( - opts: CreateExtraKeyHandlerOpts, -): (buf: Buffer) => void { - const { logsBySlot, repaint, setPaused, exportMgr, exportStatus, custom } = - opts; - - const say = (s: string): void => { - process.stdout.write(`${s}\n`); - }; - - /** - * Record that an export was written and trigger a repaint so the dashboard’s - * "Exports" panel shows the updated timestamp/path. - * - * @param slot - Slot name in {@link ExportStatusMap} (e.g., "error", "warn", etc.). - * @param p - Absolute path to the exported file. - */ - const noteExport = (slot: keyof ExportStatusMap, p: string): void => { - const now = Date.now(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const cur: any = exportStatus?.[slot] ?? { path: p }; - if (exportStatus) { - exportStatus[slot] = { - path: p || cur.path, - savedAt: now, - exported: true, - }; - repaint(); - } - }; - - let viewing = false; // optional guard to prevent stacking viewers - - /** - * Show an inline combined log viewer for the selected sources/level. - * Pauses dashboard repaint to keep the viewer visible until the user exits. - * - * @param sources - Log sources to include (e.g., "err", "warn", "info"). - * @param level - Severity level to filter by (e.g., "error", "warn", "all"). - */ - const view = (sources: LogLocation[], level: ViewLevel): void => { - if (viewing) return; - viewing = true; - setPaused(true); - - // optional UX: clear screen and show a hint - process.stdout.write('\x1b[2J\x1b[H'); // clear+home - process.stdout.write( - 'Combined logs viewer (press Esc or Ctrl+] to return)\n\n', - ); - - (async () => { - try { - await showCombinedLogs(logsBySlot, sources, level); - // NOTE: do NOT unpause here; ESC will handle it. - } catch { - // If showCombinedLogs throws, recover and unpause - viewing = false; - setPaused(false); - repaint(); - } - })(); - }; - - /** - * Export combined logs (if an export manager was provided). - * - * @param which - Severity to export (e.g., "error", "warn", "info", "all"). - * @param label - Human-readable label for the export (e.g., "error", "warn"). - */ - const exportCombined = ( - which: 'error' | 'warn' | 'info' | 'all', - label: string, - ): void => { - if (!exportMgr) return; - try { - const p = exportMgr.exportCombinedLogs(logsBySlot, which); - say(`\nWrote combined ${label} logs to: ${p}`); - noteExport(which as keyof ExportStatusMap, p); - } catch { - say(`\nFailed to write combined ${label} logs`); - } - }; - - // The keypress handler the runner will attach to stdin. - return (buf: Buffer): void => { - const s = buf.toString('utf8'); - - // Viewers (lowercase) - if (s === 'e') { - view(['err'], 'error'); - return; - } - if (s === 'w') { - view(['warn', 'err'], 'warn'); - return; - } - if (s === 'i') { - view(['info'], 'all'); - return; - } - if (s === 'l') { - view(['out', 'err', 'structured'], 'all'); - return; - } - - // Exports (uppercase) — enabled only when exportMgr is present - if (s === 'E') { - exportCombined('error', 'error'); - return; - } - if (s === 'W') { - exportCombined('warn', 'warn'); - return; - } - if (s === 'I') { - exportCombined('info', 'info'); - return; - } - if (s === 'A') { - exportCombined('all', 'ALL'); - return; - } - - // Command-specific bindings - const fn = custom?.[s]; - if (fn) { - fn({ noteExport, say }); - return; - } - - // Exit a viewer (Esc / Ctrl+]) — resume dashboard - if (s === '\x1b' || s === '\x1d') { - viewing = false; - setPaused(false); - repaint(); - } - }; -} diff --git a/src/lib/pooling/tests/extraKeys.test.ts b/src/lib/pooling/tests/extraKeys.test.ts deleted file mode 100644 index e69de29b..00000000 From 3563168a0f0c6f4e135532e657dcef9424028048 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 18 Aug 2025 21:25:52 -0700 Subject: [PATCH 50/72] rm --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 6cd02507..9f1d0249 100644 --- a/README.md +++ b/README.md @@ -3385,5 +3385,3 @@ export async function main(): Promise { ## Proxy usage If you are trying to use the CLI inside a corporate firewall and need to send traffic through a proxy, you can do so via the `http_proxy` environment variable,with a command like `http_proxy=http://localhost:5051 transcend inventory pull --auth=$TRANSCEND_API_KEY`. - -> > > > > > > main From cb3c6942676cb7753b820d05ff1e6ce0741475db Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 18 Aug 2025 21:29:42 -0700 Subject: [PATCH 51/72] merg --- .../upload-preferences/computeFiles.ts | 55 ------------------- .../consent/upload-preferences/impl.ts | 3 +- .../consent/upload-preferences/worker.ts | 2 +- 3 files changed, 3 insertions(+), 57 deletions(-) delete mode 100644 src/commands/consent/upload-preferences/computeFiles.ts diff --git a/src/commands/consent/upload-preferences/computeFiles.ts b/src/commands/consent/upload-preferences/computeFiles.ts deleted file mode 100644 index 5ad427dc..00000000 --- a/src/commands/consent/upload-preferences/computeFiles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { mkdirSync } from 'fs'; -import { join, basename } from 'path'; - -/** - * Derive a "prefix" for a CSV file (basename without ".csv"), - * used to compute the receipt filename. - * - * @param file - The file name - * @returns The file prefix - */ -export function getFilePrefix(file: string): string { - return basename(file).replace('.csv', ''); -} - -/** - * Ensure and return the receipts folder path. - * If a directory flag is provided, default to sibling "../receipts". - * - * @param receiptFileDir - Optional directory for receipt files - * @param directory - Optional directory containing CSV files - * @returns The receipts folder path - */ -export function computeReceiptsFolder( - receiptFileDir: string | undefined, - directory: string | undefined, -): string { - const receiptsFolder = - receiptFileDir || - (directory ? join(directory, '../receipts') : './receipts'); - mkdirSync(receiptsFolder, { recursive: true }); - return receiptsFolder; -} - -/** - * Resolve the schema file path. - * - If user provided `schemaFilePath`, use it. - * - Otherwise, default near the directory or next to the single file. - * - * @param schemaFilePath - Optional path to the schema file - * @param directory - Optional directory containing CSV files - * @param firstFile - The first CSV file to derive the prefix from - * @returns The resolved schema file path - */ -export function computeSchemaFile( - schemaFilePath: string | undefined, - directory: string | undefined, - firstFile: string, -): string { - return ( - schemaFilePath || - (directory - ? join(directory, '../preference-upload-schema.json') - : `${getFilePrefix(firstFile)}-preference-upload-schema.json`) - ); -} diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index a68643ef..72a08f21 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -4,7 +4,6 @@ import { logger } from '../../../logger'; import { join } from 'node:path'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; -import { computeReceiptsFolder, computeSchemaFile } from './computeFiles'; import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; import { @@ -19,6 +18,8 @@ import { import { runChild } from './worker'; import { + computeReceiptsFolder, + computeSchemaFile, ExportManager, writeFailingUpdatesCsv, type FailingUpdateRow, diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index bb55c57b..e7814e6a 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -1,6 +1,5 @@ import { mkdirSync, createWriteStream } from 'node:fs'; import { join, dirname } from 'node:path'; -import { getFilePrefix } from './computeFiles'; import { splitCsvToList } from '../../../lib/requests'; import { interactivePreferenceUploaderFromPlan } from './upload/interactivePreferenceUploaderFromPlan'; import { makeSchemaState } from './schemaState'; @@ -13,6 +12,7 @@ import { logger } from '../../../logger'; import { buildInteractiveUploadPreferencePlan } from './upload/buildInteractiveUploadPlan'; import type { TaskCommonOpts } from './buildTaskOptions'; import type { ToWorker } from '../../../lib/pooling'; +import { getFilePrefix } from './artifacts'; /** * Run the child process for handling upload preferences. From 03b2659194fd5675d092255b734f847fdc35b3ef Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Wed, 20 Aug 2025 23:17:46 -0700 Subject: [PATCH 52/72] changes eexport --- src/commands/consent/upload-preferences/impl.ts | 6 +++--- src/lib/pooling/logRotation.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 72a08f21..885acb5a 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -291,10 +291,10 @@ export async function uploadPreferences( * * @param options - Options with logDir, logsBySlot, startedAt, finishedAt, etc. */ - postProcess: async ({ totals, logDir /* , logsBySlot, viewerMode */ }) => { + postProcess: async ({ totals }) => { try { // Persist failing updates CSV next to receipts/logDir. - const fPath = join(logDir || receiptsFolder, 'failing-updates.csv'); + const fPath = join(receiptsFolder, 'failing-updates.csv'); await writeFailingUpdatesCsv(failingUpdatesMem, fPath); exportStatus.failuresCsv = { path: fPath, @@ -357,7 +357,7 @@ export async function uploadPreferences( exportStatus, // keeps the exports panel updated custom: { F: async ({ noteExport, say }) => { - const fPath = join(exportMgr.exportsDir, 'failing-updates.csv'); + const fPath = join(receiptsFolder, 'failing-updates.csv'); await writeFailingUpdatesCsv(failingUpdatesMem, fPath); say(`\nWrote failing updates CSV to: ${fPath}`); noteExport('failuresCsv', fPath); diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts index 00774b60..4fb959bb 100644 --- a/src/lib/pooling/logRotation.ts +++ b/src/lib/pooling/logRotation.ts @@ -258,15 +258,15 @@ export type ExportStatusMap = { /** * Return export statuses * - * @param logDir - Log directory + * @param receiptsFolder - Receipts directory * @returns Export map */ -export function buildExportStatus(logDir: string): ExportStatusMap { +export function buildExportStatus(receiptsFolder: string): ExportStatusMap { return { - error: { path: join(logDir, 'combined-errors.log') }, - warn: { path: join(logDir, 'combined-warns.log') }, - info: { path: join(logDir, 'combined-info.log') }, - all: { path: join(logDir, 'combined-all.log') }, - failuresCsv: { path: join(logDir, 'failing-updates.csv') }, + error: { path: join(receiptsFolder, 'combined-errors.log') }, + warn: { path: join(receiptsFolder, 'combined-warns.log') }, + info: { path: join(receiptsFolder, 'combined-info.log') }, + all: { path: join(receiptsFolder, 'combined-all.log') }, + failuresCsv: { path: join(receiptsFolder, 'failing-updates.csv') }, }; } From 42be153b52ec23c4f21822976c45b44e50b96f17 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Wed, 27 Aug 2025 12:48:38 -0700 Subject: [PATCH 53/72] parquet splitter --- .vscode/settings.json | 1 + README.md | 64 ++ package.json | 7 +- pnpm-lock.yaml | 741 +++++++++++++++++- src/commands/admin/chunk-csv/impl.ts | 2 +- src/commands/admin/parquet-to-csv/command.ts | 49 ++ src/commands/admin/parquet-to-csv/impl.ts | 117 +++ src/commands/admin/parquet-to-csv/readme.ts | 60 ++ src/commands/admin/parquet-to-csv/ui/index.ts | 1 + .../admin/parquet-to-csv/ui/plugin.ts | 38 + src/commands/admin/parquet-to-csv/worker.ts | 81 ++ src/commands/admin/routes.ts | 2 + .../artifacts/ExportManager.ts | 9 +- .../consent/upload-preferences/impl.ts | 14 +- .../consent/upload-preferences/schemaState.ts | 8 - .../upload/buildInteractiveUploadPlan.ts | 5 + .../upload-preferences/upload/index.ts | 6 + .../interactivePreferenceUploaderFromPlan.ts | 18 +- .../upload/tests/loadReferenceData.test.ts | 1 - .../upload/transform/transformCsv.ts | 43 +- .../upload-preferences/upload/types.ts | 8 + .../consent/upload-preferences/worker.ts | 17 +- src/lib/graphql/createSombraGotInstance.ts | 3 + src/lib/helpers/collectCsvFilesOrExit.ts | 2 +- src/lib/helpers/collectParquetFilesOrExit.ts | 51 ++ src/lib/helpers/index.ts | 2 + src/lib/helpers/parquetToCsvOneFile.ts | 190 +++++ src/lib/pooling/logRotation.ts | 3 + .../getPreferenceUpdatesFromRow.ts | 4 +- .../getPreferencesForIdentifiers.ts | 31 +- .../parsePreferenceManagementCsv.ts | 5 + src/types/parquetjs-lite.d.ts | 29 + test.sh | 75 ++ test_parquet.sh | 109 +++ 34 files changed, 1745 insertions(+), 51 deletions(-) create mode 100644 src/commands/admin/parquet-to-csv/command.ts create mode 100644 src/commands/admin/parquet-to-csv/impl.ts create mode 100644 src/commands/admin/parquet-to-csv/readme.ts create mode 100644 src/commands/admin/parquet-to-csv/ui/index.ts create mode 100644 src/commands/admin/parquet-to-csv/ui/plugin.ts create mode 100644 src/commands/admin/parquet-to-csv/worker.ts create mode 100644 src/commands/consent/upload-preferences/upload/index.ts create mode 100644 src/commands/consent/upload-preferences/upload/types.ts create mode 100644 src/lib/helpers/collectParquetFilesOrExit.ts create mode 100644 src/lib/helpers/parquetToCsvOneFile.ts create mode 100644 src/types/parquetjs-lite.d.ts create mode 100644 test.sh create mode 100644 test_parquet.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index e48e3e8a..1a5c8537 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,6 +58,7 @@ "maxlevel", "Optionalize", "Parens", + "parquetjs", "pipeable", "preact", "pubspec", diff --git a/README.md b/README.md index 9f1d0249..756d3685 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ A command line interface that allows you to programatically interact with the Tr - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) - [`transcend admin chunk-csv`](#transcend-admin-chunk-csv) + - [`transcend admin parquet-to-csv`](#transcend-admin-parquet-to-csv) - [`transcend migration sync-ot`](#transcend-migration-sync-ot) - [Prompt Manager](#prompt-manager) - [Proxy usage](#proxy-usage) @@ -3169,6 +3170,69 @@ transcend admin chunk-csv \ transcend admin chunk-csv --directory=./working/files ``` +### `transcend admin parquet-to-csv` + +```txt +USAGE + transcend admin parquet-to-csv (--directory value) [--outputDir value] [--clearOutputDir] [--concurrency value] [--viewerMode] + transcend admin parquet-to-csv --help + +Streams every .parquet in --directory and writes CSV output files +- Runs files in parallel across worker processes (configurable via --concurrency). +- Validates row consistency; logs periodic progress and memory usage. + +FLAGS + --directory Directory containing Parquet files to convert (required) + [--outputDir] Directory to write CSV files (defaults to each input file's directory) + [--clearOutputDir/--noClearOutputDir] Clear the output directory before writing CSVs [default = true] + [--concurrency] Max number of worker processes (defaults based on CPU and file count) + [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] + -h --help Print help information and exit +``` + +#### Examples + +**Convert all Parquet files in a directory to CSV** + +```sh +transcend admin parquet-to-csv --directory=./working/parquet --outputDir=./working/csv +``` + +**Limit worker pool concurrency** + +```sh +transcend admin parquet-to-csv --directory=./working/parquet --outputDir=./working/csv --concurrency=4 +``` + +**Viewer mode - non-interactive dashboard** + +```sh +transcend admin parquet-to-csv --directory=./working/parquet --outputDir=./working/csv --viewerMode +``` + +**Clear output directory before writing** + +```sh +transcend admin parquet-to-csv --directory=./working/parquet --outputDir=./working/csv --clearOutputDir +``` + +**Run with all options** + +```sh +transcend admin parquet-to-csv \ + --directory=./working/parquet \ + --outputDir=./working/csv \ + --concurrency=2 \ + --viewerMode=false \ + --clearOutputDir +``` + +**Default output directory (writes next to each input file)** + +```sh +transcend admin parquet-to-csv --directory=./working/parquet +``` + ### `transcend migration sync-ot` ```txt diff --git a/package.json b/package.json index 1de8d0a9..9775696c 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ ] }, "dependencies": { + "@duckdb/node-api": "1.3.2-alpha.26", "@stricli/auto-complete": "^1.2.0", "@stricli/core": "^1.2.0", "@transcend-io/airgap.js-types": "^12.12.1", @@ -107,8 +108,8 @@ "@transcend-io/secret-value": "^1.2.0", "@transcend-io/type-utils": "^1.8.0", "JSONStream": "^1.3.5", - "cli-progress": "^3.11.2", "bluebird": "^3.7.2", + "cli-progress": "^3.11.2", "colors": "^1.4.0", "csv-parse": "^5.6.0", "fast-csv": "^4.3.6", @@ -135,6 +136,7 @@ }, "devDependencies": { "@types/JSONStream": "npm:@types/jsonstream@^0.8.33", + "@types/bluebird": "^3.5.38", "@types/cli-progress": "^3.11.0", "@types/colors": "^1.2.1", "@types/fuzzysearch": "^1.0.0", @@ -166,8 +168,7 @@ "tsx": "^4.20.3", "typescript": "^5.0.4", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4", - "@types/bluebird": "^3.5.38" + "vitest": "^3.2.4" }, "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1f7a79c..ab355116 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@duckdb/node-api': + specifier: 1.3.2-alpha.26 + version: 1.3.2-alpha.26 '@stricli/auto-complete': specifier: ^1.2.0 version: 1.2.0 @@ -50,6 +53,9 @@ importers: csv-parse: specifier: ^5.6.0 version: 5.6.0 + duckdb: + specifier: ^1.3.2 + version: 1.3.2(bluebird@3.7.2)(encoding@0.1.13) fast-csv: specifier: ^4.3.6 version: 4.3.6 @@ -73,7 +79,7 @@ importers: version: 16.11.0 graphql-request: specifier: ^5.0.0 - version: 5.2.0(graphql@16.11.0) + version: 5.2.0(encoding@0.1.13)(graphql@16.11.0) inquirer: specifier: '=7.3.3' version: 7.3.3 @@ -256,6 +262,37 @@ packages: resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} engines: {node: '>=6.9.0'} + '@duckdb/node-api@1.3.2-alpha.26': + resolution: {integrity: sha512-2gLtgJaiguAuPbXuS4NCthbIfEHo72FIF9514FKxg7sYY0gGJP1DgCpvBKdisyhWtBkNJ+jjn2p2PmRqkgZabQ==} + + '@duckdb/node-bindings-darwin-arm64@1.3.2-alpha.26': + resolution: {integrity: sha512-SioksFdehT2TWuGx6otWM2bCJ6kjD4Nq9r6xHCA4gvko1MBESIm+XhwvwcnGx2IaXTzKRZ+DPxNTPobPX18lpQ==} + cpu: [arm64] + os: [darwin] + + '@duckdb/node-bindings-darwin-x64@1.3.2-alpha.26': + resolution: {integrity: sha512-RZ1n2Vtzit8YLl9kjfuH17nZd5MOLZoWTlDo0rv2EuIgslC7XLbxK3ZH6jiUQ+VEGRZAjXWvwtPOFNvQQ3+LhQ==} + cpu: [x64] + os: [darwin] + + '@duckdb/node-bindings-linux-arm64@1.3.2-alpha.26': + resolution: {integrity: sha512-vhHmoiQpkeXb0PbhsLEEmulG05BxO1elNhHVMLobdAwVfoGEPE3fZkH4bPgvTGywwZC7ccqMFwMjYTTOIIJvLg==} + cpu: [arm64] + os: [linux] + + '@duckdb/node-bindings-linux-x64@1.3.2-alpha.26': + resolution: {integrity: sha512-tLzo/lGu5DXrDZhogI8N9ohhByU8UzGnBFFE/0blBuZ/g5SonvTL0yq1zE/NCkLSmDSBtqibz3ehZ1rZuBBQTQ==} + cpu: [x64] + os: [linux] + + '@duckdb/node-bindings-win32-x64@1.3.2-alpha.26': + resolution: {integrity: sha512-wNZmvYgnkUS3/pC9RQNO6yJeI8EANxB5LAyERJMHtMKrF6LB1nTEYmFbSlVJQ9n10qtNxKv/fF47AAAovOnG6g==} + cpu: [x64] + os: [win32] + + '@duckdb/node-bindings@1.3.2-alpha.26': + resolution: {integrity: sha512-066o5XrzesZnF/qhNlpmFwDxvIXNYQWr48bjw/ecyLfUcpcvqE0MyL1/zV5oScfcf7hSd/dSntRVzUnQmJCwXg==} + '@emnapi/core@1.4.4': resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==} @@ -453,6 +490,9 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: @@ -475,6 +515,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} @@ -488,6 +532,11 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@mapbox/node-pre-gyp@2.0.0': + resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} + engines: {node: '>=18'} + hasBin: true + '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} @@ -507,6 +556,15 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -636,6 +694,10 @@ packages: '@textlint/markdown-to-ast@12.6.1': resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + '@transcend-io/airgap.js-types@12.12.2': resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} @@ -958,6 +1020,13 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -968,6 +1037,22 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -997,10 +1082,18 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1111,6 +1204,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -1182,6 +1279,18 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1207,6 +1316,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} @@ -1239,6 +1352,9 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} @@ -1339,6 +1455,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depcheck@1.4.7: resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} engines: {node: '>=10'} @@ -1351,6 +1470,10 @@ packages: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -1386,6 +1509,9 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + duckdb@1.3.2: + resolution: {integrity: sha512-a2Y0kz7HOR36RMlxdCVQ6fnNZaaXjvdVFzi03T6bRLb6trvW8OuHDNBCHZ4XanRCeT0J7w7FvpuAVbgz0r5CYA==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1405,6 +1531,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -1419,10 +1548,17 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + envalid@8.1.0: resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} engines: {node: '>=18'} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1604,6 +1740,9 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + exponential-backoff@3.1.2: + resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1717,6 +1856,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1738,6 +1881,11 @@ packages: fuzzysearch@1.0.3: resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1781,6 +1929,11 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} @@ -1859,6 +2012,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + has@1.0.4: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} @@ -1880,14 +2036,33 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1903,6 +2078,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -1940,6 +2122,10 @@ packages: peerDependencies: fp-ts: ^2.5.0 + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -2014,6 +2200,9 @@ packages: is-hexadecimal@1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2255,6 +2444,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -2262,6 +2455,10 @@ packages: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + markdown-table@2.0.0: resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} @@ -2364,6 +2561,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@7.4.6: resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} engines: {node: '>=10'} @@ -2375,15 +2576,56 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -2425,6 +2667,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -2437,6 +2683,9 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2446,10 +2695,30 @@ packages: encoding: optional: true + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2505,6 +2774,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2647,6 +2920,18 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + publint@0.3.12: resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} engines: {node: '>=18'} @@ -2673,6 +2958,10 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2742,6 +3031,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2819,6 +3112,9 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2874,9 +3170,21 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2908,6 +3216,10 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -2987,6 +3299,14 @@ packages: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -3172,6 +3492,14 @@ packages: unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} @@ -3328,6 +3656,9 @@ packages: engines: {node: '>=8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3354,6 +3685,13 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -3429,6 +3767,33 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@duckdb/node-api@1.3.2-alpha.26': + dependencies: + '@duckdb/node-bindings': 1.3.2-alpha.26 + + '@duckdb/node-bindings-darwin-arm64@1.3.2-alpha.26': + optional: true + + '@duckdb/node-bindings-darwin-x64@1.3.2-alpha.26': + optional: true + + '@duckdb/node-bindings-linux-arm64@1.3.2-alpha.26': + optional: true + + '@duckdb/node-bindings-linux-x64@1.3.2-alpha.26': + optional: true + + '@duckdb/node-bindings-win32-x64@1.3.2-alpha.26': + optional: true + + '@duckdb/node-bindings@1.3.2-alpha.26': + optionalDependencies: + '@duckdb/node-bindings-darwin-arm64': 1.3.2-alpha.26 + '@duckdb/node-bindings-darwin-x64': 1.3.2-alpha.26 + '@duckdb/node-bindings-linux-arm64': 1.3.2-alpha.26 + '@duckdb/node-bindings-linux-x64': 1.3.2-alpha.26 + '@duckdb/node-bindings-win32-x64': 1.3.2-alpha.26 + '@emnapi/core@1.4.4': dependencies: '@emnapi/wasi-threads': 1.0.3 @@ -3573,6 +3938,8 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@gar/promisify@1.1.3': {} + '@graphql-typed-document-node/core@3.2.0(graphql@16.11.0)': dependencies: graphql: 16.11.0 @@ -3598,6 +3965,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.4 @@ -3612,6 +3983,19 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@mapbox/node-pre-gyp@2.0.0(encoding@0.1.13)': + dependencies: + consola: 3.4.2 + detect-libc: 2.0.4 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0(encoding@0.1.13) + nopt: 8.1.0 + semver: 7.7.2 + tar: 7.4.3 + transitivePeerDependencies: + - encoding + - supports-color + '@napi-rs/wasm-runtime@0.2.11': dependencies: '@emnapi/core': 1.4.4 @@ -3633,6 +4017,16 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.7.2 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + '@pkgjs/parseargs@0.11.0': optional: true @@ -3726,6 +4120,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@tootallnate/once@2.0.0': {} + '@transcend-io/airgap.js-types@12.12.2': dependencies: '@transcend-io/type-utils': 1.8.4 @@ -4090,12 +4486,33 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + abbrev@1.1.1: {} + + abbrev@3.0.1: {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4123,8 +4540,15 @@ snapshots: any-promise@1.3.0: {} + aproba@2.1.0: {} + are-docs-informative@0.0.2: {} + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -4240,6 +4664,29 @@ snapshots: cac@6.7.14: {} + cacache@16.1.3(bluebird@3.7.2): + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1(bluebird@3.7.2) + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + cacheable-lookup@5.0.4: {} cacheable-request@7.0.4: @@ -4330,6 +4777,12 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@2.0.0: {} + + chownr@3.0.0: {} + + clean-stack@2.2.0: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -4356,6 +4809,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + colors@1.4.0: {} combined-stream@1.0.8: @@ -4376,6 +4831,8 @@ snapshots: consola@3.4.2: {} + console-control-strings@1.1.0: {} + constant-case@3.0.4: dependencies: no-case: 3.0.4 @@ -4392,9 +4849,9 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cross-fetch@3.2.0: + cross-fetch@3.2.0(encoding@0.1.13): dependencies: - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - encoding @@ -4496,6 +4953,8 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + depcheck@1.4.7: dependencies: '@babel/parser': 7.28.0 @@ -4528,6 +4987,8 @@ snapshots: detect-file@1.0.0: {} + detect-libc@2.0.4: {} + detect-node@2.1.0: {} dir-glob@3.0.1: @@ -4576,6 +5037,16 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + duckdb@1.3.2(bluebird@3.7.2)(encoding@0.1.13): + dependencies: + '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13) + node-addon-api: 7.1.1 + node-gyp: 9.4.1(bluebird@3.7.2) + transitivePeerDependencies: + - bluebird + - encoding + - supports-color + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4594,6 +5065,11 @@ snapshots: emoji-regex@9.2.2: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -4604,10 +5080,14 @@ snapshots: entities@4.5.0: {} + env-paths@2.2.1: {} + envalid@8.1.0: dependencies: tslib: 2.8.1 + err-code@2.0.3: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -4910,6 +5390,8 @@ snapshots: expect-type@1.2.2: {} + exponential-backoff@3.1.2: {} + extend@3.0.2: {} external-editor@3.1.0: @@ -5023,6 +5505,10 @@ snapshots: fs-constants@1.0.0: {} + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -5043,6 +5529,17 @@ snapshots: fuzzysearch@1.0.3: {} + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -5108,6 +5605,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + global-agent@3.0.0: dependencies: boolean: 3.2.0 @@ -5171,10 +5676,10 @@ snapshots: graphemer@1.4.0: {} - graphql-request@5.2.0(graphql@16.11.0): + graphql-request@5.2.0(encoding@0.1.13)(graphql@16.11.0): dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.11.0) - cross-fetch: 3.2.0 + cross-fetch: 3.2.0(encoding@0.1.13) extract-files: 9.0.0 form-data: 3.0.3 graphql: 16.11.0 @@ -5210,6 +5715,8 @@ snapshots: dependencies: has-symbols: 1.1.0 + has-unicode@2.0.1: {} + has@1.0.4: {} hasown@2.0.2: @@ -5234,15 +5741,46 @@ snapshots: http-cache-semantics@4.2.0: {} + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -5254,6 +5792,10 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -5305,6 +5847,8 @@ snapshots: dependencies: fp-ts: 2.16.10 + ip-address@10.0.1: {} + is-alphabetical@1.0.4: {} is-alphanumerical@1.0.4: @@ -5383,6 +5927,8 @@ snapshots: is-hexadecimal@1.0.4: {} + is-lambda@1.0.1: {} + is-map@2.0.3: {} is-natural-number@4.0.1: {} @@ -5583,6 +6129,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@7.18.3: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 @@ -5591,6 +6139,28 @@ snapshots: dependencies: pify: 3.0.0 + make-fetch-happen@10.2.1(bluebird@3.7.2): + dependencies: + agentkeepalive: 4.6.0 + cacache: 16.1.3(bluebird@3.7.2) + http-cache-semantics: 4.2.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + markdown-table@2.0.0: dependencies: repeat-string: 1.6.1 @@ -5747,6 +6317,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@7.4.6: dependencies: brace-expansion: 2.0.2 @@ -5757,10 +6331,51 @@ snapshots: minimist@1.2.8: {} + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + mkdirp@1.0.4: {} + mkdirp@3.0.1: {} + mlly@1.7.4: dependencies: acorn: 8.15.0 @@ -5800,6 +6415,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@0.6.4: {} + neo-async@2.6.2: {} newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)): @@ -5812,12 +6429,48 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 - node-fetch@2.7.0: + node-addon-api@7.1.1: {} + + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + + node-gyp@9.4.1(bluebird@3.7.2): + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1(bluebird@3.7.2) + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.7.2 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 normalize-url@6.1.0: {} + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -5882,6 +6535,10 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + package-json-from-dist@1.0.1: {} package-manager-detector@1.3.0: {} @@ -5995,6 +6652,15 @@ snapshots: process-nextick-args@2.0.1: {} + promise-inflight@1.0.1(bluebird@3.7.2): + optionalDependencies: + bluebird: 3.7.2 + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + publint@0.3.12: dependencies: '@publint/pack': 0.1.2 @@ -6030,6 +6696,12 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -6115,6 +6787,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.12.0: {} + reusify@1.1.0: {} rimraf@3.0.2: @@ -6215,6 +6889,8 @@ snapshots: dependencies: type-fest: 0.13.1 + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6285,11 +6961,26 @@ snapshots: slash@3.0.0: {} + smart-buffer@4.2.0: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -6313,6 +7004,10 @@ snapshots: sprintf-js@1.1.3: {} + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + stable-hash@0.0.5: {} stackback@0.0.2: {} @@ -6411,6 +7106,24 @@ snapshots: to-buffer: 1.2.1 xtend: 4.0.2 + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + text-table@0.2.0: {} thenify-all@1.6.0: @@ -6616,6 +7329,14 @@ snapshots: trough: 1.0.5 vfile: 4.2.1 + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + unist-util-is@4.1.0: {} unist-util-stringify-position@2.0.3: @@ -6834,6 +7555,10 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -6856,6 +7581,10 @@ snapshots: y18n@5.0.8: {} + yallist@4.0.0: {} + + yallist@5.0.0: {} + yaml@1.10.2: {} yargs-parser@20.2.9: {} diff --git a/src/commands/admin/chunk-csv/impl.ts b/src/commands/admin/chunk-csv/impl.ts index b3ef0b8c..2068a05c 100644 --- a/src/commands/admin/chunk-csv/impl.ts +++ b/src/commands/admin/chunk-csv/impl.ts @@ -115,7 +115,7 @@ export async function chunkCsv( /* 5) Launch the pool runner with our hooks and custom dashboard plugin. */ await runPool({ - title: 'Chunk CSV', + title: `Chunk CSV - ${directory}`, baseDir: directory || outputDir || process.cwd(), childFlag: CHILD_FLAG, childModulePath: getCurrentModulePath(), diff --git a/src/commands/admin/parquet-to-csv/command.ts b/src/commands/admin/parquet-to-csv/command.ts new file mode 100644 index 00000000..eedaea57 --- /dev/null +++ b/src/commands/admin/parquet-to-csv/command.ts @@ -0,0 +1,49 @@ +import { buildCommand } from '@stricli/core'; + +export const parquetToCsvCommand = buildCommand({ + loader: async () => { + const { parquetToCsv } = await import('./impl'); + return parquetToCsv; + }, + parameters: { + flags: { + directory: { + kind: 'parsed', + parse: String, + brief: 'Directory containing Parquet files to convert (required)', + }, + outputDir: { + kind: 'parsed', + parse: String, + brief: + "Directory to write CSV files (defaults to each input file's directory)", + optional: true, + }, + clearOutputDir: { + kind: 'boolean', + brief: 'Clear the output directory before writing CSVs', + default: true, + }, + concurrency: { + kind: 'parsed', + parse: (v: string) => Math.max(1, Number(v) || 0), + brief: + 'Max number of worker processes (defaults based on CPU and file count)', + optional: true, + }, + viewerMode: { + kind: 'boolean', + brief: + 'Run in non-interactive viewer mode (no attach UI, auto-artifacts)', + default: false, + }, + }, + }, + docs: { + brief: + 'Convert all Parquet files in a directory to CSV (optionally chunked)', + fullDescription: `Streams every .parquet in --directory and writes CSV output files +- Runs files in parallel across worker processes (configurable via --concurrency). +- Validates row consistency; logs periodic progress and memory usage.`, + }, +}); diff --git a/src/commands/admin/parquet-to-csv/impl.ts b/src/commands/admin/parquet-to-csv/impl.ts new file mode 100644 index 00000000..e1234a60 --- /dev/null +++ b/src/commands/admin/parquet-to-csv/impl.ts @@ -0,0 +1,117 @@ +import type { LocalContext } from '../../../context'; +import colors from 'colors'; +import { logger } from '../../../logger'; +import { collectParquetFilesOrExit } from '../../../lib/helpers'; +import { + computePoolSize, + createExtraKeyHandler, + CHILD_FLAG, + type PoolHooks, + runPool, + dashboardPlugin, +} from '../../../lib/pooling'; +import { + runChild, + type ParquetProgress, + type ParquetResult, + type ParquetTask, +} from './worker'; +import { parquetToCsvPlugin } from './ui'; +import { doneInputValidation } from '../../../lib/cli/done-input-validation'; + +/** + * Returns the current module's path so the worker pool knows what file to re-exec. + * In Node ESM, __filename is undefined, so we fall back to argv[1]. + * + * @returns The current module's path. + */ +function getCurrentModulePath(): string { + if (typeof __filename !== 'undefined') { + return __filename as unknown as string; + } + return process.argv[1]; +} + +/** No custom totals for the header; the runner’s built-ins suffice. */ +type Totals = Record; + +export type ParquetToCsvCommandFlags = { + directory: string; + outputDir?: string; + clearOutputDir: boolean; + concurrency?: number; + viewerMode: boolean; +}; + +/** + * Convert all Parquet files in a directory to CSV (optionally chunked), in parallel. + * + * @param flags - The command flags. + */ +export async function parquetToCsv( + this: LocalContext, + flags: ParquetToCsvCommandFlags, +): Promise { + doneInputValidation(this.process.exit); + + const { directory, outputDir, clearOutputDir, concurrency, viewerMode } = + flags; + + /* 1) Discover .parquet inputs */ + const files = collectParquetFilesOrExit(directory, this); + + /* 2) Size the pool */ + const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); + + logger.info( + colors.green( + `Converting ${files.length} Parquet file(s) → CSV with pool size ${poolSize} (CPU=${cpuCount})`, + ), + ); + + /* 3) Build FIFO queue of tasks (one per file) */ + const queue = files.map((filePath) => ({ + filePath, + options: { outputDir, clearOutputDir }, + })); + + /* 4) Pool hooks */ + const hooks: PoolHooks = + { + nextTask: () => queue.shift(), + taskLabel: (t) => t.filePath, + initTotals: () => ({} as Totals), + initSlotProgress: () => undefined, + onProgress: (totals) => totals, + onResult: (totals, res) => ({ totals, ok: !!res.ok }), + postProcess: async () => { + // nothing special post-run + }, + }; + + /* 5) Launch the pool runner with custom dashboard plugin */ + await runPool({ + title: `Parquet → CSV - ${directory}`, + baseDir: directory || outputDir || process.cwd(), + childFlag: CHILD_FLAG, + childModulePath: getCurrentModulePath(), + poolSize, + cpuCount, + filesTotal: files.length, + hooks, + viewerMode, + render: (input) => dashboardPlugin(input, parquetToCsvPlugin, viewerMode), + extraKeyHandler: ({ logsBySlot, repaint, setPaused }) => + createExtraKeyHandler({ logsBySlot, repaint, setPaused }), + }); +} + +/* ------------------------------------------------------------------------------------------------- + * If invoked directly as a child process, enter worker loop + * ------------------------------------------------------------------------------------------------- */ +if (process.argv.includes(CHILD_FLAG)) { + runChild().catch((err) => { + logger.error(err); + process.exit(1); + }); +} diff --git a/src/commands/admin/parquet-to-csv/readme.ts b/src/commands/admin/parquet-to-csv/readme.ts new file mode 100644 index 00000000..4df98140 --- /dev/null +++ b/src/commands/admin/parquet-to-csv/readme.ts @@ -0,0 +1,60 @@ +import { buildExamples } from '../../../lib/docgen/buildExamples'; +import type { ParquetToCsvCommandFlags } from './impl'; + +const examples = buildExamples( + ['admin', 'parquet-to-csv'], + [ + { + description: 'Convert all Parquet files in a directory to CSV', + flags: { + directory: './working/parquet', + outputDir: './working/csv', + }, + }, + { + description: 'Limit worker pool concurrency', + flags: { + directory: './working/parquet', + outputDir: './working/csv', + concurrency: 4, + }, + }, + { + description: 'Viewer mode - non-interactive dashboard', + flags: { + directory: './working/parquet', + outputDir: './working/csv', + viewerMode: true, + }, + }, + { + description: 'Clear output directory before writing', + flags: { + directory: './working/parquet', + outputDir: './working/csv', + clearOutputDir: true, + }, + }, + { + description: 'Run with all options', + flags: { + directory: './working/parquet', + outputDir: './working/csv', + concurrency: 2, + viewerMode: false, + clearOutputDir: true, + }, + }, + { + description: 'Default output directory (writes next to each input file)', + flags: { + directory: './working/parquet', + }, + }, + ], +); + +export default `#### Examples + +${examples} +`; diff --git a/src/commands/admin/parquet-to-csv/ui/index.ts b/src/commands/admin/parquet-to-csv/ui/index.ts new file mode 100644 index 00000000..1110b645 --- /dev/null +++ b/src/commands/admin/parquet-to-csv/ui/index.ts @@ -0,0 +1 @@ +export * from './plugin'; diff --git a/src/commands/admin/parquet-to-csv/ui/plugin.ts b/src/commands/admin/parquet-to-csv/ui/plugin.ts new file mode 100644 index 00000000..0072feb0 --- /dev/null +++ b/src/commands/admin/parquet-to-csv/ui/plugin.ts @@ -0,0 +1,38 @@ +import { + makeHeader, + makeWorkerRows, + type ChunkSlotProgress, + type CommonCtx, + type DashboardPlugin, +} from '../../../../lib/pooling'; + +/** + * Header for parquet-to-csv (no extra totals block). + * + * @param ctx - Dashboard context. + * @returns Header lines. + */ +function renderHeader( + ctx: CommonCtx, +): string[] { + // no extra lines — reuse the shared header as-is + return makeHeader(ctx); +} + +/** + * Worker rows for parquet-to-csv — share the generic row renderer. + * + * @param ctx - Dashboard context. + * @returns Array of strings, each representing one worker row. + */ +function renderWorkers( + ctx: CommonCtx, +): string[] { + return makeWorkerRows(ctx); +} + +export const parquetToCsvPlugin: DashboardPlugin = { + renderHeader, + renderWorkers, + // no extras +}; diff --git a/src/commands/admin/parquet-to-csv/worker.ts b/src/commands/admin/parquet-to-csv/worker.ts new file mode 100644 index 00000000..d98f65ef --- /dev/null +++ b/src/commands/admin/parquet-to-csv/worker.ts @@ -0,0 +1,81 @@ +import { parquetToCsvOneFile, extractErrorMessage } from '../../../lib/helpers'; +import type { ToWorker } from '../../../lib/pooling'; +import { logger } from '../../../logger'; + +export type ParquetTask = { + /** Absolute path of the Parquet file to convert. */ + filePath: string; + options: { + /** Optional directory where CSV output files should be written. */ + outputDir?: string; + /** Whether to clear any pre-existing output before writing new ones. */ + clearOutputDir: boolean; + }; +}; + +export type ParquetProgress = { + /** File being processed by the worker. */ + filePath: string; + /** Rows processed so far. */ + processed: number; + /** Optional known total rows (not always available). */ + total?: number; +}; + +export type ParquetResult = { + ok: boolean; + filePath: string; + error?: string; +}; + +/** + * Worker loop: convert a single Parquet file to one or more CSV files. + */ +export async function runChild(): Promise { + const workerId = Number(process.env.WORKER_ID || '0'); + logger.info(`[w${workerId}] ready pid=${process.pid}`); + process.send?.({ type: 'ready' }); + + process.on('message', async (msg: ToWorker) => { + if (!msg || typeof msg !== 'object') return; + + if (msg.type === 'shutdown') { + process.exit(0); + } + if (msg.type !== 'task') return; + + const { filePath, options } = msg.payload; + const { outputDir, clearOutputDir } = options; + + try { + logger.info(`[w${workerId}] processing ${filePath}`); + await parquetToCsvOneFile({ + filePath, + outputDir, + clearOutputDir, + onProgress: (processed, total) => + process.send?.({ + type: 'progress', + payload: { filePath, processed, total }, + }), + }); + + process.send?.({ + type: 'result', + payload: { ok: true, filePath }, + }); + } catch (err) { + const message = extractErrorMessage(err); + logger.error(`[w${workerId}] ERROR ${filePath}: ${err.stack || message}`); + process.send?.({ + type: 'result', + payload: { ok: false, filePath, error: message }, + }); + } + }); + + // keep alive until shutdown + await new Promise(() => { + // Do nothing + }); +} diff --git a/src/commands/admin/routes.ts b/src/commands/admin/routes.ts index d9d7567b..b97e74ec 100644 --- a/src/commands/admin/routes.ts +++ b/src/commands/admin/routes.ts @@ -1,11 +1,13 @@ import { buildRouteMap } from '@stricli/core'; import { generateApiKeysCommand } from './generate-api-keys/command'; import { chunkCsvCommand } from './chunk-csv/command'; +import { parquetToCsvCommand } from './parquet-to-csv/command'; export const adminRoutes = buildRouteMap({ routes: { 'generate-api-keys': generateApiKeysCommand, 'chunk-csv': chunkCsvCommand, + 'parquet-to-csv': parquetToCsvCommand, }, docs: { brief: 'Admin commands', diff --git a/src/commands/consent/upload-preferences/artifacts/ExportManager.ts b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts index c3040a46..9b4326a1 100644 --- a/src/commands/consent/upload-preferences/artifacts/ExportManager.ts +++ b/src/commands/consent/upload-preferences/artifacts/ExportManager.ts @@ -103,16 +103,15 @@ export class ExportManager { if (!this.exportsDir) throw new Error('exportsDir not set'); mkdirSync(this.exportsDir, { recursive: true }); - const ts = new Date().toISOString().replace(/[:.]/g, '-'); const outPath = resolve( this.exportsDir, kind === 'error' - ? `combined-errors-${ts}.log` + ? 'combined-errors.log' : kind === 'warn' - ? `combined-warns-${ts}.log` + ? 'combined-warns.log' : kind === 'info' - ? `combined-info-${ts}.log` - : `combined-all-${ts}.log`, + ? 'combined-info.log' + : 'combined-all.log', ); const lines: string[] = []; diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index 885acb5a..dac5a0de 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -291,7 +291,7 @@ export async function uploadPreferences( * * @param options - Options with logDir, logsBySlot, startedAt, finishedAt, etc. */ - postProcess: async ({ totals }) => { + postProcess: async ({ totals, logsBySlot }) => { try { // Persist failing updates CSV next to receipts/logDir. const fPath = join(receiptsFolder, 'failing-updates.csv'); @@ -302,9 +302,13 @@ export async function uploadPreferences( exported: true, }; - // (Optional) If you want to auto-export combined logs like the old viewer: - // - import and use ExportManager here, using `logsBySlot`. - // - e.g., exportManager.exportCombinedLogs(logsBySlot, 'error' | 'warn' | 'info' | 'all') + // Save logs + await Promise.all([ + exportMgr.exportCombinedLogs(logsBySlot, 'error'), + exportMgr.exportCombinedLogs(logsBySlot, 'warn'), + exportMgr.exportCombinedLogs(logsBySlot, 'info'), + exportMgr.exportCombinedLogs(logsBySlot, 'all'), + ]); // Summarize totals to stdout (parity with the old implementation) if (isUploadModeTotals(totals)) { @@ -338,7 +342,7 @@ export async function uploadPreferences( UploadPreferencesResult, Totals >({ - title: 'Upload Preferences', + title: `Upload Preferences - ${directory}`, baseDir: directory || receiptsFolder || process.cwd(), childFlag: CHILD_FLAG, childModulePath: getCurrentModulePath(), diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts index e9e0476f..95917fe3 100644 --- a/src/commands/consent/upload-preferences/schemaState.ts +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -1,11 +1,3 @@ -/** - * Module: state/schemaState - * - * Thin wrapper over PersistedState(FileFormatState) for schema/config - * discovered during CSV parsing. Includes exponential backoff on transient - * JSON parse errors ("Unexpected end of JSON input") which can occur if a - * schema cache file is being written by another process. - */ import { PersistedState } from '@transcend-io/persisted-state'; import { FileFormatState, diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index 1d1d84f7..85aefd66 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -20,6 +20,7 @@ import type { FormattedAttribute } from '../../../../lib/graphql/formatAttribute import type { GraphQLClient } from 'graphql-request'; import { limitRecords } from '../../../../lib/helpers'; import { transformCsv } from './transform'; +import type { PreferenceUploadProgress } from './types'; export interface InteractiveUploadPreferencePlan { /** CSV file path to load preference records from */ @@ -75,6 +76,7 @@ export async function buildInteractiveUploadPreferencePlan({ identifierColumns, columnsToIgnore = [], attributes = [], + onProgress, }: { /** Transcend GraphQL client */ client: GraphQLClient; @@ -106,6 +108,8 @@ export async function buildInteractiveUploadPreferencePlan({ identifierDownloadLogInterval?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; + /** on progress callback */ + onProgress?: (info: PreferenceUploadProgress) => void; }): Promise { const parsedAttributes = parseAttributesFromString(attributes); @@ -146,6 +150,7 @@ export async function buildInteractiveUploadPreferencePlan({ identifierColumns, identifierDownloadLogInterval, columnsToIgnore, + onProgress, }, schema.state, ); diff --git a/src/commands/consent/upload-preferences/upload/index.ts b/src/commands/consent/upload-preferences/upload/index.ts new file mode 100644 index 00000000..22d1a6e7 --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/index.ts @@ -0,0 +1,6 @@ +export * from './types'; +export * from './loadReferenceData'; +export * from './buildInteractiveUploadPlan'; +export * from './batchUploader'; +export * from './transform'; +export * from './interactivePreferenceUploaderFromPlan'; diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 6715c198..f01b5234 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -11,6 +11,7 @@ import { extractErrorMessage, limitRecords } from '../../../../lib/helpers'; import type { InteractiveUploadPreferencePlan } from './buildInteractiveUploadPlan'; import type { PreferenceReceiptsInterface } from '../artifacts/receipts'; import type { Got } from 'got'; +import type { PreferenceUploadProgress } from './types'; /** * Execute the upload using a pre-built InteractiveUploadPlan. @@ -67,15 +68,8 @@ export async function interactivePreferenceUploaderFromPlan( uploadConcurrency?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; - /* existing options ... */ - onProgress?: (info: { - /** how many records just succeeded */ - successDelta: number; - /** cumulative successes in this file */ - successTotal: number; - /** total records that will be uploaded in this file */ - fileTotal: number; - }) => void; + /** on progress callback */ + onProgress?: (info: PreferenceUploadProgress) => void; }, ): Promise { // Build final payloads (pure transform; no network) @@ -123,6 +117,10 @@ export async function interactivePreferenceUploaderFromPlan( const t0 = Date.now(); let uploadedCount = 0; + // reset failing + await receipts.setFailing({}); + + // Get successful and filtered entries const successful = receipts.getSuccessful(); const allEntries = Object.entries(pendingUpdates) as Array< [string, PreferenceUpdateItem] @@ -159,7 +157,7 @@ export async function interactivePreferenceUploaderFromPlan( // Retry policy for "retry in place" statuses const retryPolicy = { - maxAttempts: 3, + maxAttempts: 5, delayMs: 10_000, shouldRetry: (status?: number) => // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/commands/consent/upload-preferences/upload/tests/loadReferenceData.test.ts b/src/commands/consent/upload-preferences/upload/tests/loadReferenceData.test.ts index d8378ca1..4ed08777 100644 --- a/src/commands/consent/upload-preferences/upload/tests/loadReferenceData.test.ts +++ b/src/commands/consent/upload-preferences/upload/tests/loadReferenceData.test.ts @@ -1,4 +1,3 @@ -// src/commands/consent/upload-preferences/upload/tests/loadReferenceData.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; // Type-only imports diff --git a/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts b/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts index 90c87e93..81cb3321 100644 --- a/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts +++ b/src/commands/consent/upload-preferences/upload/transform/transformCsv.ts @@ -21,7 +21,8 @@ export function transformCsv( const isUdp = keys.includes('email_address') && keys.includes('person_id') && - keys.includes('member_id'); + keys.includes('member_id') && + keys.includes('birth_dt'); if (isUdp) { logger.info( colors.yellow( @@ -33,9 +34,20 @@ export function transformCsv( const email = (pref.email_address || '').toLowerCase().trim(); const emailAddress = !email || disallowedEmails.includes(email) ? '' : pref.email_address; + const birthDate = new Date(pref.birth_dt); + if (!!pref.birth_dt || Number.isNaN(birthDate.getTime())) { + logger.warn( + colors.yellow(`No birth date for record: ${pref.email_address}`), + ); + } return { ...pref, - person_id: pref.person_id !== '-2' ? pref.person_id : '', + Minor: + !pref.birth_dt || Number.isNaN(birthDate.getTime()) + ? '' + : Date.now() - birthDate.getTime() < 1000 * 60 * 60 * 24 * 365 * 18 + ? 'True' + : 'False', email_address: emailAddress, // preference email address over transcendID transcendID: emailAddress @@ -47,6 +59,33 @@ export function transformCsv( }); } + const isAdobe = + keys.includes('hashedCostcoID') && + keys.includes('address') && + keys.includes('lastUpdatedDate'); + if (isAdobe) { + logger.info(colors.green('Pre-processing as adobe ')); + return preferences.map((pref) => { + if (!pref.lastUpdatedDate) { + logger.warn( + colors.yellow( + `Record missing lastUpdatedDate - setting to now() - ${JSON.stringify( + pref, + )}`, + ), + ); + } + return { + ...pref, + lastUpdatedDate: pref.lastUpdatedDate + ? pref.lastUpdatedDate + : new Date('08/24/2025').toISOString(), + }; + }); + } + + logger.info(colors.green('No special transformations applied.')); + // FIXME skip the emails return preferences; } diff --git a/src/commands/consent/upload-preferences/upload/types.ts b/src/commands/consent/upload-preferences/upload/types.ts new file mode 100644 index 00000000..cb1e1b1f --- /dev/null +++ b/src/commands/consent/upload-preferences/upload/types.ts @@ -0,0 +1,8 @@ +export interface PreferenceUploadProgress { + /** how many records just succeeded */ + successDelta: number; + /** cumulative successes in this file */ + successTotal: number; + /** total records that will be uploaded in this file */ + fileTotal: number; +} diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index e7814e6a..ed96996a 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -1,7 +1,10 @@ import { mkdirSync, createWriteStream } from 'node:fs'; import { join, dirname } from 'node:path'; import { splitCsvToList } from '../../../lib/requests'; -import { interactivePreferenceUploaderFromPlan } from './upload/interactivePreferenceUploaderFromPlan'; +import { + interactivePreferenceUploaderFromPlan, + buildInteractiveUploadPreferencePlan, +} from './upload'; import { makeSchemaState } from './schemaState'; import { makeReceiptsState } from './artifacts/receipts/receiptsState'; import { @@ -9,7 +12,6 @@ import { createSombraGotInstance, } from '../../../lib/graphql'; import { logger } from '../../../logger'; -import { buildInteractiveUploadPreferencePlan } from './upload/buildInteractiveUploadPlan'; import type { TaskCommonOpts } from './buildTaskOptions'; import type { ToWorker } from '../../../lib/pooling'; import { getFilePrefix } from './artifacts'; @@ -101,6 +103,17 @@ export async function runChild(): Promise { identifierColumns: options.identifierColumns, columnsToIgnore: options.columnsToIgnore, attributes: splitCsvToList(options.attributes), + // Report progress to parent process + onProgress: ({ successTotal, fileTotal }) => { + process.send?.({ + type: 'progress', + payload: { + filePath, + processed: successTotal, + total: fileTotal, + }, + }); + }, }); // Step 2: Execute the upload using the plan diff --git a/src/lib/graphql/createSombraGotInstance.ts b/src/lib/graphql/createSombraGotInstance.ts index 90de20d8..59e4a32d 100644 --- a/src/lib/graphql/createSombraGotInstance.ts +++ b/src/lib/graphql/createSombraGotInstance.ts @@ -1,7 +1,9 @@ import got, { Got } from 'got'; +import colors from 'colors'; import { ORGANIZATION } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { buildTranscendGraphQLClient } from './buildTranscendGraphQLClient'; +import { logger } from '../../logger'; /** * Instantiate an instance of got that is capable of making requests @@ -44,6 +46,7 @@ export async function createSombraGotInstance( 'https://docs.transcend.io/docs/articles/sombra/deploying/customizing-sombra/networking', ); } + logger.info(colors.green(`Using sombra: ${customerUrl}`)); // Create got instance with default values return got.extend({ prefixUrl: customerUrl, diff --git a/src/lib/helpers/collectCsvFilesOrExit.ts b/src/lib/helpers/collectCsvFilesOrExit.ts index e956dfab..d70a4bae 100644 --- a/src/lib/helpers/collectCsvFilesOrExit.ts +++ b/src/lib/helpers/collectCsvFilesOrExit.ts @@ -44,6 +44,6 @@ export function collectCsvFilesOrExit( logger.error(colors.red(`No CSV files found in directory: ${directory}`)); localContext.process.exit(1); } - + logger.info(colors.green(`Found: ${files.join(', ')} CSV files`)); return files; } diff --git a/src/lib/helpers/collectParquetFilesOrExit.ts b/src/lib/helpers/collectParquetFilesOrExit.ts new file mode 100644 index 00000000..ccdaa8f7 --- /dev/null +++ b/src/lib/helpers/collectParquetFilesOrExit.ts @@ -0,0 +1,51 @@ +import { join } from 'node:path'; +import { readdirSync, statSync } from 'node:fs'; +import colors from 'colors'; +import { logger } from '../../logger'; +import type { LocalContext } from '../../context'; + +/** + * Validate flags and collect Parquet file paths from a directory. + * On validation error, the provided `exit` function is called. + * + * @param directory - the directory containing Parquet files + * @param localContext - the context of the command, used for logging and exit + * @returns an array of valid Parquet file paths + */ +export function collectParquetFilesOrExit( + directory: string | undefined, + localContext: LocalContext, +): string[] { + if (!directory) { + logger.error(colors.red('A --directory must be provided.')); + localContext.process.exit(1); + } + + let files: string[] = []; + try { + const entries = readdirSync(directory); + files = entries + .filter((f) => f.endsWith('.parquet')) + .map((f) => join(directory, f)) + .filter((p) => { + try { + return statSync(p).isFile(); + } catch { + return false; + } + }); + } catch (err) { + logger.error(colors.red(`Failed to read directory: ${directory}`)); + logger.error(colors.red((err as Error).message)); + localContext.process.exit(1); + } + + if (files.length === 0) { + logger.error( + colors.red(`No Parquet files found in directory: ${directory}`), + ); + localContext.process.exit(1); + } + logger.info(colors.green(`Found: ${files.join(', ')} parquet files`)); + return files; +} diff --git a/src/lib/helpers/index.ts b/src/lib/helpers/index.ts index acce1e3e..036fb079 100644 --- a/src/lib/helpers/index.ts +++ b/src/lib/helpers/index.ts @@ -10,3 +10,5 @@ export * from './retrySamePromise'; export * from './limitRecords'; export * from './RateCounter'; export * from './readSafe'; +export * from './collectParquetFilesOrExit'; +export * from './parquetToCsvOneFile'; diff --git a/src/lib/helpers/parquetToCsvOneFile.ts b/src/lib/helpers/parquetToCsvOneFile.ts new file mode 100644 index 00000000..60fee4b4 --- /dev/null +++ b/src/lib/helpers/parquetToCsvOneFile.ts @@ -0,0 +1,190 @@ +import { mkdirSync, rmSync, existsSync } from 'node:fs'; +import { dirname, join, parse } from 'node:path'; +import colors from 'colors'; +import { logger } from '../../logger'; +import { DuckDBInstance, type DuckDBConnection } from '@duckdb/node-api'; + +/** Progress callback used by the parent runner to surface progress to the UI. */ +type OnProgress = (processed: number, total?: number) => void; + +/** + * Options for converting a single Parquet file into a single CSV file. + */ +export type ParquetToCsvOneFileOptions = { + /** Absolute or relative path to the input `.parquet` file. */ + filePath: string; + /** + * Directory where the output CSV will be written. + * If omitted, the CSV is written next to the input file. + */ + outputDir?: string; + /** + * When true, removes a pre-existing output file with the same name before writing. + * Useful for re-runs; ignored if the file does not exist. + */ + clearOutputDir: boolean; + /** + * Optional progress hook. Called with the number of processed records. + * `total` is not computed here; it will be `undefined`. + */ + onProgress?: OnProgress; +}; + +/** + * Convert a single Parquet file to a single CSV file (1:1) using DuckDB. + * + * Output naming: `${basename}.csv` in `outputDir ?? dirname(filePath)`. + * + * Errors: + * - Throws on I/O failures or DuckDB execution errors. + * + * Why DuckDB? + * - Robust reader for many Parquet dialects (e.g., Spark output, nested types, timestamps). + * - Streaming COPY handles large files without loading everything into JS memory. + * + * What this does: + * - Opens an in-memory DuckDB database (no `.db` file created). + * - Optionally disables temp spilling to disk (so only your CSV is written). + * - Executes a single `COPY (SELECT * FROM read_parquet(...)) TO ...` statement. + * - Produces exactly one CSV per input Parquet (no chunking or rotation). + * + * Notes & defaults: + * - DuckDBInstance: `:memory:` (ephemeral). No persistent DB file is created. + * - Temp files: disabled via `PRAGMA temp_directory=''` (best-effort; ignored if unsupported). + * - CSV format: header row, comma delimiter, double-quote quoting, empty string for NULL. + * - Progress: DuckDB COPY doesn't expose row-level progress via the JS API; we emit a + * best-effort final callback. + * + * Requirements: + * - `@duckdb/node-api` npm package installed and available at runtime. + * - Supported platform binary (mac arm64/x64, linux x64, windows x64). + * + * @param opts - Conversion options + * @returns Promise when the CSV has been written + */ +export async function parquetToCsvOneFile( + opts: ParquetToCsvOneFileOptions, +): Promise { + const { filePath, outputDir, clearOutputDir, onProgress } = opts; + + const baseDir = outputDir || dirname(filePath); + const { name: baseName } = parse(filePath); + const outPath = join(baseDir, `${baseName}.csv`); + + // Ensure output directory exists + mkdirSync(baseDir, { recursive: true }); + + // Remove any pre-existing output file if requested + if (clearOutputDir && existsSync(outPath)) { + try { + rmSync(outPath, { force: true }); + } catch (err) { + logger.warn( + colors.yellow( + `Could not remove existing output file ${outPath}: ${ + (err as Error).message + }`, + ), + ); + } + } + + // In-memory DB: no .db file created on disk + const db = await DuckDBInstance.create(':memory:'); + const conn = await db.connect(); + + try { + // Optional: prevent DuckDB from creating temp files on disk (best-effort). + // Some versions may ignore or error; we ignore such errors safely. + await runIgnoreError(conn, "PRAGMA temp_directory='';"); + + // Optionally: cap memory to encourage in-memory execution or fail-fast + // (commented out by default; uncomment to enforce a limit) + // await runIgnoreError(conn, "PRAGMA memory_limit='4GB';"); + + // Ensure stable CSV settings: header, comma delimiter, double quotes, empty string for NULLs. + // Escape single quotes for SQL string literals + const q = (p: string): string => `'${p.replace(/'/g, "''")}'`; + + // Use COPY with a subquery so DuckDB streams Parquet -> CSV efficiently. + const sql = ` + COPY (SELECT * FROM read_parquet(${q(filePath)})) + TO ${q(outPath)} + (HEADER, DELIMITER ',', QUOTE '"', ESCAPE '"', NULL ''); + `; + + await run(conn, sql); + + // Best-effort progress notification (DuckDB JS API doesn't expose progress for COPY) + onProgress?.(0, undefined); + + logger.info(colors.green(`Wrote CSV → ${outPath}`)); + } finally { + // Close connection + db handles gracefully + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await disposeSafe(conn as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await disposeSafe(db as any); + } +} + +/* ============================================================================= + * DuckDB helpers + * ============================================================================= + */ + +/** + * Execute a SQL statement on a DuckDB connection and dispose the result. + * + * @param conn - DuckDB connection + * @param sql - SQL string to run + * @returns Promise + */ +async function run(conn: DuckDBConnection, sql: string): Promise { + const result = await conn.run(sql); + // The high-level API returns a Result; ensure we dispose it to free buffers. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await disposeSafe(result as any); +} + +/** + * Execute a SQL statement but ignore any error that occurs. + * Useful for best-effort PRAGMAs that may not be supported across versions. + * + * @param conn - DuckDB connection + * @param sql - SQL string to run + * @returns Promise + */ +async function runIgnoreError( + conn: DuckDBConnection, + sql: string, +): Promise { + try { + await run(conn, sql); + } catch { + // ignore + } +} + +/** + * Dispose a DuckDB resource (connection or instance) if present. + * + * @param handle - Object exposing an async `dispose()` method + * @returns Promise + */ +async function disposeSafe( + handle: + | { + /** Dispose handler */ + dispose: () => Promise; + } + | null + | undefined, +): Promise { + if (!handle || typeof handle.dispose !== 'function') return; + try { + await handle.dispose(); + } catch { + // ignore + } +} diff --git a/src/lib/pooling/logRotation.ts b/src/lib/pooling/logRotation.ts index 4fb959bb..2121c4fb 100644 --- a/src/lib/pooling/logRotation.ts +++ b/src/lib/pooling/logRotation.ts @@ -221,6 +221,7 @@ export function initLogDir(rootDir: string): string { const logDir = join(rootDir, 'logs'); mkdirSync(logDir, { recursive: true }); + // FIXME const RESET_MODE = (process.env.RESET_LOGS as 'truncate' | 'delete') ?? 'truncate'; resetWorkerLogs(logDir, RESET_MODE); @@ -258,6 +259,8 @@ export type ExportStatusMap = { /** * Return export statuses * + * FIXME what is this for? + * * @param receiptsFolder - Receipts directory * @returns Export map */ diff --git a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts index 6e67bb0d..84faed5d 100644 --- a/src/lib/preference-management/getPreferenceUpdatesFromRow.ts +++ b/src/lib/preference-management/getPreferenceUpdatesFromRow.ts @@ -235,7 +235,9 @@ export function getPreferenceUpdatesFromRow({ if (mappedValue === undefined && rawValue !== '') { throw new Error( `No preference mapping found for value "${rawValue}" in column ` + - `"${columnName}" (purpose=${purpose}, preference=∅)`, + `"${columnName}" (purpose=${purpose}, preference=∅) ${JSON.stringify( + row, + )}`, ); } if (mappedValue === null) { diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 58947679..3f6d9ba6 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -8,6 +8,7 @@ import { map } from 'bluebird'; import { logger } from '../../logger'; import { extractErrorMessage, getErrorStatus, splitInHalf } from '../helpers'; import { RETRYABLE_BATCH_STATUSES } from '../../constants'; +import type { PreferenceUploadProgress } from '../../commands/consent/upload-preferences/upload'; const PreferenceRecordsQueryResponse = t.intersection([ t.type({ @@ -38,9 +39,10 @@ export async function getPreferencesForIdentifiers( { identifiers, partitionKey, - skipLogging = false, - logInterval = 10000, concurrency = 30, + onProgress, + logInterval = 10000, + skipLogging = false, }: { /** The list of identifiers to look up */ identifiers: { @@ -57,6 +59,8 @@ export async function getPreferencesForIdentifiers( logInterval?: number; /** Concurrency for fetching identifiers */ concurrency?: number; + /** on progress callback */ + onProgress?: (info: PreferenceUploadProgress) => void; }, ): Promise { const results: PreferenceQueryResponseItem[] = []; @@ -66,9 +70,24 @@ export async function getPreferencesForIdentifiers( const t0 = new Date().getTime(); let total = 0; + onProgress?.({ + successDelta: 0, + successTotal: 0, + fileTotal: identifiers.length, // FIXME should be record not identifier count + }); + + /** + * Progress logger respecting `logInterval` + * + * @param delta - delta updated + */ + const maybeLogProgress = (delta: number): void => { + onProgress?.({ + successDelta: delta, + successTotal: total, + fileTotal: identifiers.length, + }); - /** Progress logger respecting `logInterval` */ - const maybeLogProgress = (): void => { if (skipLogging) return; const shouldLog = total % logInterval === 0 || @@ -173,7 +192,7 @@ export async function getPreferencesForIdentifiers( const nodes = await postGroupWithRetries(group); results.push(...nodes); total += group.length; - maybeLogProgress(); + maybeLogProgress(group.length); } catch (err) { const msg = extractErrorMessage(err); @@ -187,7 +206,7 @@ export async function getPreferencesForIdentifiers( ), ); total += 1; - maybeLogProgress(); + maybeLogProgress(1); return; } diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 654fb2c8..9570106c 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -23,6 +23,7 @@ import { checkIfPendingPreferenceUpdatesAreNoOp } from './checkIfPendingPreferen import { checkIfPendingPreferenceUpdatesCauseConflict } from './checkIfPendingPreferenceUpdatesCauseConflict'; import type { ObjByString } from '@transcend-io/type-utils'; import type { PreferenceQueryResponseItem } from '@transcend-io/privacy-types'; +import type { PreferenceUploadProgress } from '../../commands/consent/upload-preferences/upload'; /** * Parse a file into the cache @@ -49,6 +50,7 @@ export async function parsePreferenceManagementCsvWithCache( downloadIdentifierConcurrency, identifierDownloadLogInterval, columnsToIgnore, + onProgress, }: { /** File to parse */ file: string; @@ -76,6 +78,8 @@ export async function parsePreferenceManagementCsvWithCache( identifierDownloadLogInterval: number; /** Concurrency for downloading identifiers */ downloadIdentifierConcurrency: number; + /** on progress callback */ + onProgress?: (info: PreferenceUploadProgress) => void; }, schemaState: PersistedState, ): Promise<{ @@ -129,6 +133,7 @@ export async function parsePreferenceManagementCsvWithCache( logInterval: identifierDownloadLogInterval, partitionKey, concurrency: downloadIdentifierConcurrency, + onProgress, }); // Create a map of all unique identifiers to consent records diff --git a/src/types/parquetjs-lite.d.ts b/src/types/parquetjs-lite.d.ts new file mode 100644 index 00000000..6d8244b6 --- /dev/null +++ b/src/types/parquetjs-lite.d.ts @@ -0,0 +1,29 @@ +declare module 'parquetjs-lite' { + export interface ParquetCursor { + /** Returns the next row object or null when exhausted */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + next(): Promise; + } + + /** + * Reader for Parquet files + */ + export class ParquetReader { + /** Open a Parquet file from disk */ + static openFile(filePath: string): Promise; + + /** Acquire a streaming cursor */ + getCursor(): Promise; + + /** Close underlying resources */ + close(): Promise; + + /** Schema metadata (parquetjs-lite keeps fields on `schema.schema`) */ + schema: + | { + /** Schema */ + schema?: Record; + } + | undefined; + } +} diff --git a/test.sh b/test.sh new file mode 100644 index 00000000..539c45e5 --- /dev/null +++ b/test.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# count_csv_rows.sh — count lines in CSV files (works on macOS Bash 3.2) +# Usage: +# bash count_csv_rows.sh [-r] [-H] [DIR] +# -r Recurse into subdirectories +# -H Exclude one header row per file (if present) +# DIR Directory to scan (default: current directory) + +set -euo pipefail + +RECURSE=0 +EXCL_HEADER=0 +while getopts ":rHh" opt; do + case "$opt" in + r) RECURSE=1 ;; + H) EXCL_HEADER=1 ;; + h) + echo "Usage: bash count_csv_rows.sh [-r] [-H] [DIR]" + exit 0 + ;; + \?) echo "Unknown option: -$OPTARG" >&2; exit 2 ;; + esac +done +shift $((OPTIND - 1)) + +DIR="${1:-.}" +LC_ALL=C +TOTAL=0 + +count_one() { + local f="$1" + local n + if [[ "$f" == *.gz ]]; then + n=$(gzip -cd -- "$f" | awk 'END{print NR}') + else + n=$(awk 'END{print NR}' "$f") + fi + if [[ "$EXCL_HEADER" -eq 1 && "$n" -gt 0 ]]; then + n=$((n-1)) + fi + printf "%12d %s\n" "$n" "$f" + TOTAL=$((TOTAL + n)) +} + +printf "%12s %s\n" "ROWS" "FILE" +printf "%12s %s\n" "------------" "----" + +if [[ "$RECURSE" -eq 1 ]]; then + # Use process substitution to avoid a subshell, so TOTAL updates persist in Bash 3.2 + count=0 + while IFS= read -r -d '' f; do + [[ -f "$f" ]] || continue + count_one "$f" + count=$((count+1)) + done < <(find "$DIR" -type f \( -iname '*.csv' -o -iname '*.csv.gz' \) -print0) + + if [[ "$count" -eq 0 ]]; then + echo "No CSV files found in: $DIR" + exit 0 + fi +else + found=0 + for f in "$DIR"/*.csv "$DIR"/*.CSV "$DIR"/*.csv.gz "$DIR"/*.CSV.GZ; do + [[ -f "$f" ]] || continue + found=1 + count_one "$f" + done + if [[ "$found" -eq 0 ]]; then + echo "No CSV files found in: $DIR" + exit 0 + fi +fi + +printf "%12s %s\n" "------------" "----" +printf "%12d %s\n" "$TOTAL" "TOTAL" diff --git a/test_parquet.sh b/test_parquet.sh new file mode 100644 index 00000000..1c1226b9 --- /dev/null +++ b/test_parquet.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# count_parquet_rows.sh — count rows in Parquet files (macOS Bash 3.2 compatible) +# Usage: +# bash count_parquet_rows.sh [-r] [-T duckdb|parquet-tools] [DIR] +# Options: +# -r Recurse into subdirectories +# -T Tool to use (default: auto-detect). Choices: duckdb | parquet-tools +# DIR Directory to scan (default: current directory) +# +# Dependencies (either works): +# - duckdb CLI → brew install duckdb +# - parquet-tools → brew install parquet-tools (Apache Parquet tools) + +set -euo pipefail + +RECURSE=0 +TOOL="auto" +while getopts ":rT:h" opt; do + case "$opt" in + r) RECURSE=1 ;; + T) TOOL="$OPTARG" ;; + h) + grep -E "^# " "$0" | sed 's/^# //' + exit 0 + ;; + \?) echo "Unknown option: -$OPTARG" >&2; exit 2 ;; + esac +done +shift $((OPTIND - 1)) + +DIR="${1:-.}" +LC_ALL=C +TOTAL=0 + +# Pick a tool if auto +if [[ "$TOOL" == "auto" ]]; then + if command -v duckdb >/dev/null 2>&1; then + TOOL="duckdb" + elif command -v parquet-tools >/dev/null 2>&1; then + TOOL="parquet-tools" + else + echo "No supported tool found." + echo "Install one of:" + echo " brew install duckdb" + echo " brew install parquet-tools" + exit 1 + fi +fi + +count_one() { + local f="$1" + local n + case "$TOOL" in + duckdb) + # Escape single quotes for SQL literal + local fp=${f//\'/\'\'} + # -csv gives two lines; we take the last one + n=$(duckdb -csv -c "select count(*) from read_parquet('$fp');" 2>/dev/null | tail -n 1) + ;; + parquet-tools) + # Handles outputs like "row count: N" or just "N" + n=$(parquet-tools rowcount "$f" 2>/dev/null | awk 'NF{print $NF}' | tail -n 1) + ;; + *) + echo "Unknown tool: $TOOL" >&2; exit 3 + ;; + esac + + # Validate numeric + if ! printf '%s' "$n" | grep -Eq '^[0-9]+$'; then + echo "Failed to count rows for: $f" >&2 + return + fi + + printf "%12d %s\n" "$n" "$f" + TOTAL=$((TOTAL + n)) +} + +printf "%12s %s\n" "ROWS" "FILE" +printf "%12s %s\n" "------------" "----" + +if [[ "$RECURSE" -eq 1 ]]; then + found=0 + # Use -print0 to handle spaces/newlines in filenames + while IFS= read -r -d '' f; do + [[ -f "$f" ]] || continue + found=1 + count_one "$f" + done < <(find "$DIR" -type f \( -iname '*.parquet' \) -print0) + + if [[ "$found" -eq 0 ]]; then + echo "No Parquet files found in: $DIR" + exit 0 + fi +else + found=0 + for f in "$DIR"/*.parquet "$DIR"/*.PARQUET; do + [[ -f "$f" ]] || continue + found=1 + count_one "$f" + done + if [[ "$found" -eq 0 ]]; then + echo "No Parquet files found in: $DIR" + exit 0 + fi +fi + +printf "%12s %s\n" "------------" "----" +printf "%12d %s\n" "$TOTAL" "TOTAL" From 1171cd8790f6c1638507fa2dcc449265cfd1adf8 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 2 Sep 2025 21:00:40 -0700 Subject: [PATCH 54/72] fuxem --- src/lib/graphql/gqls/RequestDataSilo.ts | 2 ++ src/lib/requests/skipRequestDataSilos.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib/graphql/gqls/RequestDataSilo.ts b/src/lib/graphql/gqls/RequestDataSilo.ts index 15e15c4d..c3b323ee 100644 --- a/src/lib/graphql/gqls/RequestDataSilo.ts +++ b/src/lib/graphql/gqls/RequestDataSilo.ts @@ -20,6 +20,8 @@ export const REQUEST_DATA_SILOS = gql` ) { nodes { id + # FIXME remove + status } totalCount } diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index cdd56209..334b0a19 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -22,7 +22,7 @@ export async function skipRequestDataSilos({ dataSiloId, auth, concurrency = 50, - maxUploadPerChunk = 50000, + maxUploadPerChunk = 200000, // FIXME status = 'SKIPPED', transcendUrl = DEFAULT_TRANSCEND_API, requestStatuses = [RequestStatus.Compiling, RequestStatus.Secondary], @@ -86,6 +86,15 @@ export async function skipRequestDataSilos({ requestDataSilos, // eslint-disable-next-line no-loop-func async (requestDataSilo) => { + // FIXME + if ( + requestDataSilo.status === 'SKIPPED' || + requestDataSilo.status === 'RESOLVED' + ) { + total += 0.5; + progressBar.update(total); + return; + } try { await makeGraphQLRequest<{ /** Whether we successfully uploaded the results */ From bc568ebb20cc0e8f1980e49a235f0d5e72b241ca Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Fri, 17 Oct 2025 11:05:34 -0700 Subject: [PATCH 55/72] fixes tsc --- pnpm-lock.yaml | 673 +---------------------- src/lib/requests/skipRequestDataSilos.ts | 18 +- 2 files changed, 15 insertions(+), 676 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7f12a1d..191f6225 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,9 +53,6 @@ importers: csv-parse: specifier: ^5.6.0 version: 5.6.0 - duckdb: - specifier: ^1.3.2 - version: 1.3.2(bluebird@3.7.2)(encoding@0.1.13) fast-csv: specifier: ^4.3.6 version: 4.3.6 @@ -493,9 +490,6 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - '@gar/promisify@1.1.3': - resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: @@ -527,10 +521,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} @@ -544,11 +534,6 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - '@mapbox/node-pre-gyp@2.0.0': - resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} - engines: {node: '>=18'} - hasBin: true - '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} @@ -568,15 +553,6 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - '@npmcli/fs@2.1.2': - resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - '@npmcli/move-file@2.0.1': - resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This functionality has been moved to @npmcli/fs - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -706,17 +682,8 @@ packages: '@textlint/markdown-to-ast@12.6.1': resolution: {integrity: sha512-T0HO+VrU9VbLRiEx/kH4+gwGMHNMIGkp0Pok+p0I33saOOLyhfGvwOKQgvt2qkxzQEV2L5MtGB8EnW4r5d3CqQ==} -<<<<<<< HEAD - '@tootallnate/once@2.0.0': - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - - '@transcend-io/airgap.js-types@12.12.2': - resolution: {integrity: sha512-bvJGlcmd+vY0iatpBTHpGE1Qg4L004v2xSYd2aBGZ/rHPH21GHdtskEGbX5pzaJpUem8ZnI4B3d7aQLbdmJTUQ==} -======= '@transcend-io/airgap.js-types@12.14.1': resolution: {integrity: sha512-Y+//ztBNdsJoIhy+W+Xvg1LuCOaGSRfQH3gXm1uG3fqhVnVufyIva9csixo9ZBAAoV0EwW/iO72b3B24PZ7HxA==} ->>>>>>> main '@transcend-io/handlebars-utils@1.1.0': resolution: {integrity: sha512-mDbKm9JObd9mioNlEYGa2zfTBqli1KAAM+iRHSqi6s2l7MAYENxjlfvXN5uC97T9/90vfWlxNbHjLpUuQVURaA==} @@ -1037,13 +1004,6 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - - abbrev@3.0.1: - resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} - engines: {node: ^18.17.0 || >=20.5.0} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1054,22 +1014,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1099,18 +1043,10 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - aproba@2.1.0: - resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} - are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} - are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1224,10 +1160,6 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacache@16.1.3: - resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -1299,18 +1231,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1344,10 +1264,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} @@ -1380,9 +1296,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} @@ -1486,9 +1399,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - depcheck@1.4.7: resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} engines: {node: '>=10'} @@ -1501,10 +1411,6 @@ packages: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} engines: {node: '>=0.10.0'} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -1540,9 +1446,6 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - duckdb@1.3.2: - resolution: {integrity: sha512-a2Y0kz7HOR36RMlxdCVQ6fnNZaaXjvdVFzi03T6bRLb6trvW8OuHDNBCHZ4XanRCeT0J7w7FvpuAVbgz0r5CYA==} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1579,17 +1482,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - envalid@8.1.0: resolution: {integrity: sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow==} engines: {node: '>=18'} - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1771,9 +1667,6 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - exponential-backoff@3.1.2: - resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1883,10 +1776,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1908,11 +1797,6 @@ packages: fuzzysearch@1.0.3: resolution: {integrity: sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==} - gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1956,11 +1840,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported - global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} @@ -2039,9 +1918,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - has@1.0.4: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} @@ -2063,36 +1939,16 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} -<<<<<<< HEAD - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} -======= iconv-lite@0.7.0: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} ->>>>>>> main - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -2110,13 +1966,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -2154,10 +2003,6 @@ packages: peerDependencies: fp-ts: ^2.5.0 - ip-address@10.0.1: - resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} - engines: {node: '>= 12'} - is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -2232,14 +2077,9 @@ packages: is-hexadecimal@1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} -<<<<<<< HEAD - is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} -======= is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} ->>>>>>> main is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} @@ -2490,10 +2330,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -2501,10 +2337,6 @@ packages: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} - make-fetch-happen@10.2.1: - resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - markdown-table@2.0.0: resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} @@ -2607,10 +2439,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - minimatch@7.4.6: resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} engines: {node: '>=10'} @@ -2622,56 +2450,15 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - - minipass-fetch@2.1.2: - resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} - mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -2717,10 +2504,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -2733,9 +2516,6 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2745,30 +2525,10 @@ packages: encoding: optional: true - node-gyp@9.4.1: - resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} - engines: {node: ^12.13 || ^14.13 || >=16} - hasBin: true - - nopt@6.0.0: - resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - hasBin: true - - nopt@8.1.0: - resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} - npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2824,10 +2584,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2970,18 +2726,6 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - publint@0.3.12: resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} engines: {node: '>=18'} @@ -3081,10 +2825,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3165,9 +2905,6 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3223,21 +2960,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socks-proxy-agent@7.0.0: - resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} - engines: {node: '>= 10'} - - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3270,10 +2995,6 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - ssri@9.0.1: - resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -3353,14 +3074,6 @@ packages: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} - engines: {node: '>=18'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -3542,14 +3255,6 @@ packages: unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} - unique-filename@2.0.1: - resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - unique-slug@3.0.0: - resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} @@ -3709,9 +3414,6 @@ packages: engines: {node: '>=8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3742,13 +3444,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -3995,8 +3690,6 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@gar/promisify@1.1.3': {} - '@graphql-typed-document-node/core@3.2.0(graphql@16.11.0)': dependencies: graphql: 16.11.0 @@ -4029,10 +3722,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.4 @@ -4047,19 +3736,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 - '@mapbox/node-pre-gyp@2.0.0(encoding@0.1.13)': - dependencies: - consola: 3.4.2 - detect-libc: 2.0.4 - https-proxy-agent: 7.0.6 - node-fetch: 2.7.0(encoding@0.1.13) - nopt: 8.1.0 - semver: 7.7.2 - tar: 7.4.3 - transitivePeerDependencies: - - encoding - - supports-color - '@napi-rs/wasm-runtime@0.2.11': dependencies: '@emnapi/core': 1.4.4 @@ -4081,16 +3757,6 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@npmcli/fs@2.1.2': - dependencies: - '@gar/promisify': 1.1.3 - semver: 7.7.2 - - '@npmcli/move-file@2.0.1': - dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - '@pkgjs/parseargs@0.11.0': optional: true @@ -4184,13 +3850,7 @@ snapshots: transitivePeerDependencies: - supports-color -<<<<<<< HEAD - '@tootallnate/once@2.0.0': {} - - '@transcend-io/airgap.js-types@12.12.2': -======= '@transcend-io/airgap.js-types@12.14.1': ->>>>>>> main dependencies: '@transcend-io/type-utils': 1.8.4 fp-ts: 2.16.10 @@ -4555,33 +4215,12 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 - abbrev@1.1.1: {} - - abbrev@3.0.1: {} - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - agent-base@7.1.4: {} - - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 - - aggregate-error@3.1.0: - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4609,15 +4248,8 @@ snapshots: any-promise@1.3.0: {} - aproba@2.1.0: {} - are-docs-informative@0.0.2: {} - are-we-there-yet@3.0.1: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -4739,29 +4371,6 @@ snapshots: cac@6.7.14: {} - cacache@16.1.3(bluebird@3.7.2): - dependencies: - '@npmcli/fs': 2.1.2 - '@npmcli/move-file': 2.0.1 - chownr: 2.0.0 - fs-minipass: 2.1.0 - glob: 8.1.0 - infer-owner: 1.0.4 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - mkdirp: 1.0.4 - p-map: 4.0.0 - promise-inflight: 1.0.1(bluebird@3.7.2) - rimraf: 3.0.2 - ssri: 9.0.1 - tar: 6.2.1 - unique-filename: 2.0.1 - transitivePeerDependencies: - - bluebird - cacheable-lookup@5.0.4: {} cacheable-request@7.0.4: @@ -4852,12 +4461,6 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@2.0.0: {} - - chownr@3.0.0: {} - - clean-stack@2.2.0: {} - cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -4888,8 +4491,6 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} - colors@1.4.0: {} combined-stream@1.0.8: @@ -4910,8 +4511,6 @@ snapshots: consola@3.4.2: {} - console-control-strings@1.1.0: {} - constant-case@3.0.4: dependencies: no-case: 3.0.4 @@ -5036,8 +4635,6 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - depcheck@1.4.7: dependencies: '@babel/parser': 7.28.0 @@ -5070,8 +4667,6 @@ snapshots: detect-file@1.0.0: {} - detect-libc@2.0.4: {} - detect-node@2.1.0: {} dir-glob@3.0.1: @@ -5120,16 +4715,6 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 - duckdb@1.3.2(bluebird@3.7.2)(encoding@0.1.13): - dependencies: - '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13) - node-addon-api: 7.1.1 - node-gyp: 9.4.1(bluebird@3.7.2) - transitivePeerDependencies: - - bluebird - - encoding - - supports-color - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5163,14 +4748,10 @@ snapshots: entities@4.5.0: {} - env-paths@2.2.1: {} - envalid@8.1.0: dependencies: tslib: 2.8.1 - err-code@2.0.3: {} - error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -5473,8 +5054,6 @@ snapshots: expect-type@1.2.2: {} - exponential-backoff@3.1.2: {} - extend@3.0.2: {} extract-files@9.0.0: {} @@ -5582,10 +5161,6 @@ snapshots: fs-constants@1.0.0: {} - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -5606,17 +5181,6 @@ snapshots: fuzzysearch@1.0.3: {} - gauge@4.0.4: - dependencies: - aproba: 2.1.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -5682,14 +5246,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - global-agent@3.0.0: dependencies: boolean: 3.2.0 @@ -5792,8 +5348,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - has-unicode@2.0.1: {} - has@1.0.4: {} hasown@2.0.2: @@ -5818,49 +5372,19 @@ snapshots: http-cache-semantics@4.2.0: {} - http-proxy-agent@5.0.0: - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 -<<<<<<< HEAD - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - - iconv-lite@0.4.24: -======= - iconv-lite@0.7.0: ->>>>>>> main + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true - iconv-lite@0.6.3: + iconv-lite@0.7.0: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.2.1: {} @@ -5873,10 +5397,6 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@4.0.0: {} - - infer-owner@1.0.4: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -5932,8 +5452,6 @@ snapshots: dependencies: fp-ts: 2.16.10 - ip-address@10.0.1: {} - is-alphabetical@1.0.4: {} is-alphanumerical@1.0.4: @@ -6012,11 +5530,7 @@ snapshots: is-hexadecimal@1.0.4: {} -<<<<<<< HEAD - is-lambda@1.0.1: {} -======= is-interactive@1.0.0: {} ->>>>>>> main is-map@2.0.3: {} @@ -6225,8 +5739,6 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@7.18.3: {} - magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 @@ -6235,28 +5747,6 @@ snapshots: dependencies: pify: 3.0.0 - make-fetch-happen@10.2.1(bluebird@3.7.2): - dependencies: - agentkeepalive: 4.6.0 - cacache: 16.1.3(bluebird@3.7.2) - http-cache-semantics: 4.2.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-fetch: 2.1.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 0.6.4 - promise-retry: 2.0.1 - socks-proxy-agent: 7.0.0 - ssri: 9.0.1 - transitivePeerDependencies: - - bluebird - - supports-color - markdown-table@2.0.0: dependencies: repeat-string: 1.6.1 @@ -6413,10 +5903,6 @@ snapshots: dependencies: brace-expansion: 1.1.12 - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.2 - minimatch@7.4.6: dependencies: brace-expansion: 2.0.2 @@ -6427,51 +5913,10 @@ snapshots: minimist@1.2.8: {} - minipass-collect@1.0.2: - dependencies: - minipass: 3.3.6 - - minipass-fetch@2.1.2: - dependencies: - minipass: 3.3.6 - minipass-sized: 1.0.3 - minizlib: 2.1.2 - optionalDependencies: - encoding: 0.1.13 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass-sized@1.0.3: - dependencies: - minipass: 3.3.6 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - minizlib@3.0.2: - dependencies: - minipass: 7.1.2 - mkdirp@1.0.4: {} - mkdirp@3.0.1: {} - mlly@1.7.4: dependencies: acorn: 8.15.0 @@ -6513,8 +5958,6 @@ snapshots: natural-compare@1.4.0: {} - negotiator@0.6.4: {} - neo-async@2.6.2: {} newtype-ts@0.3.5(fp-ts@2.16.10)(monocle-ts@2.3.13(fp-ts@2.16.10)): @@ -6527,48 +5970,14 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 - node-addon-api@7.1.1: {} - node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 - node-gyp@9.4.1(bluebird@3.7.2): - dependencies: - env-paths: 2.2.1 - exponential-backoff: 3.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - make-fetch-happen: 10.2.1(bluebird@3.7.2) - nopt: 6.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - which: 2.0.2 - transitivePeerDependencies: - - bluebird - - supports-color - - nopt@6.0.0: - dependencies: - abbrev: 1.1.1 - - nopt@8.1.0: - dependencies: - abbrev: 3.0.1 - normalize-url@6.1.0: {} - npmlog@6.0.2: - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -6643,10 +6052,6 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - package-json-from-dist@1.0.1: {} package-manager-detector@1.3.0: {} @@ -6760,15 +6165,6 @@ snapshots: process-nextick-args@2.0.1: {} - promise-inflight@1.0.1(bluebird@3.7.2): - optionalDependencies: - bluebird: 3.7.2 - - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - publint@0.3.12: dependencies: '@publint/pack': 0.1.2 @@ -6895,8 +6291,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - retry@0.12.0: {} - reusify@1.1.0: {} rimraf@3.0.2: @@ -7001,8 +6395,6 @@ snapshots: dependencies: type-fest: 0.13.1 - set-blocking@2.0.0: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -7073,26 +6465,11 @@ snapshots: slash@3.0.0: {} - smart-buffer@4.2.0: {} - snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.8.1 - socks-proxy-agent@7.0.0: - dependencies: - agent-base: 6.0.2 - debug: 4.4.1 - socks: 2.8.7 - transitivePeerDependencies: - - supports-color - - socks@2.8.7: - dependencies: - ip-address: 10.0.1 - smart-buffer: 4.2.0 - source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -7116,10 +6493,6 @@ snapshots: sprintf-js@1.1.3: {} - ssri@9.0.1: - dependencies: - minipass: 3.3.6 - stable-hash@0.0.5: {} stackback@0.0.2: {} @@ -7218,24 +6591,6 @@ snapshots: to-buffer: 1.2.1 xtend: 4.0.2 - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - tar@7.4.3: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 - yallist: 5.0.0 - text-table@0.2.0: {} thenify-all@1.6.0: @@ -7437,14 +6792,6 @@ snapshots: trough: 1.0.5 vfile: 4.2.1 - unique-filename@2.0.1: - dependencies: - unique-slug: 3.0.0 - - unique-slug@3.0.0: - dependencies: - imurmurhash: 0.1.4 - unist-util-is@4.1.0: {} unist-util-stringify-position@2.0.3: @@ -7667,10 +7014,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -7699,10 +7042,6 @@ snapshots: y18n@5.0.8: {} - yallist@4.0.0: {} - - yallist@5.0.0: {} - yaml@1.10.2: {} yargs-parser@20.2.9: {} diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index 334b0a19..da77e740 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -86,15 +86,15 @@ export async function skipRequestDataSilos({ requestDataSilos, // eslint-disable-next-line no-loop-func async (requestDataSilo) => { - // FIXME - if ( - requestDataSilo.status === 'SKIPPED' || - requestDataSilo.status === 'RESOLVED' - ) { - total += 0.5; - progressBar.update(total); - return; - } + // // FIXME + // if ( + // requestDataSilo.status === 'SKIPPED' || + // requestDataSilo.status === 'RESOLVED' + // ) { + // total += 0.5; + // progressBar.update(total); + // return; + // } try { await makeGraphQLRequest<{ /** Whether we successfully uploaded the results */ From decb452049c211b2bed0a9a6f81eee29d695f54f Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 20 Oct 2025 12:29:25 -0700 Subject: [PATCH 56/72] Rev --- test_parquet.sh | 109 ------------------------------------------------ 1 file changed, 109 deletions(-) delete mode 100644 test_parquet.sh diff --git a/test_parquet.sh b/test_parquet.sh deleted file mode 100644 index 1c1226b9..00000000 --- a/test_parquet.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env bash -# count_parquet_rows.sh — count rows in Parquet files (macOS Bash 3.2 compatible) -# Usage: -# bash count_parquet_rows.sh [-r] [-T duckdb|parquet-tools] [DIR] -# Options: -# -r Recurse into subdirectories -# -T Tool to use (default: auto-detect). Choices: duckdb | parquet-tools -# DIR Directory to scan (default: current directory) -# -# Dependencies (either works): -# - duckdb CLI → brew install duckdb -# - parquet-tools → brew install parquet-tools (Apache Parquet tools) - -set -euo pipefail - -RECURSE=0 -TOOL="auto" -while getopts ":rT:h" opt; do - case "$opt" in - r) RECURSE=1 ;; - T) TOOL="$OPTARG" ;; - h) - grep -E "^# " "$0" | sed 's/^# //' - exit 0 - ;; - \?) echo "Unknown option: -$OPTARG" >&2; exit 2 ;; - esac -done -shift $((OPTIND - 1)) - -DIR="${1:-.}" -LC_ALL=C -TOTAL=0 - -# Pick a tool if auto -if [[ "$TOOL" == "auto" ]]; then - if command -v duckdb >/dev/null 2>&1; then - TOOL="duckdb" - elif command -v parquet-tools >/dev/null 2>&1; then - TOOL="parquet-tools" - else - echo "No supported tool found." - echo "Install one of:" - echo " brew install duckdb" - echo " brew install parquet-tools" - exit 1 - fi -fi - -count_one() { - local f="$1" - local n - case "$TOOL" in - duckdb) - # Escape single quotes for SQL literal - local fp=${f//\'/\'\'} - # -csv gives two lines; we take the last one - n=$(duckdb -csv -c "select count(*) from read_parquet('$fp');" 2>/dev/null | tail -n 1) - ;; - parquet-tools) - # Handles outputs like "row count: N" or just "N" - n=$(parquet-tools rowcount "$f" 2>/dev/null | awk 'NF{print $NF}' | tail -n 1) - ;; - *) - echo "Unknown tool: $TOOL" >&2; exit 3 - ;; - esac - - # Validate numeric - if ! printf '%s' "$n" | grep -Eq '^[0-9]+$'; then - echo "Failed to count rows for: $f" >&2 - return - fi - - printf "%12d %s\n" "$n" "$f" - TOTAL=$((TOTAL + n)) -} - -printf "%12s %s\n" "ROWS" "FILE" -printf "%12s %s\n" "------------" "----" - -if [[ "$RECURSE" -eq 1 ]]; then - found=0 - # Use -print0 to handle spaces/newlines in filenames - while IFS= read -r -d '' f; do - [[ -f "$f" ]] || continue - found=1 - count_one "$f" - done < <(find "$DIR" -type f \( -iname '*.parquet' \) -print0) - - if [[ "$found" -eq 0 ]]; then - echo "No Parquet files found in: $DIR" - exit 0 - fi -else - found=0 - for f in "$DIR"/*.parquet "$DIR"/*.PARQUET; do - [[ -f "$f" ]] || continue - found=1 - count_one "$f" - done - if [[ "$found" -eq 0 ]]; then - echo "No Parquet files found in: $DIR" - exit 0 - fi -fi - -printf "%12s %s\n" "------------" "----" -printf "%12d %s\n" "$TOTAL" "TOTAL" From 25702def95a4db14ddad9b946a7265837e671360 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 20 Oct 2025 12:29:31 -0700 Subject: [PATCH 57/72] Rev --- test.sh | 75 --------------------------------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 test.sh diff --git a/test.sh b/test.sh deleted file mode 100644 index 539c45e5..00000000 --- a/test.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -# count_csv_rows.sh — count lines in CSV files (works on macOS Bash 3.2) -# Usage: -# bash count_csv_rows.sh [-r] [-H] [DIR] -# -r Recurse into subdirectories -# -H Exclude one header row per file (if present) -# DIR Directory to scan (default: current directory) - -set -euo pipefail - -RECURSE=0 -EXCL_HEADER=0 -while getopts ":rHh" opt; do - case "$opt" in - r) RECURSE=1 ;; - H) EXCL_HEADER=1 ;; - h) - echo "Usage: bash count_csv_rows.sh [-r] [-H] [DIR]" - exit 0 - ;; - \?) echo "Unknown option: -$OPTARG" >&2; exit 2 ;; - esac -done -shift $((OPTIND - 1)) - -DIR="${1:-.}" -LC_ALL=C -TOTAL=0 - -count_one() { - local f="$1" - local n - if [[ "$f" == *.gz ]]; then - n=$(gzip -cd -- "$f" | awk 'END{print NR}') - else - n=$(awk 'END{print NR}' "$f") - fi - if [[ "$EXCL_HEADER" -eq 1 && "$n" -gt 0 ]]; then - n=$((n-1)) - fi - printf "%12d %s\n" "$n" "$f" - TOTAL=$((TOTAL + n)) -} - -printf "%12s %s\n" "ROWS" "FILE" -printf "%12s %s\n" "------------" "----" - -if [[ "$RECURSE" -eq 1 ]]; then - # Use process substitution to avoid a subshell, so TOTAL updates persist in Bash 3.2 - count=0 - while IFS= read -r -d '' f; do - [[ -f "$f" ]] || continue - count_one "$f" - count=$((count+1)) - done < <(find "$DIR" -type f \( -iname '*.csv' -o -iname '*.csv.gz' \) -print0) - - if [[ "$count" -eq 0 ]]; then - echo "No CSV files found in: $DIR" - exit 0 - fi -else - found=0 - for f in "$DIR"/*.csv "$DIR"/*.CSV "$DIR"/*.csv.gz "$DIR"/*.CSV.GZ; do - [[ -f "$f" ]] || continue - found=1 - count_one "$f" - done - if [[ "$found" -eq 0 ]]; then - echo "No CSV files found in: $DIR" - exit 0 - fi -fi - -printf "%12s %s\n" "------------" "----" -printf "%12d %s\n" "$TOTAL" "TOTAL" From 9f8bdb61ab81285ec39ed2e1185082f4d80f8b3e Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 20 Oct 2025 12:29:49 -0700 Subject: [PATCH 58/72] rev --- src/lib/requests/skipRequestDataSilos.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index da77e740..cdd56209 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -22,7 +22,7 @@ export async function skipRequestDataSilos({ dataSiloId, auth, concurrency = 50, - maxUploadPerChunk = 200000, // FIXME + maxUploadPerChunk = 50000, status = 'SKIPPED', transcendUrl = DEFAULT_TRANSCEND_API, requestStatuses = [RequestStatus.Compiling, RequestStatus.Secondary], @@ -86,15 +86,6 @@ export async function skipRequestDataSilos({ requestDataSilos, // eslint-disable-next-line no-loop-func async (requestDataSilo) => { - // // FIXME - // if ( - // requestDataSilo.status === 'SKIPPED' || - // requestDataSilo.status === 'RESOLVED' - // ) { - // total += 0.5; - // progressBar.update(total); - // return; - // } try { await makeGraphQLRequest<{ /** Whether we successfully uploaded the results */ From 7d12a5b50048c6e9b0ecd09d10936ebb68bb2ddd Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Thu, 30 Oct 2025 11:25:54 -0700 Subject: [PATCH 59/72] ud --- .../consent/upload-preferences/upload/batchUploader.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/consent/upload-preferences/upload/batchUploader.ts b/src/commands/consent/upload-preferences/upload/batchUploader.ts index fc8985e2..249fbd18 100644 --- a/src/commands/consent/upload-preferences/upload/batchUploader.ts +++ b/src/commands/consent/upload-preferences/upload/batchUploader.ts @@ -76,7 +76,11 @@ export async function uploadChunkWithSplit( // 2) For retryable statuses, attempt in-place retries without splitting. const isSoftRateLimit = - status === 400 && /slow down|please try again shortly/i.test(msg); + // FIXME + status === 400 && + /slow down|please try again shortly|Throughput exceeds the current/i.test( + msg, + ); if (deps.isRetryableStatus(status) || isSoftRateLimit) { try { From 040285890e6e8cbfa5963c2514e44e7e55aa40f5 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 15 Dec 2025 19:57:39 -0800 Subject: [PATCH 60/72] Ud --- .../consent/pull-consent-metrics/impl.ts | 3 +- .../consent/update-consent-manager/impl.ts | 3 +- .../interactivePreferenceUploaderFromPlan.ts | 4 +- src/commands/inventory/pull/impl.ts | 3 +- src/commands/inventory/push/impl.ts | 3 +- .../request/cron/pull-profiles/impl.ts | 3 +- src/find-exact.ts | 340 +++++++ src/lib/ai/TranscendPromptManager.ts | 3 +- .../api-keys/generateCrossAccountApiKeys.ts | 3 +- .../consent-manager/buildXdiSyncEndpoint.ts | 3 +- .../updateConsentManagerVersionToLatest.ts | 3 +- src/lib/consent-manager/uploadConsents.ts | 3 +- .../cron/markRequestDataSiloIdsCompleted.ts | 3 +- ...ChunkedCustomSiloOutstandingIdentifiers.ts | 3 +- .../pullCustomSiloOutstandingIdentifiers.ts | 3 +- src/lib/cron/pushCronIdentifiersFromCsv.ts | 3 +- src/lib/data-inventory/pullAllDatapoints.ts | 3 +- .../graphql/createPreferenceAccessTokens.ts | 3 +- src/lib/graphql/createSombraGotInstance.ts | 2 +- src/lib/graphql/fetchDataSubjects.ts | 3 +- src/lib/graphql/fetchIdentifiers.ts | 3 +- src/lib/graphql/syncActionItemCollections.ts | 3 +- src/lib/graphql/syncActionItems.ts | 3 +- src/lib/graphql/syncAgentFiles.ts | 3 +- src/lib/graphql/syncAgentFunctions.ts | 3 +- src/lib/graphql/syncAgents.ts | 3 +- src/lib/graphql/syncAttribute.ts | 3 +- src/lib/graphql/syncBusinessEntities.ts | 3 +- src/lib/graphql/syncCodePackages.ts | 3 +- .../graphql/syncConfigurationToTranscend.ts | 3 +- src/lib/graphql/syncConsentManager.ts | 3 +- src/lib/graphql/syncCookies.ts | 3 +- src/lib/graphql/syncDataCategories.ts | 3 +- src/lib/graphql/syncDataFlows.ts | 3 +- src/lib/graphql/syncDataSilos.ts | 3 +- src/lib/graphql/syncIntlMessages.ts | 3 +- src/lib/graphql/syncPartitions.ts | 3 +- src/lib/graphql/syncPolicies.ts | 3 +- src/lib/graphql/syncProcessingActivities.ts | 3 +- src/lib/graphql/syncProcessingPurposes.ts | 3 +- src/lib/graphql/syncPromptGroups.ts | 3 +- src/lib/graphql/syncPromptPartials.ts | 3 +- src/lib/graphql/syncPrompts.ts | 3 +- src/lib/graphql/syncRepositories.ts | 3 +- .../graphql/syncSoftwareDevelopmentKits.ts | 3 +- src/lib/graphql/syncTeams.ts | 3 +- src/lib/graphql/syncVendors.ts | 3 +- src/lib/graphql/uploadSiloDiscoveryResults.ts | 3 +- .../pullManualEnrichmentIdentifiersToCsv.ts | 3 +- .../pushManualEnrichmentIdentifiersFromCsv.ts | 3 +- .../syncOneTrustAssessmentsFromOneTrust.ts | 3 +- .../fetchConsentPreferencesChunked.ts | 3 +- .../getPreferencesForIdentifiers.ts | 6 +- .../parsePreferenceAndPurposeValuesFromCsv.ts | 3 +- .../parsePreferenceIdentifiersFromCsv.ts | 3 +- .../getPreferencesForIdentifiers.test.ts | 2 + .../withPreferenceQueryRetry.ts | 1 + src/lib/requests/approvePrivacyRequests.ts | 3 +- src/lib/requests/bulkRestartRequests.ts | 3 +- src/lib/requests/bulkRetryEnrichers.ts | 3 +- src/lib/requests/cancelPrivacyRequests.ts | 3 +- .../requests/downloadPrivacyRequestFiles.ts | 3 +- .../getFileMetadataForPrivacyRequests.ts | 3 +- src/lib/requests/markSilentPrivacyRequests.ts | 4 +- .../notifyPrivacyRequestsAdditionalTime.ts | 3 +- src/lib/requests/pullPrivacyRequests.ts | 3 +- .../removeUnverifiedRequestIdentifiers.ts | 3 +- src/lib/requests/retryRequestDataSilos.ts | 3 +- src/lib/requests/skipPreflightJobs.ts | 3 +- src/lib/requests/skipRequestDataSilos.ts | 3 +- src/lib/requests/streamPrivacyRequestFiles.ts | 3 +- .../requests/uploadPrivacyRequestsFromCsv.ts | 3 +- src/reconcile-preference-records.ts | 872 ++++++++++++++++++ src/trim-scripts.ts | 231 +++++ 74 files changed, 1587 insertions(+), 70 deletions(-) create mode 100644 src/find-exact.ts create mode 100644 src/reconcile-preference-records.ts create mode 100644 src/trim-scripts.ts diff --git a/src/commands/consent/pull-consent-metrics/impl.ts b/src/commands/consent/pull-consent-metrics/impl.ts index 015a5615..12434d28 100644 --- a/src/commands/consent/pull-consent-metrics/impl.ts +++ b/src/commands/consent/pull-consent-metrics/impl.ts @@ -1,7 +1,8 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; import colors from 'colors'; -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map, mapSeries } = Bluebird; import { join } from 'node:path'; import fs, { existsSync, mkdirSync } from 'node:fs'; import { diff --git a/src/commands/consent/update-consent-manager/impl.ts b/src/commands/consent/update-consent-manager/impl.ts index 705fc629..2508c4aa 100644 --- a/src/commands/consent/update-consent-manager/impl.ts +++ b/src/commands/consent/update-consent-manager/impl.ts @@ -1,7 +1,8 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { ConsentBundleType } from '@transcend-io/privacy-types'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { logger } from '../../../logger'; import { updateConsentManagerVersionToLatest } from '../../../lib/consent-manager'; diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index f01b5234..1a3a9868 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ import colors from 'colors'; -import { map as pMap } from 'bluebird'; +import Bluebird from 'bluebird'; import { chunk, groupBy } from 'lodash-es'; import { logger } from '../../../../logger'; import { buildPendingUpdates } from './transform'; @@ -13,6 +13,8 @@ import type { PreferenceReceiptsInterface } from '../artifacts/receipts'; import type { Got } from 'got'; import type { PreferenceUploadProgress } from './types'; +const { map: pMap } = Bluebird; + /** * Execute the upload using a pre-built InteractiveUploadPlan. * diff --git a/src/commands/inventory/pull/impl.ts b/src/commands/inventory/pull/impl.ts index ea0a560b..c6497348 100644 --- a/src/commands/inventory/pull/impl.ts +++ b/src/commands/inventory/pull/impl.ts @@ -8,7 +8,8 @@ import { import { logger } from '../../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { join } from 'node:path'; import fs from 'node:fs'; import { diff --git a/src/commands/inventory/push/impl.ts b/src/commands/inventory/push/impl.ts index db44ef73..ac9a899b 100644 --- a/src/commands/inventory/push/impl.ts +++ b/src/commands/inventory/push/impl.ts @@ -1,7 +1,8 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { existsSync, lstatSync } from 'node:fs'; import { join } from 'node:path'; import { readTranscendYaml } from '../../../lib/readTranscendYaml'; diff --git a/src/commands/request/cron/pull-profiles/impl.ts b/src/commands/request/cron/pull-profiles/impl.ts index 94bf1d01..ba48a74e 100644 --- a/src/commands/request/cron/pull-profiles/impl.ts +++ b/src/commands/request/cron/pull-profiles/impl.ts @@ -2,7 +2,8 @@ import type { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../../../logger'; import colors from 'colors'; import { uniq, chunk } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { buildTranscendGraphQLClient, fetchRequestFilesForRequest, diff --git a/src/find-exact.ts b/src/find-exact.ts new file mode 100644 index 00000000..8d306a5d --- /dev/null +++ b/src/find-exact.ts @@ -0,0 +1,340 @@ +#!/usr/bin/env node +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; +import fg from 'fast-glob'; + +/** + * + */ +type Options = { + /** */ + root: string; + /** */ + needle: string; + /** */ + exts: Set; + /** */ + includeParquet: boolean; + /** */ + concurrency: number; + /** */ + maxBytes?: number; // optional: stop scanning a file after N bytes +}; + +/** + * + */ +function parseArgs(): Options { + const args = process.argv.slice(2); + + const get = (flag: string) => { + const idx = args.indexOf(flag); + if (idx === -1) return undefined; + return args[idx + 1]; + }; + + const root = get('--root') ?? '.'; + const needle = get('--needle'); + if (!needle) { + console.error('Missing --needle "..."'); + process.exit(2); + } + + const extsRaw = get('--exts') ?? 'csv,json,txt,ndjson,log'; + const includeParquet = !args.includes('--no-parquet'); + const concurrency = Number(get('--concurrency') ?? '16'); + const maxBytesStr = get('--max-bytes'); + const maxBytes = maxBytesStr ? Number(maxBytesStr) : undefined; + + return { + root, + needle, + exts: new Set( + extsRaw + .split(',') + .map((x) => x.trim().replace(/^\./, '').toLowerCase()) + .filter(Boolean), + ), + includeParquet, + concurrency: + Number.isFinite(concurrency) && concurrency > 0 ? concurrency : 16, + maxBytes: maxBytes && maxBytes > 0 ? maxBytes : undefined, + }; +} + +/** + * + * @param filePath + * @param needle + * @param maxBytes + */ +async function fileContainsExactBytes( + filePath: string, + needle: Buffer, + maxBytes?: number, +): Promise { + return await new Promise((resolve, reject) => { + const stream = fs.createReadStream(filePath); + let carry = Buffer.alloc(0); + const n = needle.length; + + let seen = 0; + + stream.on('data', (chunk: Buffer) => { + if (maxBytes) { + const remaining = maxBytes - seen; + if (remaining <= 0) { + stream.destroy(); + resolve(false); + return; + } + if (chunk.length > remaining) { + chunk = chunk.subarray(0, remaining); + } + seen += chunk.length; + } + + const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk; + if (buf.indexOf(needle) !== -1) { + stream.destroy(); + resolve(true); + return; + } + + // keep last n-1 bytes to catch boundary matches + if (n > 1) { + carry = buf.subarray(Math.max(0, buf.length - (n - 1))) as any; + } else { + carry = Buffer.alloc(0); + } + }); + + stream.on('error', reject); + stream.on('close', () => resolve(false)); + stream.on('end', () => resolve(false)); + }); +} + +/** + * + * @param items + * @param limit + * @param worker + */ +async function runWithConcurrency( + items: T[], + limit: number, + worker: (item: T) => Promise, +): Promise { + let i = 0; + const runners = Array.from( + { length: Math.min(limit, items.length) }, + async () => { + while (true) { + const idx = i++; + if (idx >= items.length) return; + await worker(items[idx]); + } + }, + ); + await Promise.all(runners); +} + +/** + * Parquet search strategy: + * - DuckDB can read parquet quickly. + * - We ask DuckDB to scan each parquet file and return 1 row if any string column contains the needle. + * + * Note: exact byte match inside parquet isn’t meaningful (compressed/encoded). We do exact string match: + * col = 'needle' OR contains(col, 'needle') depending on your preference. + * + * Here: we use exact equality on ANY string column. (You can change to LIKE/contains if you want.) + * + * @param duckdbPath + * @param filePath + * @param needle + */ +async function parquetFileHasExactString( + duckdbPath: string, + filePath: string, + needle: string, +): Promise { + // Build a DuckDB query that: + // 1) introspects schema + // 2) checks any VARCHAR column for equality to needle + // + // We do it in a single DuckDB invocation for the file. + const sql = ` +WITH cols AS ( + SELECT column_name + FROM parquet_schema('${filePath.replace(/'/g, "''")}') + WHERE lower(column_type) LIKE '%varchar%' OR lower(column_type) LIKE '%string%' +), +q AS ( + SELECT 1 AS hit + FROM read_parquet('${filePath.replace(/'/g, "''")}') + WHERE ${/* OR-chain across string cols */ ''} + ${ + // If no string cols, make it FALSE + '(SELECT count(*) FROM cols) = 0' + } = FALSE + AND ( + ${'__OR_CHAIN__'} + ) + LIMIT 1 +) +SELECT hit FROM q; +`.trim(); + + // We need to replace __OR_CHAIN__ with OR conditions dynamically. + // DuckDB doesn't easily allow dynamic SQL without a second layer, so we do a 2-step: + // - First: get string column names + // - Second: run the OR query + const cols = await duckdbGetParquetStringColumns(duckdbPath, filePath); + if (cols.length === 0) return false; + + const orChain = cols + .map((c) => `"${c.replace(/"/g, '""')}" = '${needle.replace(/'/g, "''")}'`) + .join(' OR '); + + const finalSql = sql.replace('__OR_CHAIN__', orChain); + + const out = await duckdbQuery(duckdbPath, finalSql); + return out.trim().length > 0; // any output row means hit +} + +/** + * + * @param duckdbPath + * @param filePath + */ +async function duckdbGetParquetStringColumns( + duckdbPath: string, + filePath: string, +): Promise { + const sql = ` +SELECT column_name +FROM parquet_schema('${filePath.replace(/'/g, "''")}') +WHERE lower(column_type) LIKE '%varchar%' OR lower(column_type) LIKE '%string%'; +`.trim(); + const out = await duckdbQuery(duckdbPath, sql); + // output is tab-separated by default + return out + .split('\n') + .map((l) => l.trim()) + .filter(Boolean); +} + +/** + * + * @param duckdbPath + * @param sql + */ +async function duckdbQuery(duckdbPath: string, sql: string): Promise { + return await new Promise((resolve, reject) => { + const child = spawn(duckdbPath, ['-noheader', '-batch', '-cmd', sql], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (d) => (stdout += String(d))); + child.stderr.on('data', (d) => (stderr += String(d))); + + child.on('error', reject); + child.on('close', (code) => { + if (code === 0) resolve(stdout); + else reject(new Error(`duckdb exited ${code}: ${stderr}`)); + }); + }); +} + +/** + * + */ +function findDuckdbBinary(): string | null { + // rely on PATH + return 'duckdb'; +} + +/** + * + */ +async function main() { + const opts = parseArgs(); + const rootAbs = path.resolve(opts.root); + + const patterns = Array.from(opts.exts).map((e) => `**/*.${e}`); + // always include parquet separately + const parquetPattern = '**/*.parquet'; + + const normalFiles = await fg(patterns, { + cwd: rootAbs, + absolute: true, + onlyFiles: true, + followSymbolicLinks: false, + suppressErrors: true, + }); + + const needleBuf = Buffer.from(opts.needle, 'utf8'); + + const hits: string[] = []; + await runWithConcurrency(normalFiles, opts.concurrency, async (file) => { + try { + const ok = await fileContainsExactBytes(file, needleBuf, opts.maxBytes); + if (ok) { + hits.push(file); + process.stdout.write(`${file}\n`); + } + } catch { + // ignore unreadable files + } + }); + + if (opts.includeParquet) { + const duckdbPath = findDuckdbBinary(); + if (!duckdbPath) { + console.error( + 'DuckDB not found in PATH; install duckdb or run with --no-parquet', + ); + process.exit(2); + } + + const parquetFiles = await fg([parquetPattern], { + cwd: rootAbs, + absolute: true, + onlyFiles: true, + followSymbolicLinks: false, + suppressErrors: true, + }); + + await runWithConcurrency( + parquetFiles, + Math.max(2, Math.floor(opts.concurrency / 4)), + async (file) => { + try { + const ok = await parquetFileHasExactString( + duckdbPath, + file, + opts.needle, + ); + if (ok) { + hits.push(file); + process.stdout.write(`${file}\n`); + } + } catch { + // ignore parquet read issues + } + }, + ); + } + + // If you want a summary at end: + // console.error(`Done. Hits: ${hits.length}`); +} + +main().catch((err) => { + console.error(err?.stack ?? String(err)); + process.exit(1); +}); diff --git a/src/lib/ai/TranscendPromptManager.ts b/src/lib/ai/TranscendPromptManager.ts index 6871f576..41178238 100644 --- a/src/lib/ai/TranscendPromptManager.ts +++ b/src/lib/ai/TranscendPromptManager.ts @@ -42,7 +42,8 @@ import { fetchAllLargeLanguageModels, } from '../graphql/fetchLargeLanguageModels'; import { groupBy, keyBy, uniq, chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { PromptThread, fetchAllPromptThreads, diff --git a/src/lib/api-keys/generateCrossAccountApiKeys.ts b/src/lib/api-keys/generateCrossAccountApiKeys.ts index 4de7ba33..f2c9fc90 100644 --- a/src/lib/api-keys/generateCrossAccountApiKeys.ts +++ b/src/lib/api-keys/generateCrossAccountApiKeys.ts @@ -1,4 +1,5 @@ -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { buildTranscendGraphQLClientGeneric, loginUser, diff --git a/src/lib/consent-manager/buildXdiSyncEndpoint.ts b/src/lib/consent-manager/buildXdiSyncEndpoint.ts index 32e490cb..4fa8b87d 100644 --- a/src/lib/consent-manager/buildXdiSyncEndpoint.ts +++ b/src/lib/consent-manager/buildXdiSyncEndpoint.ts @@ -2,7 +2,8 @@ import colors from 'colors'; import { buildTranscendGraphQLClient, fetchConsentManager } from '../graphql'; import { difference } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { StoredApiKey } from '../../codecs'; import { DEFAULT_TRANSCEND_API } from '../../constants'; import { logger } from '../../logger'; diff --git a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts index a103db6e..7d0e12e6 100644 --- a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts +++ b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts @@ -1,5 +1,6 @@ import { ConsentBundleType } from '@transcend-io/privacy-types'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { updateConsentManagerToLatest, buildTranscendGraphQLClient, diff --git a/src/lib/consent-manager/uploadConsents.ts b/src/lib/consent-manager/uploadConsents.ts index 3b1b3b35..58ab1546 100644 --- a/src/lib/consent-manager/uploadConsents.ts +++ b/src/lib/consent-manager/uploadConsents.ts @@ -2,7 +2,8 @@ import { createTranscendConsentGotInstance } from '../graphql'; import colors from 'colors'; import * as t from 'io-ts'; import { DEFAULT_TRANSCEND_CONSENT_API } from '../../constants'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { createConsentToken } from './createConsentToken'; import { logger } from '../../logger'; import cliProgress from 'cli-progress'; diff --git a/src/lib/cron/markRequestDataSiloIdsCompleted.ts b/src/lib/cron/markRequestDataSiloIdsCompleted.ts index 07b46d2d..83927877 100644 --- a/src/lib/cron/markRequestDataSiloIdsCompleted.ts +++ b/src/lib/cron/markRequestDataSiloIdsCompleted.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts index 95697b6c..9cf0bd89 100644 --- a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts @@ -13,7 +13,8 @@ import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; /** * A CSV formatted identifier diff --git a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts index e2866c8f..1f9ea873 100644 --- a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts @@ -12,7 +12,8 @@ import { import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; /** * A CSV formatted identifier diff --git a/src/lib/cron/pushCronIdentifiersFromCsv.ts b/src/lib/cron/pushCronIdentifiersFromCsv.ts index 82027087..86e6a4cc 100644 --- a/src/lib/cron/pushCronIdentifiersFromCsv.ts +++ b/src/lib/cron/pushCronIdentifiersFromCsv.ts @@ -1,4 +1,5 @@ -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map, mapSeries } = Bluebird; import { chunk } from 'lodash-es'; import { createSombraGotInstance } from '../graphql'; import colors from 'colors'; diff --git a/src/lib/data-inventory/pullAllDatapoints.ts b/src/lib/data-inventory/pullAllDatapoints.ts index 302315af..be7d1038 100644 --- a/src/lib/data-inventory/pullAllDatapoints.ts +++ b/src/lib/data-inventory/pullAllDatapoints.ts @@ -17,7 +17,8 @@ import { } from '../graphql'; import { logger } from '../../logger'; import type { DataCategoryInput, ProcessingPurposeInput } from '../../codecs'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; export interface DataSiloCsvPreview { /** ID of dataSilo */ diff --git a/src/lib/graphql/createPreferenceAccessTokens.ts b/src/lib/graphql/createPreferenceAccessTokens.ts index b099b966..c3540ddb 100644 --- a/src/lib/graphql/createPreferenceAccessTokens.ts +++ b/src/lib/graphql/createPreferenceAccessTokens.ts @@ -3,7 +3,8 @@ import { CREATE_PREFERENCE_ACCESS_TOKENS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import type { GraphQLClient } from 'graphql-request'; import { chunk } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; export interface PreferenceAccessTokenInput { /** Slug of data subject to authenticate as */ diff --git a/src/lib/graphql/createSombraGotInstance.ts b/src/lib/graphql/createSombraGotInstance.ts index 59e4a32d..955d9ca6 100644 --- a/src/lib/graphql/createSombraGotInstance.ts +++ b/src/lib/graphql/createSombraGotInstance.ts @@ -49,7 +49,7 @@ export async function createSombraGotInstance( logger.info(colors.green(`Using sombra: ${customerUrl}`)); // Create got instance with default values return got.extend({ - prefixUrl: customerUrl, + prefixUrl: process.env.SOMBRA_URL || customerUrl, headers: { Authorization: `Bearer ${transcendApiKey}`, ...(sombraApiKey diff --git a/src/lib/graphql/fetchDataSubjects.ts b/src/lib/graphql/fetchDataSubjects.ts index cb25b686..76d08438 100644 --- a/src/lib/graphql/fetchDataSubjects.ts +++ b/src/lib/graphql/fetchDataSubjects.ts @@ -5,7 +5,8 @@ import { RequestActionObjectResolver } from '@transcend-io/privacy-types'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { makeGraphQLRequest } from './makeGraphQLRequest'; export interface DataSubject { diff --git a/src/lib/graphql/fetchIdentifiers.ts b/src/lib/graphql/fetchIdentifiers.ts index 34e0e1a7..669872c5 100644 --- a/src/lib/graphql/fetchIdentifiers.ts +++ b/src/lib/graphql/fetchIdentifiers.ts @@ -5,7 +5,8 @@ import { keyBy, uniq, flatten, difference } from 'lodash-es'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { makeGraphQLRequest } from './makeGraphQLRequest'; export interface Identifier { diff --git a/src/lib/graphql/syncActionItemCollections.ts b/src/lib/graphql/syncActionItemCollections.ts index 5c1f1682..65bc27a2 100644 --- a/src/lib/graphql/syncActionItemCollections.ts +++ b/src/lib/graphql/syncActionItemCollections.ts @@ -1,6 +1,7 @@ import { ActionItemCollectionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_ACTION_ITEM_COLLECTION, CREATE_ACTION_ITEM_COLLECTION, diff --git a/src/lib/graphql/syncActionItems.ts b/src/lib/graphql/syncActionItems.ts index 5fc86a96..e33b5d19 100644 --- a/src/lib/graphql/syncActionItems.ts +++ b/src/lib/graphql/syncActionItems.ts @@ -1,7 +1,8 @@ import { ActionItemInput } from '../../codecs'; import { uniq, keyBy, chunk } from 'lodash-es'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_ACTION_ITEMS, CREATE_ACTION_ITEMS } from './gqls'; import { logger } from '../../logger'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncAgentFiles.ts b/src/lib/graphql/syncAgentFiles.ts index 5ffd1f95..f91514fe 100644 --- a/src/lib/graphql/syncAgentFiles.ts +++ b/src/lib/graphql/syncAgentFiles.ts @@ -1,6 +1,7 @@ import { AgentFileInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_AGENT_FILES, CREATE_AGENT_FILE } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncAgentFunctions.ts b/src/lib/graphql/syncAgentFunctions.ts index ed5665e9..1b658ab4 100644 --- a/src/lib/graphql/syncAgentFunctions.ts +++ b/src/lib/graphql/syncAgentFunctions.ts @@ -1,6 +1,7 @@ import { AgentFunctionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_AGENT_FUNCTIONS, CREATE_AGENT_FUNCTION } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncAgents.ts b/src/lib/graphql/syncAgents.ts index 755c6528..ae12c6ec 100644 --- a/src/lib/graphql/syncAgents.ts +++ b/src/lib/graphql/syncAgents.ts @@ -1,6 +1,7 @@ import { AgentInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_AGENTS, CREATE_AGENT } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncAttribute.ts b/src/lib/graphql/syncAttribute.ts index beb3df03..2da09766 100644 --- a/src/lib/graphql/syncAttribute.ts +++ b/src/lib/graphql/syncAttribute.ts @@ -11,7 +11,8 @@ import { } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { Attribute } from './fetchAllAttributes'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { logger } from '../../logger'; /** diff --git a/src/lib/graphql/syncBusinessEntities.ts b/src/lib/graphql/syncBusinessEntities.ts index 7709b1bb..6894e98b 100644 --- a/src/lib/graphql/syncBusinessEntities.ts +++ b/src/lib/graphql/syncBusinessEntities.ts @@ -1,6 +1,7 @@ import { BusinessEntityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_BUSINESS_ENTITIES, CREATE_BUSINESS_ENTITY } from './gqls'; import { logger } from '../../logger'; import { keyBy, chunk } from 'lodash-es'; diff --git a/src/lib/graphql/syncCodePackages.ts b/src/lib/graphql/syncCodePackages.ts index 4aa0991c..1af4b2a2 100644 --- a/src/lib/graphql/syncCodePackages.ts +++ b/src/lib/graphql/syncCodePackages.ts @@ -4,7 +4,8 @@ import { GraphQLClient } from 'graphql-request'; import { CodePackage, fetchAllCodePackages } from './fetchAllCodePackages'; import { logger } from '../../logger'; import { syncSoftwareDevelopmentKits } from './syncSoftwareDevelopmentKits'; -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map, mapSeries } = Bluebird; import { CodePackageInput, RepositoryInput } from '../../codecs'; import { CodePackageType } from '@transcend-io/privacy-types'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncConfigurationToTranscend.ts b/src/lib/graphql/syncConfigurationToTranscend.ts index 1d4cda0c..a1de8239 100644 --- a/src/lib/graphql/syncConfigurationToTranscend.ts +++ b/src/lib/graphql/syncConfigurationToTranscend.ts @@ -3,7 +3,8 @@ import { TranscendInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { fetchIdentifiersAndCreateMissing, Identifier, diff --git a/src/lib/graphql/syncConsentManager.ts b/src/lib/graphql/syncConsentManager.ts index 151d063f..c7951550 100644 --- a/src/lib/graphql/syncConsentManager.ts +++ b/src/lib/graphql/syncConsentManager.ts @@ -24,7 +24,8 @@ import { fetchConsentManagerExperiences, } from './fetchConsentManagerId'; import { keyBy } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { InitialViewState, OnConsentExpiry, diff --git a/src/lib/graphql/syncCookies.ts b/src/lib/graphql/syncCookies.ts index 40cdba4d..6c87a43b 100644 --- a/src/lib/graphql/syncCookies.ts +++ b/src/lib/graphql/syncCookies.ts @@ -5,7 +5,8 @@ import colors from 'colors'; import { UPDATE_OR_CREATE_COOKIES } from './gqls'; import { chunk } from 'lodash-es'; import { fetchConsentManagerId } from './fetchConsentManagerId'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; // import { keyBy } from 'lodash-es'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncDataCategories.ts b/src/lib/graphql/syncDataCategories.ts index 6ed8d20c..befb24f1 100644 --- a/src/lib/graphql/syncDataCategories.ts +++ b/src/lib/graphql/syncDataCategories.ts @@ -1,6 +1,7 @@ import { DataCategoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_DATA_SUB_CATEGORIES, CREATE_DATA_SUB_CATEGORY } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncDataFlows.ts b/src/lib/graphql/syncDataFlows.ts index 52601f03..ec39c3f5 100644 --- a/src/lib/graphql/syncDataFlows.ts +++ b/src/lib/graphql/syncDataFlows.ts @@ -1,7 +1,8 @@ import { GraphQLClient } from 'graphql-request'; import { CREATE_DATA_FLOWS, UPDATE_DATA_FLOWS } from './gqls'; import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { DataFlowInput } from '../../codecs'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchConsentManagerId } from './fetchConsentManagerId'; diff --git a/src/lib/graphql/syncDataSilos.ts b/src/lib/graphql/syncDataSilos.ts index 4536f9df..32d5b9c7 100644 --- a/src/lib/graphql/syncDataSilos.ts +++ b/src/lib/graphql/syncDataSilos.ts @@ -8,7 +8,8 @@ import { import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries, map } = Bluebird; import { DATA_SILOS, CREATE_DATA_SILOS, diff --git a/src/lib/graphql/syncIntlMessages.ts b/src/lib/graphql/syncIntlMessages.ts index f5af9f00..eff72f2c 100644 --- a/src/lib/graphql/syncIntlMessages.ts +++ b/src/lib/graphql/syncIntlMessages.ts @@ -4,7 +4,8 @@ import { IntlMessageInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_INTL_MESSAGES } from './gqls'; import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { makeGraphQLRequest } from './makeGraphQLRequest'; const MAX_PAGE_SIZE = 100; diff --git a/src/lib/graphql/syncPartitions.ts b/src/lib/graphql/syncPartitions.ts index bca2bafa..68403d7d 100644 --- a/src/lib/graphql/syncPartitions.ts +++ b/src/lib/graphql/syncPartitions.ts @@ -2,7 +2,8 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { CREATE_CONSENT_PARTITION, CONSENT_PARTITIONS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { difference } from 'lodash-es'; import { logger } from '../../logger'; import { PartitionInput } from '../../codecs'; diff --git a/src/lib/graphql/syncPolicies.ts b/src/lib/graphql/syncPolicies.ts index e0546efc..754b1baa 100644 --- a/src/lib/graphql/syncPolicies.ts +++ b/src/lib/graphql/syncPolicies.ts @@ -4,7 +4,8 @@ import { PolicyInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_POLICIES } from './gqls'; import { chunk, keyBy } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchPrivacyCenterId } from './fetchPrivacyCenterId'; import { fetchAllPolicies } from './fetchAllPolicies'; diff --git a/src/lib/graphql/syncProcessingActivities.ts b/src/lib/graphql/syncProcessingActivities.ts index 68a7d85f..ae7b8616 100644 --- a/src/lib/graphql/syncProcessingActivities.ts +++ b/src/lib/graphql/syncProcessingActivities.ts @@ -1,6 +1,7 @@ import { ProcessingActivityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_PROCESSING_ACTIVITIES, CREATE_PROCESSING_ACTIVITY, diff --git a/src/lib/graphql/syncProcessingPurposes.ts b/src/lib/graphql/syncProcessingPurposes.ts index 89805f71..e244b827 100644 --- a/src/lib/graphql/syncProcessingPurposes.ts +++ b/src/lib/graphql/syncProcessingPurposes.ts @@ -1,6 +1,7 @@ import { ProcessingPurposeInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_PROCESSING_PURPOSE_SUB_CATEGORIES, CREATE_PROCESSING_PURPOSE_SUB_CATEGORY, diff --git a/src/lib/graphql/syncPromptGroups.ts b/src/lib/graphql/syncPromptGroups.ts index b717a326..d8d77cb7 100644 --- a/src/lib/graphql/syncPromptGroups.ts +++ b/src/lib/graphql/syncPromptGroups.ts @@ -3,7 +3,8 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_GROUPS, CREATE_PROMPT_GROUP } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { fetchAllPromptGroups } from './fetchPromptGroups'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncPromptPartials.ts b/src/lib/graphql/syncPromptPartials.ts index 98ee67a6..c99e04d0 100644 --- a/src/lib/graphql/syncPromptPartials.ts +++ b/src/lib/graphql/syncPromptPartials.ts @@ -3,7 +3,8 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_PARTIALS, CREATE_PROMPT_PARTIAL } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { fetchAllPromptPartials } from './fetchPromptPartials'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncPrompts.ts b/src/lib/graphql/syncPrompts.ts index 3b03c0c9..4e85d10c 100644 --- a/src/lib/graphql/syncPrompts.ts +++ b/src/lib/graphql/syncPrompts.ts @@ -3,7 +3,8 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPTS, CREATE_PROMPT } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { fetchAllPrompts } from './fetchPrompts'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncRepositories.ts b/src/lib/graphql/syncRepositories.ts index ee3f063e..bdd56213 100644 --- a/src/lib/graphql/syncRepositories.ts +++ b/src/lib/graphql/syncRepositories.ts @@ -4,7 +4,8 @@ import { RepositoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_REPOSITORIES, CREATE_REPOSITORY } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries, map } = Bluebird; import { fetchAllRepositories, Repository } from './fetchAllRepositories'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncSoftwareDevelopmentKits.ts b/src/lib/graphql/syncSoftwareDevelopmentKits.ts index a02e76d2..3d9a112f 100644 --- a/src/lib/graphql/syncSoftwareDevelopmentKits.ts +++ b/src/lib/graphql/syncSoftwareDevelopmentKits.ts @@ -7,7 +7,8 @@ import { CREATE_SOFTWARE_DEVELOPMENT_KIT, } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries, map } = Bluebird; import { fetchAllSoftwareDevelopmentKits, SoftwareDevelopmentKit, diff --git a/src/lib/graphql/syncTeams.ts b/src/lib/graphql/syncTeams.ts index eccf0c98..e9ab29e7 100644 --- a/src/lib/graphql/syncTeams.ts +++ b/src/lib/graphql/syncTeams.ts @@ -1,6 +1,7 @@ import { TeamInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_TEAM, CREATE_TEAM } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncVendors.ts b/src/lib/graphql/syncVendors.ts index 0fbb73d3..3bc80c40 100644 --- a/src/lib/graphql/syncVendors.ts +++ b/src/lib/graphql/syncVendors.ts @@ -1,6 +1,7 @@ import { VendorInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { UPDATE_VENDORS, CREATE_VENDOR } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/uploadSiloDiscoveryResults.ts b/src/lib/graphql/uploadSiloDiscoveryResults.ts index 2fad990d..298b9415 100644 --- a/src/lib/graphql/uploadSiloDiscoveryResults.ts +++ b/src/lib/graphql/uploadSiloDiscoveryResults.ts @@ -1,5 +1,6 @@ import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { ADD_SILO_DISCOVERY_RESULTS } from './gqls'; import { GraphQLClient } from 'graphql-request'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts index 314bf069..b058f799 100644 --- a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts +++ b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts @@ -1,5 +1,6 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { groupBy, uniq } from 'lodash-es'; import { DEFAULT_TRANSCEND_API } from '../../constants'; diff --git a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts index 80aaeb5c..c4d1f466 100644 --- a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts +++ b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts @@ -1,5 +1,6 @@ import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { logger } from '../../logger'; import { UPDATE_PRIVACY_REQUEST, diff --git a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts index ed91d269..dfb4b524 100644 --- a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts +++ b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts @@ -6,7 +6,8 @@ import { getOneTrustRisk, getOneTrustUser, } from '../endpoints'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries, map } = Bluebird; import { logger } from '../../../logger'; import { OneTrustAssessmentQuestion, diff --git a/src/lib/preference-management/fetchConsentPreferencesChunked.ts b/src/lib/preference-management/fetchConsentPreferencesChunked.ts index e54ce0fd..d91d7a7c 100644 --- a/src/lib/preference-management/fetchConsentPreferencesChunked.ts +++ b/src/lib/preference-management/fetchConsentPreferencesChunked.ts @@ -1,4 +1,5 @@ -import { map as pmap } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map: pmap } = Bluebird; import type { Got } from 'got'; import cliProgress from 'cli-progress'; import colors from 'colors'; diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index 177efe74..c98a07bf 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -3,13 +3,15 @@ import type { Got } from 'got'; import colors from 'colors'; import { chunk } from 'lodash-es'; import { decodeCodec } from '@transcend-io/type-utils'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { logger } from '../../logger'; import { withPreferenceQueryRetry } from './withPreferenceQueryRetry'; import { ConsentPreferenceResponse } from './types'; import type { PreferenceUploadProgress } from '../../commands/consent/upload-preferences/upload'; import { extractErrorMessage, splitInHalf } from '../helpers'; +const { map } = Bluebird; + /** * Grab the current consent preference values for a list of identifiers * @@ -109,7 +111,7 @@ export async function getPreferencesForIdentifiers( .post(`v1/preferences/${partitionKey}/query`, { json: { filter: { identifiers: group }, - limit: group.length, + // limit: group.length, }, }) .json(), diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index a9bc13a8..62127f68 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -3,7 +3,8 @@ import colors from 'colors'; import inquirer from 'inquirer'; import { FileFormatState } from './codecs'; import { logger } from '../../logger'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import { PreferenceTopic } from '../graphql'; import { PreferenceTopicType } from '@transcend-io/privacy-types'; import { splitCsvToList } from '../requests'; diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 081ae0ba..237a7496 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -8,7 +8,8 @@ import type { } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries } = Bluebird; import type { Identifier } from '../graphql'; import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; import type { PersistedState } from '@transcend-io/persisted-state'; diff --git a/src/lib/preference-management/tests/getPreferencesForIdentifiers.test.ts b/src/lib/preference-management/tests/getPreferencesForIdentifiers.test.ts index c34934c9..1a6fcd3b 100644 --- a/src/lib/preference-management/tests/getPreferencesForIdentifiers.test.ts +++ b/src/lib/preference-management/tests/getPreferencesForIdentifiers.test.ts @@ -91,6 +91,7 @@ describe('getPreferencesForIdentifiers', () => { // Build 250 identifiers -> 3 groups: 100, 100, 50 const identifiers = Array.from({ length: 250 }, (_, i) => ({ value: `user-${i + 1}@ex.com`, + name: 'email', })); // Fake Got client with post().json() chain that returns a result based on the requested group @@ -195,6 +196,7 @@ describe('getPreferencesForIdentifiers', () => { it('logs progress start and completion when skipLogging=false', async () => { const identifiers = Array.from({ length: 5 }, (_, i) => ({ value: `u${i + 1}`, + name: 'test-id', })); const postMock = vi.fn( diff --git a/src/lib/preference-management/withPreferenceQueryRetry.ts b/src/lib/preference-management/withPreferenceQueryRetry.ts index 0fc1c74f..670eea9b 100644 --- a/src/lib/preference-management/withPreferenceQueryRetry.ts +++ b/src/lib/preference-management/withPreferenceQueryRetry.ts @@ -13,6 +13,7 @@ export const RETRY_PREFERENCE_MSGS: string[] = [ '502 Bad Gateway', '504 Gateway Time-out', 'Task timed out after', + '429', 'unknown request error', ].map((s) => s.toLowerCase()); diff --git a/src/lib/requests/approvePrivacyRequests.ts b/src/lib/requests/approvePrivacyRequests.ts index added1e8..340671e8 100644 --- a/src/lib/requests/approvePrivacyRequests.ts +++ b/src/lib/requests/approvePrivacyRequests.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/bulkRestartRequests.ts b/src/lib/requests/bulkRestartRequests.ts index 04704ee6..2c266026 100644 --- a/src/lib/requests/bulkRestartRequests.ts +++ b/src/lib/requests/bulkRestartRequests.ts @@ -1,6 +1,7 @@ import { PersistedState } from '@transcend-io/persisted-state'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import cliProgress from 'cli-progress'; import colors from 'colors'; import * as t from 'io-ts'; diff --git a/src/lib/requests/bulkRetryEnrichers.ts b/src/lib/requests/bulkRetryEnrichers.ts index d1be5fd3..00409722 100644 --- a/src/lib/requests/bulkRetryEnrichers.ts +++ b/src/lib/requests/bulkRetryEnrichers.ts @@ -3,7 +3,8 @@ import { RequestEnricherStatus, RequestStatus, } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import cliProgress from 'cli-progress'; import colors from 'colors'; import { difference } from 'lodash-es'; diff --git a/src/lib/requests/cancelPrivacyRequests.ts b/src/lib/requests/cancelPrivacyRequests.ts index 198afedb..8da27ba3 100644 --- a/src/lib/requests/cancelPrivacyRequests.ts +++ b/src/lib/requests/cancelPrivacyRequests.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/downloadPrivacyRequestFiles.ts b/src/lib/requests/downloadPrivacyRequestFiles.ts index 11d2d520..4f56f3c6 100644 --- a/src/lib/requests/downloadPrivacyRequestFiles.ts +++ b/src/lib/requests/downloadPrivacyRequestFiles.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import colors from 'colors'; diff --git a/src/lib/requests/getFileMetadataForPrivacyRequests.ts b/src/lib/requests/getFileMetadataForPrivacyRequests.ts index 53a255ad..863b839f 100644 --- a/src/lib/requests/getFileMetadataForPrivacyRequests.ts +++ b/src/lib/requests/getFileMetadataForPrivacyRequests.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import cliProgress from 'cli-progress'; diff --git a/src/lib/requests/markSilentPrivacyRequests.ts b/src/lib/requests/markSilentPrivacyRequests.ts index 8b56d748..6ccb6a4e 100644 --- a/src/lib/requests/markSilentPrivacyRequests.ts +++ b/src/lib/requests/markSilentPrivacyRequests.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -11,6 +11,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Mark a set of privacy requests to be in silent mode * diff --git a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts index 861be0e1..5e50ad36 100644 --- a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts +++ b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/pullPrivacyRequests.ts b/src/lib/requests/pullPrivacyRequests.ts index 0865f60d..b38a7a0c 100644 --- a/src/lib/requests/pullPrivacyRequests.ts +++ b/src/lib/requests/pullPrivacyRequests.ts @@ -1,5 +1,6 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { groupBy } from 'lodash-es'; diff --git a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts index d641bec4..55db7517 100644 --- a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts +++ b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/retryRequestDataSilos.ts b/src/lib/requests/retryRequestDataSilos.ts index c6132a94..4996795a 100644 --- a/src/lib/requests/retryRequestDataSilos.ts +++ b/src/lib/requests/retryRequestDataSilos.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/skipPreflightJobs.ts b/src/lib/requests/skipPreflightJobs.ts index 59516f75..d95fd2fd 100644 --- a/src/lib/requests/skipPreflightJobs.ts +++ b/src/lib/requests/skipPreflightJobs.ts @@ -1,4 +1,5 @@ -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { mapSeries, map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index cdd56209..33306b57 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/streamPrivacyRequestFiles.ts b/src/lib/requests/streamPrivacyRequestFiles.ts index 4f94ba22..57b5d014 100644 --- a/src/lib/requests/streamPrivacyRequestFiles.ts +++ b/src/lib/requests/streamPrivacyRequestFiles.ts @@ -1,4 +1,5 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import colors from 'colors'; import { RequestFileMetadata } from './getFileMetadataForPrivacyRequests'; import type { Got } from 'got'; diff --git a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts index 550b0131..bab7beeb 100644 --- a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts +++ b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; +const { map } = Bluebird; import * as t from 'io-ts'; import { uniq } from 'lodash-es'; import cliProgress from 'cli-progress'; diff --git a/src/reconcile-preference-records.ts b/src/reconcile-preference-records.ts new file mode 100644 index 00000000..022b3a35 --- /dev/null +++ b/src/reconcile-preference-records.ts @@ -0,0 +1,872 @@ +#!/usr/bin/env node +/* eslint-disable max-len */ +/* eslint-disable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,@typescript-eslint/no-explicit-any,max-lines,no-continue,no-loop-func,no-param-reassign */ + +import fs from 'node:fs'; +import path from 'node:path'; + +import colors from 'colors'; +import cliProgress from 'cli-progress'; + +import { chunk, uniqBy } from 'lodash-es'; +import type { Got } from 'got'; +import type { Options as CsvParseOptions } from 'csv-parse'; +import { parse as parseCsvSync } from 'csv-parse/sync'; +import * as t from 'io-ts'; + +import { decodeCodec } from '@transcend-io/type-utils'; +import { logger } from './logger'; +import { createSombraGotInstance } from './lib/graphql'; +import { getPreferencesForIdentifiers } from './lib/preference-management'; + +import Bluebird from 'bluebird'; +// import { extractErrorMessage } from './lib/helpers'; + +const { map } = Bluebird; + +/** + * + */ +type Identifier = { + /** */ name: string /** */; + /** */ + value: string; +}; + +/** + * + */ +type PreferenceRecord = { + /** */ + identifiers?: Identifier[]; + /** */ + purposes?: Array<{ + /** */ + purpose: string; + /** */ + enabled: boolean; + /** */ + preferences?: Array<{ + /** */ + topic?: string; + /** */ + choice?: unknown; + }>; + }>; + [k: string]: unknown; +}; + +/** + * + */ +type Options = { + /** */ + in: string; + /** */ + partition: string; + /** */ + batchSize: number; + /** */ + downloadLogInterval: number; + /** */ + transcendUrl: string; + /** */ + transcendApiKey: string; + /** */ + sombraApiKey?: string; +}; + +/** + * + * @param pathToFile + * @param codec + * @param options + */ +export function readCsv( + pathToFile: string, + codec: T, + options: CsvParseOptions = {}, +): t.TypeOf[] { + const fileContent = parseCsvSync(fs.readFileSync(pathToFile, 'utf-8'), { + columns: true, + relax_column_count: true, + relax_quotes: true, + skip_empty_lines: true, + trim: true, + ...options, + }); + + const data = decodeCodec(t.array(codec), fileContent); + + const parsed = data.map((datum) => + Object.entries(datum).reduce( + (acc, [key, value]) => + Object.assign(acc, { + [key.replace(/[^a-z_.+\-A-Z -~]/g, '')]: value, + }), + {} as any, + ), + ); + + return parsed as any; +} + +const OutRowCodec = t.intersection([ + t.type({ + personID: t.string, + transcendID: t.string, + email_withheld: t.string, + }), + t.record(t.string, t.unknown), +]); + +/** + * + */ +type OutRow = t.TypeOf; + +/** + * + */ +type RowMetrics = { + /** */ + lookupBy: 'transcendID' | 'personID'; + /** */ + lookupValue: string; + + /** Total records AFTER unique-by-fingerprint */ + totalRecords: number; + + /** Total records BEFORE unique-by-fingerprint */ + totalRecordsRaw: number; + + /** */ + email: string; + /** */ + emailCount: number; + /** */ + multiEmail: boolean; + + /** */ + distinctVariants: number; + /** */ + largestVariantCount: number; + /** */ + identicalRecordCount: number; + /** */ + allIdentical: boolean; + + /** */ + isDuplicateRow: boolean; + /** */ + dupOfRowIndex: number; + + /** JSON dump of RAW records (before unique) */ + recordsJson: string; + /** */ + recordsJsonTruncated: boolean; + + /** */ + runAttempted: boolean; + /** */ + runUpdated: boolean; + /** */ + runUpdateIdentifier: 'transcendID' | 'email' | ''; + /** */ + runError: string; +}; + +/** + * + * @param record + */ +function fingerprintRecord(record: PreferenceRecord): string { + const ids = (record.identifiers ?? []) + .filter((x) => x?.name && x?.value) + .map((x) => ({ name: String(x.name), value: String(x.value) })) + .sort((a, b) => + a.name === b.name + ? a.value.localeCompare(b.value) + : a.name.localeCompare(b.name), + ); + + const purposes = (record.purposes ?? []) + .map((p) => { + const prefs = (p.preferences ?? []) + .map((pr) => ({ topic: pr.topic ?? '', choice: pr.choice ?? null })) + .sort((a, b) => String(a.topic).localeCompare(String(b.topic))); + + return { purpose: p.purpose, enabled: !!p.enabled, preferences: prefs }; + }) + .sort((a, b) => String(a.purpose).localeCompare(String(b.purpose))); + + return JSON.stringify({ identifiers: ids, purposes }); +} + +/** + * + * @param records + */ +function getUniqueEmails(records: PreferenceRecord[]): string[] { + const set = new Set(); + for (const r of records) { + for (const id of r.identifiers ?? []) { + if (id?.name === 'email' && id.value) { + const v = String(id.value).trim(); + if (v) set.add(v); + } + } + } + return Array.from(set).sort((a, b) => a.localeCompare(b)); +} + +/** + * Build lookup map with duplicates preserved. + * + * @param records + */ +function buildLookupMapAll( + records: PreferenceRecord[], +): Map { + const m = new Map(); + for (const r of records) { + for (const id of r.identifiers ?? []) { + if (!id?.name || !id?.value) continue; + const k = `${id.name}:${id.value}`; + const arr = m.get(k) ?? []; + arr.push(r); + m.set(k, arr); + } + } + return m; +} + +/** + * + * @param records + */ +function uniqueByFingerprint(records: PreferenceRecord[]): PreferenceRecord[] { + const seen = new Set(); + const out: PreferenceRecord[] = []; + for (const r of records) { + const fp = fingerprintRecord(r); + if (seen.has(fp)) continue; + seen.add(fp); + out.push(r); + } + return out; +} + +/** + * + * @param records + */ +function countRawVsUniqueByFingerprint(records: PreferenceRecord[]): { + /** */ + raw: number; + /** */ + unique: number; +} { + const seen = new Set(); + for (const r of records) seen.add(fingerprintRecord(r)); + return { raw: records.length, unique: seen.size }; +} + +/** + * Fetch preferences for NON-shared identifiers (transcendID/email) in a batch, + * caching raw results PER identifier value. + * + * IMPORTANT: Cache stores RAW arrays (not deduped). + * + * @param sombra + * @param opts + */ +async function getPreferencesForIdentifiersCachedRaw( + sombra: Got, + opts: { + /** */ + identifiers: Identifier[]; + /** */ + partitionKey: string; + /** */ + logInterval: number; + /** */ + cache: Map; + /** */ + counters: { + /** */ hit: number /** */; + /** */ + miss: number; + }; + }, +): Promise { + if (opts.identifiers.length === 0) return []; + + const toFetch: Identifier[] = []; + const fromCache: PreferenceRecord[] = []; + + for (const id of opts.identifiers) { + const k = `${id.name}:${id.value}`; + const cached = opts.cache.get(k); + if (cached) { + opts.counters.hit += 1; + fromCache.push(...cached); + } else { + opts.counters.miss += 1; + toFetch.push(id); + } + } + + if (toFetch.length === 0) return fromCache; + + const fetched = (await getPreferencesForIdentifiers(sombra, { + identifiers: toFetch, + partitionKey: opts.partitionKey, + concurrency: 50, + logInterval: opts.logInterval, + skipLogging: true, + })) as any as PreferenceRecord[]; + + // Populate cache PER identifier by scanning returned records. + // This preserves duplicates in the cached arrays. + const lookupFetched = buildLookupMapAll(fetched); + for (const id of toFetch) { + const k = `${id.name}:${id.value}`; + opts.cache.set(k, lookupFetched.get(k) ?? []); + } + + return [...fromCache, ...fetched]; +} + +/** + * Shared identifiers (personID) must be queried one-by-one. + * Cache stores RAW arrays per identifier. + * + * @param sombra + * @param opts + */ +async function getPreferencesForSharedIdentifiersOneByOneCachedRaw( + sombra: Got, + opts: { + /** */ + identifiers: Identifier[]; + /** */ + partitionKey: string; + /** */ + cache: Map; + /** */ + counters: { + /** */ hit: number /** */; + /** */ + miss: number; + }; + }, +): Promise { + if (opts.identifiers.length === 0) return []; + + const results = await map( + opts.identifiers, + async (identifier) => { + const cacheKey = `${identifier.name}:${identifier.value}`; + const cached = opts.cache.get(cacheKey); + if (cached) { + opts.counters.hit += 1; + return cached; + } + opts.counters.miss += 1; + + const recs = (await getPreferencesForIdentifiers(sombra, { + identifiers: [identifier], + partitionKey: opts.partitionKey, + concurrency: 1, + logInterval: 999999999, + skipLogging: true, + })) as any as PreferenceRecord[]; + + opts.cache.set(cacheKey, recs); + return recs; + }, + { concurrency: 25 }, + ); + + return results.flat(); +} + +/** + * + * @param sombra + * @param args + */ +async function putIdentifierOnly( + sombra: Got, + args: { + /** */ partition: string /** */; + /** */ + identifier: Identifier; + }, +): Promise { + try { + await sombra + .put('v1/preferences', { + json: { + records: [ + { + timestamp: new Date( + Date.now() - 365 * 24 * 60 * 60 * 1000, + ).toISOString(), + partition: args.partition, + identifiers: [args.identifier], + }, + ], + skipWorkflowTriggers: true, + }, + }) + .json(); + } catch (e) { + throw new Error(`Failed to put identifier: ${e?.response?.body}`); + } +} + +/** + * + * @param v + */ +function csvEscape(v: string): string { + const s = v ?? ''; + if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`; + return s; +} + +/** + * + * @param n + */ +function ms(n: number): string { + if (n < 1000) return `${n}ms`; + return `${(n / 1000).toFixed(2)}s`; +} + +/** + * + */ +async function main(): Promise { + const opts: Options = { + in: path.resolve('./working/costco/concerns/out.csv'), + partition: process.env.PARTITION ?? '448b3320-9d7c-499a-bc56-f0dae33c8f5c', + batchSize: Number(process.env.BATCH_SIZE ?? '500'), + downloadLogInterval: Number(process.env.DOWNLOAD_LOG_INTERVAL ?? '100'), + transcendUrl: process.env.TRANSCEND_URL ?? '', + transcendApiKey: process.env.TRANSCEND_API_KEY ?? '', + sombraApiKey: process.env.SOMBRA_API_KEY, + }; + + const runEnabled = String(process.env.RUN ?? '').toLowerCase() === 'true'; + const maxJsonChars = Number(process.env.MAX_JSON_CHARS ?? '50000'); + + if (!opts.transcendUrl || !opts.transcendApiKey) { + throw new Error( + 'Missing TRANSCEND_URL or TRANSCEND_API_KEY in environment.', + ); + } + + const t0 = Date.now(); + + logger.info(colors.green(`Reading CSV: ${opts.in}`)); + const rows = readCsv(opts.in, OutRowCodec, { columns: true }) as OutRow[]; + + const rawFile = fs.readFileSync(opts.in, 'utf-8'); + const headerLine = + rawFile.split(/\r?\n/)[0] ?? 'personID,transcendID,email_withheld'; + + const extraHeaders = [ + 'lookupBy', + 'lookupValue', + 'totalRecordsRaw', + 'totalRecords', + 'distinctVariants', + 'largestVariantCount', + 'identicalRecordCount', + 'allIdentical', + 'email', + 'emailCount', + 'multiEmail', + 'isDuplicateRow', + 'dupOfRowIndex', + 'recordsJsonTruncated', + 'recordsJson', + 'runAttempted', + 'runUpdated', + 'runUpdateIdentifier', + 'runError', + ]; + + logger.info(colors.green('Creating Sombra client...')); + const sombra = await createSombraGotInstance( + opts.transcendUrl, + opts.transcendApiKey, + opts.sombraApiKey, + ); + + // RAW caches (per identifier value) + const personIdCache = new Map(); + const transcendIdCache = new Map(); + const emailCache = new Map(); + + // duplicate-row tracking + const rowSeen = new Map(); + + const outTmp = `${opts.in}.tmp`; + const writer = fs.createWriteStream(outTmp, { encoding: 'utf8' }); + writer.write(`${headerLine},${extraHeaders.join(',')}\n`); + + const batches = chunk(rows, opts.batchSize); + logger.info( + colors.magenta( + `Processing ${rows.length} rows in ${batches.length} batches (batchSize=${opts.batchSize}) RUN=${runEnabled}`, + ), + ); + + const progressBar = new cliProgress.SingleBar( + { + format: `Rows |${colors.cyan( + '{bar}', + )}| {value}/{total} | {percentage}% | ETA {eta}s | batch {batch}/{batches}`, + }, + cliProgress.Presets.shades_classic, + ); + progressBar.start(rows.length, 0, { batch: 0, batches: batches.length }); + + let processed = 0; + let written = 0; + let dupRows = 0; + + for (let batchIndex = 0; batchIndex < batches.length; batchIndex += 1) { + const batch = batches[batchIndex]; + const batchT0 = Date.now(); + + progressBar.update(processed, { + batch: batchIndex + 1, + batches: batches.length, + }); + + // counters for cache efficiency + const cTrans = { hit: 0, miss: 0 }; + const cEmail = { hit: 0, miss: 0 }; + const cPerson = { hit: 0, miss: 0 }; + + const stage0 = Date.now(); + const transcendIDs: Identifier[] = uniqBy( + batch + .map((r) => { + const v = String((r as any).transcendID ?? '').trim(); + return v ? ({ name: 'transcendID', value: v } as Identifier) : null; + }) + .filter(Boolean) as Identifier[], + (x) => `${x.name}:${x.value}`, + ); + + const personIDs: Identifier[] = uniqBy( + batch + .map((r) => { + const v = String((r as any).personID ?? '').trim(); + return v ? ({ name: 'personID', value: v } as Identifier) : null; + }) + .filter(Boolean) as Identifier[], + (x) => `${x.name}:${x.value}`, + ); + + const emails: Identifier[] = uniqBy( + batch + .map((r) => { + const v = String((r as any).email ?? '').trim(); + return v && v.includes('@') + ? ({ name: 'email', value: v } as Identifier) + : null; + }) + .filter(Boolean) as Identifier[], + (x) => `${x.name}:${x.value}`, + ); + + const idBuildMs = Date.now() - stage0; + + // FETCH RAW (no dedupe here) + const stage1 = Date.now(); + const [recordsByTranscendRaw, recordsByPersonRaw, recordsByEmailRaw] = + await Promise.all([ + getPreferencesForIdentifiersCachedRaw(sombra, { + identifiers: transcendIDs, + partitionKey: opts.partition, + logInterval: opts.downloadLogInterval, + cache: transcendIdCache, + counters: cTrans, + }), + getPreferencesForSharedIdentifiersOneByOneCachedRaw(sombra, { + identifiers: personIDs, + partitionKey: opts.partition, + cache: personIdCache, + counters: cPerson, + }), + getPreferencesForIdentifiersCachedRaw(sombra, { + identifiers: emails, + partitionKey: opts.partition, + logInterval: opts.downloadLogInterval, + cache: emailCache, + counters: cEmail, + }), + ]); + const fetchMs = Date.now() - stage1; + + // Batch-level “how many dupes” metrics (raw vs unique-by-fingerprint) + const tCounts = countRawVsUniqueByFingerprint(recordsByTranscendRaw); + const pCounts = countRawVsUniqueByFingerprint(recordsByPersonRaw); + const eCounts = countRawVsUniqueByFingerprint(recordsByEmailRaw); + + const allRaw = [ + ...(recordsByTranscendRaw as PreferenceRecord[]), + ...(recordsByPersonRaw as PreferenceRecord[]), + ...(recordsByEmailRaw as PreferenceRecord[]), + ]; + const allCounts = countRawVsUniqueByFingerprint(allRaw); + + // Lookup map MUST preserve duplicates so per-row raw length is meaningful + const lookupMapRaw = buildLookupMapAll(allRaw); + + // ROW PROCESSING + const stage2 = Date.now(); + const results = await map( + batch, + async (r, idxInBatch) => { + const rowIndex = batchIndex * opts.batchSize + idxInBatch + 1; + + const personID = String((r as any).personID ?? '').trim(); + const transcendID = String((r as any).transcendID ?? '').trim(); + const emailWithheld = String((r as any).email_withheld ?? '').trim(); + const email = String((r as any).email ?? '').trim(); + + const lookupBy: 'transcendID' | 'personID' = transcendID + ? 'transcendID' + : 'personID'; + const lookupValue = transcendID || personID || ''; + + const rowKey = `${personID}||${transcendID}||${emailWithheld}`; + const firstSeenAt = rowSeen.get(rowKey) ?? 0; + const isDup = firstSeenAt > 0; + if (!isDup) rowSeen.set(rowKey, rowIndex); + else dupRows += 1; + + const metrics: RowMetrics = { + lookupBy, + lookupValue, + totalRecordsRaw: 0, + totalRecords: 0, + + email: '', + emailCount: 0, + multiEmail: false, + + distinctVariants: 0, + largestVariantCount: 0, + identicalRecordCount: 0, + allIdentical: false, + + isDuplicateRow: isDup, + dupOfRowIndex: isDup ? firstSeenAt : 0, + + recordsJson: '', + recordsJsonTruncated: false, + + runAttempted: false, + runUpdated: false, + runUpdateIdentifier: '', + runError: '', + }; + + if (!lookupValue) { + metrics.runError = 'Missing both transcendID and personID'; + return { personID, transcendID, emailWithheld, metrics }; + } + + // RAW matches (duplicates preserved) + const recsRaw: PreferenceRecord[] = [ + ...(transcendID + ? lookupMapRaw.get(`transcendID:${transcendID}`) ?? [] + : []), + // ...(personID ? lookupMapRaw.get(`personID:${personID}`) ?? [] : []), + ...(email && email.includes('@') + ? lookupMapRaw.get(`email:${email}`) ?? [] + : []), + ]; + + metrics.totalRecordsRaw = recsRaw.length; + + // Unique-by-fingerprint (this is where you measure “how many dupes”) + const recsUnique = uniqueByFingerprint(recsRaw); + metrics.totalRecords = recsUnique.length; + + // email source of truth from UNIQUE set + const emailsFound = getUniqueEmails(recsUnique); + metrics.emailCount = emailsFound.length; + metrics.multiEmail = emailsFound.length > 1; + metrics.email = emailsFound.length === 1 ? emailsFound[0] : ''; + + // variant stats on UNIQUE set + if (recsUnique.length > 0) { + metrics.distinctVariants = recsUnique.length; // because recsUnique is unique by fingerprint + metrics.largestVariantCount = 1; + metrics.identicalRecordCount = 1; + metrics.allIdentical = recsUnique.length === 1; + } + + // JSON dump of RAW records (pre-unique) + let json = ''; + try { + json = JSON.stringify(recsRaw); + } catch (e: any) { + json = JSON.stringify({ + error: 'Failed to stringify recsRaw', + message: e?.message ?? String(e), + }); + } + if (maxJsonChars > 0 && json.length > maxJsonChars) { + metrics.recordsJsonTruncated = true; + metrics.recordsJson = json.slice(0, maxJsonChars); + } else { + metrics.recordsJson = json; + } + + if (runEnabled) { + metrics.runAttempted = true; + try { + if (transcendID) { + await putIdentifierOnly(sombra, { + partition: opts.partition, + identifier: { name: 'transcendID', value: transcendID }, + }); + metrics.runUpdated = true; + metrics.runUpdateIdentifier = 'transcendID'; + } else if (emailsFound.length === 1) { + await putIdentifierOnly(sombra, { + partition: opts.partition, + identifier: { name: 'email', value: emailsFound[0] }, + }); + metrics.runUpdated = true; + metrics.runUpdateIdentifier = 'email'; + } else if (emailsFound.length === 0) { + metrics.runError = + 'RUN enabled but no transcendID and no email found in existing records'; + } else { + metrics.runError = `RUN enabled but multiple emails found (${emailsFound.length})`; + } + } catch (err: any) { + metrics.runError = err?.message ?? String(err); + } + if (metrics.runError) { + logger.warn( + colors.yellow( + `Row ${rowIndex} update error: ${metrics.runError}`, + ), + ); + } + } + + return { personID, transcendID, emailWithheld, metrics }; + }, + { concurrency: 50 }, + ); + const processMs = Date.now() - stage2; + + // WRITE (always write 1 output row per input row) + const stage3 = Date.now(); + for (const { + personID, + transcendID, + emailWithheld, + metrics, + } of results as any) { + writer.write( + `${[ + csvEscape(personID), + csvEscape(transcendID), + csvEscape(emailWithheld), + + csvEscape(metrics.lookupBy), + csvEscape(metrics.lookupValue), + csvEscape(String(metrics.totalRecordsRaw)), + csvEscape(String(metrics.totalRecords)), + csvEscape(String(metrics.distinctVariants)), + csvEscape(String(metrics.largestVariantCount)), + csvEscape(String(metrics.identicalRecordCount)), + csvEscape(String(metrics.allIdentical)), + + csvEscape(metrics.email), + csvEscape(String(metrics.emailCount)), + csvEscape(String(metrics.multiEmail)), + + csvEscape(String(metrics.isDuplicateRow)), + csvEscape(String(metrics.dupOfRowIndex)), + + csvEscape(String(metrics.recordsJsonTruncated)), + csvEscape(metrics.recordsJson), + + csvEscape(String(metrics.runAttempted)), + csvEscape(String(metrics.runUpdated)), + csvEscape(metrics.runUpdateIdentifier), + csvEscape(metrics.runError), + ].join(',')}\n`, + ); + written += 1; + } + const writeMs = Date.now() - stage3; + + processed += batch.length; + progressBar.update(processed, { + batch: batchIndex + 1, + batches: batches.length, + }); + + const batchMs = Date.now() - batchT0; + + // This is the key log you want: raw vs unique before any dedupe. + logger.info( + colors.green( + `Batch ${batchIndex + 1}/${batches.length} rows=${ + batch.length + } written=${written} dupRows=${dupRows} ` + + `| ids: tID=${transcendIDs.length} email=${emails.length} personID=${personIDs.length} ` + + `| fetched(raw/uniq): tID=${tCounts.raw}/${tCounts.unique} ` + + `email=${eCounts.raw}/${eCounts.unique} personID=${pCounts.raw}/${pCounts.unique} ` + + `ALL=${allCounts.raw}/${allCounts.unique} ` + + `| cache(hit/miss): tID=${cTrans.hit}/${cTrans.miss} email=${cEmail.hit}/${cEmail.miss} personID=${cPerson.hit}/${cPerson.miss} ` + + `| timing: build=${ms(idBuildMs)} fetch=${ms(fetchMs)} process=${ms( + processMs, + )} write=${ms(writeMs)} total=${ms(batchMs)}`, + ), + ); + } + + progressBar.update(rows.length); + progressBar.stop(); + + await new Promise((resolve, reject) => { + writer.end(() => resolve()); + writer.on('error', reject); + }); + + fs.renameSync(outTmp, opts.in); + + const totalMs = Date.now() - t0; + logger.info( + colors.magenta( + `Done. Wrote ${written}/${rows.length} rows (dupRows=${dupRows}) to "${ + opts.in + }" in ${ms(totalMs)}.`, + ), + ); +} + +main().catch((err) => { + logger.error(colors.red(err?.stack ?? String(err))); + process.exit(1); +}); +/* eslint-enable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,@typescript-eslint/no-explicit-any,max-lines,no-continue,no-loop-func,no-param-reassign */ +/* eslint-enable max-len */ diff --git a/src/trim-scripts.ts b/src/trim-scripts.ts new file mode 100644 index 00000000..7c4c209b --- /dev/null +++ b/src/trim-scripts.ts @@ -0,0 +1,231 @@ +#!/usr/bin/env node +import fs from 'node:fs'; +import path, { resolve } from 'node:path'; +import readline from 'node:readline'; +/* eslint-disable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,no-continue */ + +// /** +// * CLI options +// */ +// type Options = { +// /** */ +// in1: string; +// /** */ +// in2: string; +// /** */ +// out: string; +// /** if true, include rows even if all 3 columns are blank */ +// emptyOut: string; +// }; + +/** + * Very small CSV parser for a single line: + * - supports commas + * - supports double-quoted fields (with "" escaping) + * - does NOT support embedded newlines inside quotes (most exports won’t have them) + * + * @param line + */ +function parseCsvLine(line: string): string[] { + const out: string[] = []; + let cur = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const ch = line[i]; + + if (inQuotes) { + if (ch === '"') { + // escaped quote + if (i + 1 < line.length && line[i + 1] === '"') { + cur += '"'; + i++; + } else { + inQuotes = false; + } + } else { + cur += ch; + } + } else if (ch === ',') { + out.push(cur); + cur = ''; + } else if (ch === '"') { + inQuotes = true; + } else { + cur += ch; + } + } + out.push(cur); + return out; +} + +/** + * CSV escape for output + * + * @param v + */ +function csvEscape(v: string): string { + const s = v ?? ''; + if (/[",\n\r]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +/** + * Normalize headers: trim, lowercase, remove wrapping quotes, + * and treat "(email_withheld)" as "email_withheld". + * + * @param h + */ +function normalizeHeader(h: string): string { + const raw = (h ?? '').trim().replace(/^"|"$/g, ''); + const lowered = raw.toLowerCase(); + // strip surrounding parentheses + const parenStripped = lowered.replace(/^\((.*)\)$/, '$1'); + return parenStripped; +} + +/** + * + */ +type HeaderIndex = Record; + +/** + * + * @param headerLine + */ +function buildHeaderIndex(headerLine: string): HeaderIndex { + const headers = parseCsvLine(headerLine).map(normalizeHeader); + const idx: HeaderIndex = {}; + headers.forEach((h, i) => { + if (h) idx[h] = i; + }); + return idx; +} + +/** + * + * @param cols + * @param idx + * @param name + */ +function getField(cols: string[], idx: HeaderIndex, name: string): string { + const i = idx[name]; + if (i === undefined) return ''; + return (cols[i] ?? '').trim(); +} + +/** + * + * @param filePath + * @param writer + * @param emptyWriter + */ +async function processFile( + filePath: string, + writer: fs.WriteStream, + emptyWriter: fs.WriteStream, +): Promise { + const rl = readline.createInterface({ + input: fs.createReadStream(filePath), + crlfDelay: Infinity, + }); + + let headerIdx: HeaderIndex | null = null; + let rowCount = 0; + let dataRowIndex = 0; + let outCount = 0; + + for await (const line of rl) { + if (rowCount === 0) { + headerIdx = buildHeaderIndex(line); + rowCount += 1; + continue; + } + + rowCount += 1; + dataRowIndex += 1; + + if (!headerIdx) continue; + + // keep truly blank lines out of both outputs + if (!line.trim()) continue; + + const cols = parseCsvLine(line); + + const personID = getField(cols, headerIdx, 'personid'); + const transcendID = getField(cols, headerIdx, 'transcendid'); + const emailWithheld = + getField(cols, headerIdx, 'email_withheld') || + getField(cols, headerIdx, 'email') || + getField(cols, headerIdx, 'emailwitheld'); + + const allEmpty = !personID && !transcendID && !emailWithheld; + + if (allEmpty) { + emptyWriter.write( + `${csvEscape(path.basename(filePath))},${dataRowIndex},${csvEscape( + line, + )}\n`, + ); + writer.write( + `${csvEscape(personID)},${csvEscape(transcendID)},${csvEscape( + emailWithheld, + )}\n`, + ); + continue; + } + + writer.write( + `${csvEscape(personID)},${csvEscape(transcendID)},${csvEscape( + emailWithheld, + )}\n`, + ); + outCount += 1; + } + + return outCount; +} + +/** + * + */ +async function main() { + const opts = { + in1: resolve('./working/costco/concerns/concern_1.csv'), + in2: resolve('./working/costco/concerns/concern_4.csv'), + out: resolve('./working/costco/concerns/out.csv'), + emptyOut: resolve('./working/costco/concerns/empty_rows.csv'), + }; + + const in1Abs = path.resolve(opts.in1); + const in2Abs = path.resolve(opts.in2); + const outAbs = path.resolve(opts.out); + + const writer = fs.createWriteStream(outAbs, { encoding: 'utf8' }); + const emptyWriter = fs.createWriteStream(opts.emptyOut, { + encoding: 'utf8', + }); + + // header + writer.write('personID,transcendID,email_withheld\n'); + emptyWriter.write('source_file,row_index,raw_row\n'); + + let total = 0; + total += await processFile(in1Abs, writer, emptyWriter); + total += await processFile(in2Abs, writer, emptyWriter); + + await new Promise((resolve, reject) => { + writer.end(() => resolve()); + writer.on('error', reject); + }); + + console.error(`Wrote ${total} rows -> ${outAbs}`); +} + +main().catch((err) => { + console.error(err?.stack ?? String(err)); + process.exit(1); +}); +/* eslint-enable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,no-continue */ From 6817f5006d513dad72476e0350aee26eb5281aef Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 15 Dec 2025 20:20:41 -0800 Subject: [PATCH 61/72] Reverts --- src/commands/consent/pull-consent-metrics/impl.ts | 3 +-- src/commands/consent/update-consent-manager/impl.ts | 3 +-- src/commands/inventory/pull/impl.ts | 3 +-- src/commands/inventory/push/impl.ts | 3 +-- src/lib/ai/TranscendPromptManager.ts | 3 +-- src/lib/api-keys/generateCrossAccountApiKeys.ts | 3 +-- src/lib/consent-manager/updateConsentManagerVersionToLatest.ts | 3 +-- src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts | 3 +-- src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts | 3 +-- src/lib/cron/pushCronIdentifiersFromCsv.ts | 3 +-- src/lib/data-inventory/pullAllDatapoints.ts | 3 +-- src/lib/graphql/fetchDataSubjects.ts | 3 +-- src/lib/graphql/fetchIdentifiers.ts | 3 +-- src/lib/graphql/syncActionItemCollections.ts | 3 +-- src/lib/graphql/syncActionItems.ts | 3 +-- src/lib/graphql/syncAgentFiles.ts | 3 +-- src/lib/graphql/syncAgentFunctions.ts | 3 +-- src/lib/graphql/syncAgents.ts | 3 +-- src/lib/graphql/syncBusinessEntities.ts | 3 +-- src/lib/graphql/syncCodePackages.ts | 3 +-- src/lib/graphql/syncCookies.ts | 3 +-- src/lib/graphql/syncDataCategories.ts | 3 +-- src/lib/graphql/syncDataFlows.ts | 3 +-- src/lib/graphql/syncIntlMessages.ts | 3 +-- src/lib/graphql/syncPartitions.ts | 3 +-- src/lib/graphql/syncPolicies.ts | 3 +-- src/lib/graphql/syncProcessingActivities.ts | 3 +-- src/lib/graphql/syncProcessingPurposes.ts | 3 +-- src/lib/graphql/syncTeams.ts | 3 +-- src/lib/graphql/syncVendors.ts | 3 +-- src/lib/graphql/uploadSiloDiscoveryResults.ts | 3 +-- .../parsePreferenceAndPurposeValuesFromCsv.ts | 3 +-- .../preference-management/parsePreferenceIdentifiersFromCsv.ts | 3 +-- 33 files changed, 33 insertions(+), 66 deletions(-) diff --git a/src/commands/consent/pull-consent-metrics/impl.ts b/src/commands/consent/pull-consent-metrics/impl.ts index 12434d28..015a5615 100644 --- a/src/commands/consent/pull-consent-metrics/impl.ts +++ b/src/commands/consent/pull-consent-metrics/impl.ts @@ -1,8 +1,7 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { map, mapSeries } = Bluebird; +import { map, mapSeries } from 'bluebird'; import { join } from 'node:path'; import fs, { existsSync, mkdirSync } from 'node:fs'; import { diff --git a/src/commands/consent/update-consent-manager/impl.ts b/src/commands/consent/update-consent-manager/impl.ts index 2508c4aa..705fc629 100644 --- a/src/commands/consent/update-consent-manager/impl.ts +++ b/src/commands/consent/update-consent-manager/impl.ts @@ -1,8 +1,7 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { ConsentBundleType } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { logger } from '../../../logger'; import { updateConsentManagerVersionToLatest } from '../../../lib/consent-manager'; diff --git a/src/commands/inventory/pull/impl.ts b/src/commands/inventory/pull/impl.ts index c6497348..ea0a560b 100644 --- a/src/commands/inventory/pull/impl.ts +++ b/src/commands/inventory/pull/impl.ts @@ -8,8 +8,7 @@ import { import { logger } from '../../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { join } from 'node:path'; import fs from 'node:fs'; import { diff --git a/src/commands/inventory/push/impl.ts b/src/commands/inventory/push/impl.ts index ac9a899b..db44ef73 100644 --- a/src/commands/inventory/push/impl.ts +++ b/src/commands/inventory/push/impl.ts @@ -1,8 +1,7 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { existsSync, lstatSync } from 'node:fs'; import { join } from 'node:path'; import { readTranscendYaml } from '../../../lib/readTranscendYaml'; diff --git a/src/lib/ai/TranscendPromptManager.ts b/src/lib/ai/TranscendPromptManager.ts index 41178238..6871f576 100644 --- a/src/lib/ai/TranscendPromptManager.ts +++ b/src/lib/ai/TranscendPromptManager.ts @@ -42,8 +42,7 @@ import { fetchAllLargeLanguageModels, } from '../graphql/fetchLargeLanguageModels'; import { groupBy, keyBy, uniq, chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { PromptThread, fetchAllPromptThreads, diff --git a/src/lib/api-keys/generateCrossAccountApiKeys.ts b/src/lib/api-keys/generateCrossAccountApiKeys.ts index f2c9fc90..4de7ba33 100644 --- a/src/lib/api-keys/generateCrossAccountApiKeys.ts +++ b/src/lib/api-keys/generateCrossAccountApiKeys.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { buildTranscendGraphQLClientGeneric, loginUser, diff --git a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts index 7d0e12e6..a103db6e 100644 --- a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts +++ b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts @@ -1,6 +1,5 @@ import { ConsentBundleType } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { updateConsentManagerToLatest, buildTranscendGraphQLClient, diff --git a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts index 9cf0bd89..95697b6c 100644 --- a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts @@ -13,8 +13,7 @@ import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; /** * A CSV formatted identifier diff --git a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts index 1f9ea873..e2866c8f 100644 --- a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts @@ -12,8 +12,7 @@ import { import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; /** * A CSV formatted identifier diff --git a/src/lib/cron/pushCronIdentifiersFromCsv.ts b/src/lib/cron/pushCronIdentifiersFromCsv.ts index 86e6a4cc..82027087 100644 --- a/src/lib/cron/pushCronIdentifiersFromCsv.ts +++ b/src/lib/cron/pushCronIdentifiersFromCsv.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map, mapSeries } = Bluebird; +import { map, mapSeries } from 'bluebird'; import { chunk } from 'lodash-es'; import { createSombraGotInstance } from '../graphql'; import colors from 'colors'; diff --git a/src/lib/data-inventory/pullAllDatapoints.ts b/src/lib/data-inventory/pullAllDatapoints.ts index be7d1038..302315af 100644 --- a/src/lib/data-inventory/pullAllDatapoints.ts +++ b/src/lib/data-inventory/pullAllDatapoints.ts @@ -17,8 +17,7 @@ import { } from '../graphql'; import { logger } from '../../logger'; import type { DataCategoryInput, ProcessingPurposeInput } from '../../codecs'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; export interface DataSiloCsvPreview { /** ID of dataSilo */ diff --git a/src/lib/graphql/fetchDataSubjects.ts b/src/lib/graphql/fetchDataSubjects.ts index 76d08438..cb25b686 100644 --- a/src/lib/graphql/fetchDataSubjects.ts +++ b/src/lib/graphql/fetchDataSubjects.ts @@ -5,8 +5,7 @@ import { RequestActionObjectResolver } from '@transcend-io/privacy-types'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; export interface DataSubject { diff --git a/src/lib/graphql/fetchIdentifiers.ts b/src/lib/graphql/fetchIdentifiers.ts index 669872c5..34e0e1a7 100644 --- a/src/lib/graphql/fetchIdentifiers.ts +++ b/src/lib/graphql/fetchIdentifiers.ts @@ -5,8 +5,7 @@ import { keyBy, uniq, flatten, difference } from 'lodash-es'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; export interface Identifier { diff --git a/src/lib/graphql/syncActionItemCollections.ts b/src/lib/graphql/syncActionItemCollections.ts index 65bc27a2..5c1f1682 100644 --- a/src/lib/graphql/syncActionItemCollections.ts +++ b/src/lib/graphql/syncActionItemCollections.ts @@ -1,7 +1,6 @@ import { ActionItemCollectionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_ACTION_ITEM_COLLECTION, CREATE_ACTION_ITEM_COLLECTION, diff --git a/src/lib/graphql/syncActionItems.ts b/src/lib/graphql/syncActionItems.ts index e33b5d19..5fc86a96 100644 --- a/src/lib/graphql/syncActionItems.ts +++ b/src/lib/graphql/syncActionItems.ts @@ -1,8 +1,7 @@ import { ActionItemInput } from '../../codecs'; import { uniq, keyBy, chunk } from 'lodash-es'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_ACTION_ITEMS, CREATE_ACTION_ITEMS } from './gqls'; import { logger } from '../../logger'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncAgentFiles.ts b/src/lib/graphql/syncAgentFiles.ts index f91514fe..5ffd1f95 100644 --- a/src/lib/graphql/syncAgentFiles.ts +++ b/src/lib/graphql/syncAgentFiles.ts @@ -1,7 +1,6 @@ import { AgentFileInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_AGENT_FILES, CREATE_AGENT_FILE } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncAgentFunctions.ts b/src/lib/graphql/syncAgentFunctions.ts index 1b658ab4..ed5665e9 100644 --- a/src/lib/graphql/syncAgentFunctions.ts +++ b/src/lib/graphql/syncAgentFunctions.ts @@ -1,7 +1,6 @@ import { AgentFunctionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_AGENT_FUNCTIONS, CREATE_AGENT_FUNCTION } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncAgents.ts b/src/lib/graphql/syncAgents.ts index ae12c6ec..755c6528 100644 --- a/src/lib/graphql/syncAgents.ts +++ b/src/lib/graphql/syncAgents.ts @@ -1,7 +1,6 @@ import { AgentInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_AGENTS, CREATE_AGENT } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncBusinessEntities.ts b/src/lib/graphql/syncBusinessEntities.ts index 6894e98b..7709b1bb 100644 --- a/src/lib/graphql/syncBusinessEntities.ts +++ b/src/lib/graphql/syncBusinessEntities.ts @@ -1,7 +1,6 @@ import { BusinessEntityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_BUSINESS_ENTITIES, CREATE_BUSINESS_ENTITY } from './gqls'; import { logger } from '../../logger'; import { keyBy, chunk } from 'lodash-es'; diff --git a/src/lib/graphql/syncCodePackages.ts b/src/lib/graphql/syncCodePackages.ts index 1af4b2a2..4aa0991c 100644 --- a/src/lib/graphql/syncCodePackages.ts +++ b/src/lib/graphql/syncCodePackages.ts @@ -4,8 +4,7 @@ import { GraphQLClient } from 'graphql-request'; import { CodePackage, fetchAllCodePackages } from './fetchAllCodePackages'; import { logger } from '../../logger'; import { syncSoftwareDevelopmentKits } from './syncSoftwareDevelopmentKits'; -import Bluebird from 'bluebird'; -const { map, mapSeries } = Bluebird; +import { map, mapSeries } from 'bluebird'; import { CodePackageInput, RepositoryInput } from '../../codecs'; import { CodePackageType } from '@transcend-io/privacy-types'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncCookies.ts b/src/lib/graphql/syncCookies.ts index 6c87a43b..40cdba4d 100644 --- a/src/lib/graphql/syncCookies.ts +++ b/src/lib/graphql/syncCookies.ts @@ -5,8 +5,7 @@ import colors from 'colors'; import { UPDATE_OR_CREATE_COOKIES } from './gqls'; import { chunk } from 'lodash-es'; import { fetchConsentManagerId } from './fetchConsentManagerId'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; // import { keyBy } from 'lodash-es'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/graphql/syncDataCategories.ts b/src/lib/graphql/syncDataCategories.ts index befb24f1..6ed8d20c 100644 --- a/src/lib/graphql/syncDataCategories.ts +++ b/src/lib/graphql/syncDataCategories.ts @@ -1,7 +1,6 @@ import { DataCategoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_DATA_SUB_CATEGORIES, CREATE_DATA_SUB_CATEGORY } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncDataFlows.ts b/src/lib/graphql/syncDataFlows.ts index ec39c3f5..52601f03 100644 --- a/src/lib/graphql/syncDataFlows.ts +++ b/src/lib/graphql/syncDataFlows.ts @@ -1,8 +1,7 @@ import { GraphQLClient } from 'graphql-request'; import { CREATE_DATA_FLOWS, UPDATE_DATA_FLOWS } from './gqls'; import { chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { DataFlowInput } from '../../codecs'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchConsentManagerId } from './fetchConsentManagerId'; diff --git a/src/lib/graphql/syncIntlMessages.ts b/src/lib/graphql/syncIntlMessages.ts index eff72f2c..f5af9f00 100644 --- a/src/lib/graphql/syncIntlMessages.ts +++ b/src/lib/graphql/syncIntlMessages.ts @@ -4,8 +4,7 @@ import { IntlMessageInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_INTL_MESSAGES } from './gqls'; import { chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; const MAX_PAGE_SIZE = 100; diff --git a/src/lib/graphql/syncPartitions.ts b/src/lib/graphql/syncPartitions.ts index 68403d7d..bca2bafa 100644 --- a/src/lib/graphql/syncPartitions.ts +++ b/src/lib/graphql/syncPartitions.ts @@ -2,8 +2,7 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { CREATE_CONSENT_PARTITION, CONSENT_PARTITIONS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { difference } from 'lodash-es'; import { logger } from '../../logger'; import { PartitionInput } from '../../codecs'; diff --git a/src/lib/graphql/syncPolicies.ts b/src/lib/graphql/syncPolicies.ts index 754b1baa..e0546efc 100644 --- a/src/lib/graphql/syncPolicies.ts +++ b/src/lib/graphql/syncPolicies.ts @@ -4,8 +4,7 @@ import { PolicyInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_POLICIES } from './gqls'; import { chunk, keyBy } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchPrivacyCenterId } from './fetchPrivacyCenterId'; import { fetchAllPolicies } from './fetchAllPolicies'; diff --git a/src/lib/graphql/syncProcessingActivities.ts b/src/lib/graphql/syncProcessingActivities.ts index ae7b8616..68a7d85f 100644 --- a/src/lib/graphql/syncProcessingActivities.ts +++ b/src/lib/graphql/syncProcessingActivities.ts @@ -1,7 +1,6 @@ import { ProcessingActivityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_PROCESSING_ACTIVITIES, CREATE_PROCESSING_ACTIVITY, diff --git a/src/lib/graphql/syncProcessingPurposes.ts b/src/lib/graphql/syncProcessingPurposes.ts index e244b827..89805f71 100644 --- a/src/lib/graphql/syncProcessingPurposes.ts +++ b/src/lib/graphql/syncProcessingPurposes.ts @@ -1,7 +1,6 @@ import { ProcessingPurposeInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_PROCESSING_PURPOSE_SUB_CATEGORIES, CREATE_PROCESSING_PURPOSE_SUB_CATEGORY, diff --git a/src/lib/graphql/syncTeams.ts b/src/lib/graphql/syncTeams.ts index e9ab29e7..eccf0c98 100644 --- a/src/lib/graphql/syncTeams.ts +++ b/src/lib/graphql/syncTeams.ts @@ -1,7 +1,6 @@ import { TeamInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_TEAM, CREATE_TEAM } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/syncVendors.ts b/src/lib/graphql/syncVendors.ts index 3bc80c40..0fbb73d3 100644 --- a/src/lib/graphql/syncVendors.ts +++ b/src/lib/graphql/syncVendors.ts @@ -1,7 +1,6 @@ import { VendorInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { UPDATE_VENDORS, CREATE_VENDOR } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; diff --git a/src/lib/graphql/uploadSiloDiscoveryResults.ts b/src/lib/graphql/uploadSiloDiscoveryResults.ts index 298b9415..2fad990d 100644 --- a/src/lib/graphql/uploadSiloDiscoveryResults.ts +++ b/src/lib/graphql/uploadSiloDiscoveryResults.ts @@ -1,6 +1,5 @@ import { chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { ADD_SILO_DISCOVERY_RESULTS } from './gqls'; import { GraphQLClient } from 'graphql-request'; import { makeGraphQLRequest } from './makeGraphQLRequest'; diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index 62127f68..a9bc13a8 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -3,8 +3,7 @@ import colors from 'colors'; import inquirer from 'inquirer'; import { FileFormatState } from './codecs'; import { logger } from '../../logger'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import { PreferenceTopic } from '../graphql'; import { PreferenceTopicType } from '@transcend-io/privacy-types'; import { splitCsvToList } from '../requests'; diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 237a7496..081ae0ba 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -8,8 +8,7 @@ import type { } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; -import Bluebird from 'bluebird'; -const { mapSeries } = Bluebird; +import { mapSeries } from 'bluebird'; import type { Identifier } from '../graphql'; import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; import type { PersistedState } from '@transcend-io/persisted-state'; From a4b1f31c9f2958c5e03152bc44ad0b6444479d9d Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 15 Dec 2025 20:21:48 -0800 Subject: [PATCH 62/72] Reverts --- src/commands/request/cron/pull-profiles/impl.ts | 3 +-- src/lib/consent-manager/buildXdiSyncEndpoint.ts | 3 +-- src/lib/consent-manager/uploadConsents.ts | 3 +-- src/lib/cron/markRequestDataSiloIdsCompleted.ts | 3 +-- src/lib/graphql/createPreferenceAccessTokens.ts | 3 +-- src/lib/graphql/syncAttribute.ts | 3 +-- src/lib/graphql/syncConfigurationToTranscend.ts | 3 +-- src/lib/graphql/syncConsentManager.ts | 3 +-- src/lib/graphql/syncDataSilos.ts | 3 +-- src/lib/graphql/syncPromptGroups.ts | 3 +-- src/lib/graphql/syncPromptPartials.ts | 3 +-- src/lib/graphql/syncPrompts.ts | 3 +-- src/lib/graphql/syncRepositories.ts | 3 +-- src/lib/graphql/syncSoftwareDevelopmentKits.ts | 3 +-- .../manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts | 3 +-- .../pushManualEnrichmentIdentifiersFromCsv.ts | 3 +-- .../oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts | 3 +-- src/lib/requests/approvePrivacyRequests.ts | 3 +-- src/lib/requests/bulkRestartRequests.ts | 3 +-- src/lib/requests/bulkRetryEnrichers.ts | 3 +-- src/lib/requests/cancelPrivacyRequests.ts | 3 +-- src/lib/requests/downloadPrivacyRequestFiles.ts | 3 +-- src/lib/requests/getFileMetadataForPrivacyRequests.ts | 3 +-- src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts | 3 +-- src/lib/requests/pullPrivacyRequests.ts | 3 +-- src/lib/requests/removeUnverifiedRequestIdentifiers.ts | 3 +-- src/lib/requests/retryRequestDataSilos.ts | 3 +-- src/lib/requests/skipPreflightJobs.ts | 3 +-- src/lib/requests/skipRequestDataSilos.ts | 3 +-- src/lib/requests/streamPrivacyRequestFiles.ts | 3 +-- src/lib/requests/uploadPrivacyRequestsFromCsv.ts | 3 +-- 31 files changed, 31 insertions(+), 62 deletions(-) diff --git a/src/commands/request/cron/pull-profiles/impl.ts b/src/commands/request/cron/pull-profiles/impl.ts index ba48a74e..94bf1d01 100644 --- a/src/commands/request/cron/pull-profiles/impl.ts +++ b/src/commands/request/cron/pull-profiles/impl.ts @@ -2,8 +2,7 @@ import type { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../../../logger'; import colors from 'colors'; import { uniq, chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { buildTranscendGraphQLClient, fetchRequestFilesForRequest, diff --git a/src/lib/consent-manager/buildXdiSyncEndpoint.ts b/src/lib/consent-manager/buildXdiSyncEndpoint.ts index 4fa8b87d..32e490cb 100644 --- a/src/lib/consent-manager/buildXdiSyncEndpoint.ts +++ b/src/lib/consent-manager/buildXdiSyncEndpoint.ts @@ -2,8 +2,7 @@ import colors from 'colors'; import { buildTranscendGraphQLClient, fetchConsentManager } from '../graphql'; import { difference } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { StoredApiKey } from '../../codecs'; import { DEFAULT_TRANSCEND_API } from '../../constants'; import { logger } from '../../logger'; diff --git a/src/lib/consent-manager/uploadConsents.ts b/src/lib/consent-manager/uploadConsents.ts index 58ab1546..3b1b3b35 100644 --- a/src/lib/consent-manager/uploadConsents.ts +++ b/src/lib/consent-manager/uploadConsents.ts @@ -2,8 +2,7 @@ import { createTranscendConsentGotInstance } from '../graphql'; import colors from 'colors'; import * as t from 'io-ts'; import { DEFAULT_TRANSCEND_CONSENT_API } from '../../constants'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { createConsentToken } from './createConsentToken'; import { logger } from '../../logger'; import cliProgress from 'cli-progress'; diff --git a/src/lib/cron/markRequestDataSiloIdsCompleted.ts b/src/lib/cron/markRequestDataSiloIdsCompleted.ts index 83927877..07b46d2d 100644 --- a/src/lib/cron/markRequestDataSiloIdsCompleted.ts +++ b/src/lib/cron/markRequestDataSiloIdsCompleted.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/graphql/createPreferenceAccessTokens.ts b/src/lib/graphql/createPreferenceAccessTokens.ts index c3540ddb..b099b966 100644 --- a/src/lib/graphql/createPreferenceAccessTokens.ts +++ b/src/lib/graphql/createPreferenceAccessTokens.ts @@ -3,8 +3,7 @@ import { CREATE_PREFERENCE_ACCESS_TOKENS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import type { GraphQLClient } from 'graphql-request'; import { chunk } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; export interface PreferenceAccessTokenInput { /** Slug of data subject to authenticate as */ diff --git a/src/lib/graphql/syncAttribute.ts b/src/lib/graphql/syncAttribute.ts index 2da09766..beb3df03 100644 --- a/src/lib/graphql/syncAttribute.ts +++ b/src/lib/graphql/syncAttribute.ts @@ -11,8 +11,7 @@ import { } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { Attribute } from './fetchAllAttributes'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { logger } from '../../logger'; /** diff --git a/src/lib/graphql/syncConfigurationToTranscend.ts b/src/lib/graphql/syncConfigurationToTranscend.ts index a1de8239..1d4cda0c 100644 --- a/src/lib/graphql/syncConfigurationToTranscend.ts +++ b/src/lib/graphql/syncConfigurationToTranscend.ts @@ -3,8 +3,7 @@ import { TranscendInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { fetchIdentifiersAndCreateMissing, Identifier, diff --git a/src/lib/graphql/syncConsentManager.ts b/src/lib/graphql/syncConsentManager.ts index c7951550..151d063f 100644 --- a/src/lib/graphql/syncConsentManager.ts +++ b/src/lib/graphql/syncConsentManager.ts @@ -24,8 +24,7 @@ import { fetchConsentManagerExperiences, } from './fetchConsentManagerId'; import { keyBy } from 'lodash-es'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { InitialViewState, OnConsentExpiry, diff --git a/src/lib/graphql/syncDataSilos.ts b/src/lib/graphql/syncDataSilos.ts index 32d5b9c7..4536f9df 100644 --- a/src/lib/graphql/syncDataSilos.ts +++ b/src/lib/graphql/syncDataSilos.ts @@ -8,8 +8,7 @@ import { import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import Bluebird from 'bluebird'; -const { mapSeries, map } = Bluebird; +import { mapSeries, map } from 'bluebird'; import { DATA_SILOS, CREATE_DATA_SILOS, diff --git a/src/lib/graphql/syncPromptGroups.ts b/src/lib/graphql/syncPromptGroups.ts index d8d77cb7..b717a326 100644 --- a/src/lib/graphql/syncPromptGroups.ts +++ b/src/lib/graphql/syncPromptGroups.ts @@ -3,8 +3,7 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_GROUPS, CREATE_PROMPT_GROUP } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { fetchAllPromptGroups } from './fetchPromptGroups'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncPromptPartials.ts b/src/lib/graphql/syncPromptPartials.ts index c99e04d0..98ee67a6 100644 --- a/src/lib/graphql/syncPromptPartials.ts +++ b/src/lib/graphql/syncPromptPartials.ts @@ -3,8 +3,7 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_PARTIALS, CREATE_PROMPT_PARTIAL } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { fetchAllPromptPartials } from './fetchPromptPartials'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncPrompts.ts b/src/lib/graphql/syncPrompts.ts index 4e85d10c..3b03c0c9 100644 --- a/src/lib/graphql/syncPrompts.ts +++ b/src/lib/graphql/syncPrompts.ts @@ -3,8 +3,7 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPTS, CREATE_PROMPT } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { fetchAllPrompts } from './fetchPrompts'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncRepositories.ts b/src/lib/graphql/syncRepositories.ts index bdd56213..ee3f063e 100644 --- a/src/lib/graphql/syncRepositories.ts +++ b/src/lib/graphql/syncRepositories.ts @@ -4,8 +4,7 @@ import { RepositoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_REPOSITORIES, CREATE_REPOSITORY } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { mapSeries, map } = Bluebird; +import { mapSeries, map } from 'bluebird'; import { fetchAllRepositories, Repository } from './fetchAllRepositories'; import { logger } from '../../logger'; diff --git a/src/lib/graphql/syncSoftwareDevelopmentKits.ts b/src/lib/graphql/syncSoftwareDevelopmentKits.ts index 3d9a112f..a02e76d2 100644 --- a/src/lib/graphql/syncSoftwareDevelopmentKits.ts +++ b/src/lib/graphql/syncSoftwareDevelopmentKits.ts @@ -7,8 +7,7 @@ import { CREATE_SOFTWARE_DEVELOPMENT_KIT, } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import Bluebird from 'bluebird'; -const { mapSeries, map } = Bluebird; +import { mapSeries, map } from 'bluebird'; import { fetchAllSoftwareDevelopmentKits, SoftwareDevelopmentKit, diff --git a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts index b058f799..314bf069 100644 --- a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts +++ b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts @@ -1,6 +1,5 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { groupBy, uniq } from 'lodash-es'; import { DEFAULT_TRANSCEND_API } from '../../constants'; diff --git a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts index c4d1f466..80aaeb5c 100644 --- a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts +++ b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts @@ -1,6 +1,5 @@ import colors from 'colors'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { logger } from '../../logger'; import { UPDATE_PRIVACY_REQUEST, diff --git a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts index dfb4b524..ed91d269 100644 --- a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts +++ b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts @@ -6,8 +6,7 @@ import { getOneTrustRisk, getOneTrustUser, } from '../endpoints'; -import Bluebird from 'bluebird'; -const { mapSeries, map } = Bluebird; +import { mapSeries, map } from 'bluebird'; import { logger } from '../../../logger'; import { OneTrustAssessmentQuestion, diff --git a/src/lib/requests/approvePrivacyRequests.ts b/src/lib/requests/approvePrivacyRequests.ts index 340671e8..added1e8 100644 --- a/src/lib/requests/approvePrivacyRequests.ts +++ b/src/lib/requests/approvePrivacyRequests.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/bulkRestartRequests.ts b/src/lib/requests/bulkRestartRequests.ts index 2c266026..04704ee6 100644 --- a/src/lib/requests/bulkRestartRequests.ts +++ b/src/lib/requests/bulkRestartRequests.ts @@ -1,7 +1,6 @@ import { PersistedState } from '@transcend-io/persisted-state'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import cliProgress from 'cli-progress'; import colors from 'colors'; import * as t from 'io-ts'; diff --git a/src/lib/requests/bulkRetryEnrichers.ts b/src/lib/requests/bulkRetryEnrichers.ts index 00409722..d1be5fd3 100644 --- a/src/lib/requests/bulkRetryEnrichers.ts +++ b/src/lib/requests/bulkRetryEnrichers.ts @@ -3,8 +3,7 @@ import { RequestEnricherStatus, RequestStatus, } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import cliProgress from 'cli-progress'; import colors from 'colors'; import { difference } from 'lodash-es'; diff --git a/src/lib/requests/cancelPrivacyRequests.ts b/src/lib/requests/cancelPrivacyRequests.ts index 8da27ba3..198afedb 100644 --- a/src/lib/requests/cancelPrivacyRequests.ts +++ b/src/lib/requests/cancelPrivacyRequests.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/downloadPrivacyRequestFiles.ts b/src/lib/requests/downloadPrivacyRequestFiles.ts index 4f56f3c6..11d2d520 100644 --- a/src/lib/requests/downloadPrivacyRequestFiles.ts +++ b/src/lib/requests/downloadPrivacyRequestFiles.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import colors from 'colors'; diff --git a/src/lib/requests/getFileMetadataForPrivacyRequests.ts b/src/lib/requests/getFileMetadataForPrivacyRequests.ts index 863b839f..53a255ad 100644 --- a/src/lib/requests/getFileMetadataForPrivacyRequests.ts +++ b/src/lib/requests/getFileMetadataForPrivacyRequests.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import cliProgress from 'cli-progress'; diff --git a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts index 5e50ad36..861be0e1 100644 --- a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts +++ b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/pullPrivacyRequests.ts b/src/lib/requests/pullPrivacyRequests.ts index b38a7a0c..0865f60d 100644 --- a/src/lib/requests/pullPrivacyRequests.ts +++ b/src/lib/requests/pullPrivacyRequests.ts @@ -1,6 +1,5 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { groupBy } from 'lodash-es'; diff --git a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts index 55db7517..d641bec4 100644 --- a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts +++ b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/retryRequestDataSilos.ts b/src/lib/requests/retryRequestDataSilos.ts index 4996795a..c6132a94 100644 --- a/src/lib/requests/retryRequestDataSilos.ts +++ b/src/lib/requests/retryRequestDataSilos.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; diff --git a/src/lib/requests/skipPreflightJobs.ts b/src/lib/requests/skipPreflightJobs.ts index d95fd2fd..59516f75 100644 --- a/src/lib/requests/skipPreflightJobs.ts +++ b/src/lib/requests/skipPreflightJobs.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { mapSeries, map } = Bluebird; +import { mapSeries, map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index 33306b57..cdd56209 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { diff --git a/src/lib/requests/streamPrivacyRequestFiles.ts b/src/lib/requests/streamPrivacyRequestFiles.ts index 57b5d014..4f94ba22 100644 --- a/src/lib/requests/streamPrivacyRequestFiles.ts +++ b/src/lib/requests/streamPrivacyRequestFiles.ts @@ -1,5 +1,4 @@ -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import colors from 'colors'; import { RequestFileMetadata } from './getFileMetadataForPrivacyRequests'; import type { Got } from 'got'; diff --git a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts index bab7beeb..550b0131 100644 --- a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts +++ b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts @@ -1,7 +1,6 @@ /* eslint-disable max-lines */ import colors from 'colors'; -import Bluebird from 'bluebird'; -const { map } = Bluebird; +import { map } from 'bluebird'; import * as t from 'io-ts'; import { uniq } from 'lodash-es'; import cliProgress from 'cli-progress'; From 5ff7810eef955a794a60ca9496f9c0a0fc833381 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Mon, 15 Dec 2025 20:22:56 -0800 Subject: [PATCH 63/72] ud --- src/lib/requests/markSilentPrivacyRequests.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/requests/markSilentPrivacyRequests.ts b/src/lib/requests/markSilentPrivacyRequests.ts index 6ccb6a4e..8b56d748 100644 --- a/src/lib/requests/markSilentPrivacyRequests.ts +++ b/src/lib/requests/markSilentPrivacyRequests.ts @@ -1,4 +1,4 @@ -import Bluebird from 'bluebird'; +import { map } from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -11,8 +11,6 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -const { map } = Bluebird; - /** * Mark a set of privacy requests to be in silent mode * From f9c63b86fed7d36ae4fb7953418c668f43dab60f Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 16 Dec 2025 23:36:52 -0800 Subject: [PATCH 64/72] exact --- src/find-exact.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/find-exact.ts b/src/find-exact.ts index 8d306a5d..5b467b40 100644 --- a/src/find-exact.ts +++ b/src/find-exact.ts @@ -96,7 +96,8 @@ async function fileContainsExactBytes( } const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk; - if (buf.indexOf(needle) !== -1) { + const haystack = buf.toString('utf8').toLowerCase(); + if (haystack.includes(needle.toString('utf8'))) { stream.destroy(); resolve(true); return; @@ -277,7 +278,7 @@ async function main() { suppressErrors: true, }); - const needleBuf = Buffer.from(opts.needle, 'utf8'); + const needleBuf = Buffer.from(opts.needle.toLowerCase(), 'utf8'); const hits: string[] = []; await runWithConcurrency(normalFiles, opts.concurrency, async (file) => { From 85abeebd7c9e55f185d2faa3e26e7a690b6f3eeb Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Mon, 26 Jan 2026 08:20:59 -0800 Subject: [PATCH 65/72] Delete src/trim-scripts.ts --- src/trim-scripts.ts | 231 -------------------------------------------- 1 file changed, 231 deletions(-) delete mode 100644 src/trim-scripts.ts diff --git a/src/trim-scripts.ts b/src/trim-scripts.ts deleted file mode 100644 index 7c4c209b..00000000 --- a/src/trim-scripts.ts +++ /dev/null @@ -1,231 +0,0 @@ -#!/usr/bin/env node -import fs from 'node:fs'; -import path, { resolve } from 'node:path'; -import readline from 'node:readline'; -/* eslint-disable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,no-continue */ - -// /** -// * CLI options -// */ -// type Options = { -// /** */ -// in1: string; -// /** */ -// in2: string; -// /** */ -// out: string; -// /** if true, include rows even if all 3 columns are blank */ -// emptyOut: string; -// }; - -/** - * Very small CSV parser for a single line: - * - supports commas - * - supports double-quoted fields (with "" escaping) - * - does NOT support embedded newlines inside quotes (most exports won’t have them) - * - * @param line - */ -function parseCsvLine(line: string): string[] { - const out: string[] = []; - let cur = ''; - let inQuotes = false; - - for (let i = 0; i < line.length; i++) { - const ch = line[i]; - - if (inQuotes) { - if (ch === '"') { - // escaped quote - if (i + 1 < line.length && line[i + 1] === '"') { - cur += '"'; - i++; - } else { - inQuotes = false; - } - } else { - cur += ch; - } - } else if (ch === ',') { - out.push(cur); - cur = ''; - } else if (ch === '"') { - inQuotes = true; - } else { - cur += ch; - } - } - out.push(cur); - return out; -} - -/** - * CSV escape for output - * - * @param v - */ -function csvEscape(v: string): string { - const s = v ?? ''; - if (/[",\n\r]/.test(s)) { - return `"${s.replace(/"/g, '""')}"`; - } - return s; -} - -/** - * Normalize headers: trim, lowercase, remove wrapping quotes, - * and treat "(email_withheld)" as "email_withheld". - * - * @param h - */ -function normalizeHeader(h: string): string { - const raw = (h ?? '').trim().replace(/^"|"$/g, ''); - const lowered = raw.toLowerCase(); - // strip surrounding parentheses - const parenStripped = lowered.replace(/^\((.*)\)$/, '$1'); - return parenStripped; -} - -/** - * - */ -type HeaderIndex = Record; - -/** - * - * @param headerLine - */ -function buildHeaderIndex(headerLine: string): HeaderIndex { - const headers = parseCsvLine(headerLine).map(normalizeHeader); - const idx: HeaderIndex = {}; - headers.forEach((h, i) => { - if (h) idx[h] = i; - }); - return idx; -} - -/** - * - * @param cols - * @param idx - * @param name - */ -function getField(cols: string[], idx: HeaderIndex, name: string): string { - const i = idx[name]; - if (i === undefined) return ''; - return (cols[i] ?? '').trim(); -} - -/** - * - * @param filePath - * @param writer - * @param emptyWriter - */ -async function processFile( - filePath: string, - writer: fs.WriteStream, - emptyWriter: fs.WriteStream, -): Promise { - const rl = readline.createInterface({ - input: fs.createReadStream(filePath), - crlfDelay: Infinity, - }); - - let headerIdx: HeaderIndex | null = null; - let rowCount = 0; - let dataRowIndex = 0; - let outCount = 0; - - for await (const line of rl) { - if (rowCount === 0) { - headerIdx = buildHeaderIndex(line); - rowCount += 1; - continue; - } - - rowCount += 1; - dataRowIndex += 1; - - if (!headerIdx) continue; - - // keep truly blank lines out of both outputs - if (!line.trim()) continue; - - const cols = parseCsvLine(line); - - const personID = getField(cols, headerIdx, 'personid'); - const transcendID = getField(cols, headerIdx, 'transcendid'); - const emailWithheld = - getField(cols, headerIdx, 'email_withheld') || - getField(cols, headerIdx, 'email') || - getField(cols, headerIdx, 'emailwitheld'); - - const allEmpty = !personID && !transcendID && !emailWithheld; - - if (allEmpty) { - emptyWriter.write( - `${csvEscape(path.basename(filePath))},${dataRowIndex},${csvEscape( - line, - )}\n`, - ); - writer.write( - `${csvEscape(personID)},${csvEscape(transcendID)},${csvEscape( - emailWithheld, - )}\n`, - ); - continue; - } - - writer.write( - `${csvEscape(personID)},${csvEscape(transcendID)},${csvEscape( - emailWithheld, - )}\n`, - ); - outCount += 1; - } - - return outCount; -} - -/** - * - */ -async function main() { - const opts = { - in1: resolve('./working/costco/concerns/concern_1.csv'), - in2: resolve('./working/costco/concerns/concern_4.csv'), - out: resolve('./working/costco/concerns/out.csv'), - emptyOut: resolve('./working/costco/concerns/empty_rows.csv'), - }; - - const in1Abs = path.resolve(opts.in1); - const in2Abs = path.resolve(opts.in2); - const outAbs = path.resolve(opts.out); - - const writer = fs.createWriteStream(outAbs, { encoding: 'utf8' }); - const emptyWriter = fs.createWriteStream(opts.emptyOut, { - encoding: 'utf8', - }); - - // header - writer.write('personID,transcendID,email_withheld\n'); - emptyWriter.write('source_file,row_index,raw_row\n'); - - let total = 0; - total += await processFile(in1Abs, writer, emptyWriter); - total += await processFile(in2Abs, writer, emptyWriter); - - await new Promise((resolve, reject) => { - writer.end(() => resolve()); - writer.on('error', reject); - }); - - console.error(`Wrote ${total} rows -> ${outAbs}`); -} - -main().catch((err) => { - console.error(err?.stack ?? String(err)); - process.exit(1); -}); -/* eslint-enable jsdoc/require-description,jsdoc/require-returns,jsdoc/require-param-description,no-continue */ From cbc1acaac6c1f9f7aa2655b35eba12e076345a05 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 22 Feb 2026 20:34:10 -0800 Subject: [PATCH 66/72] bluebird --- README.md | 83 ++++++++++--------- .../consent/pull-consent-metrics/impl.ts | 4 +- .../upload/batchUploader.ts | 2 - .../upload/buildInteractiveUploadPlan.ts | 1 + .../interactivePreferenceUploaderFromPlan.ts | 9 +- .../upload/tests/batchUploader.test.ts | 12 +-- .../upload/transform/buildPendingUpdates.ts | 16 ++++ src/lib/graphql/syncProcessingActivities.ts | 4 +- src/lib/graphql/syncProcessingPurposes.ts | 4 +- src/lib/graphql/syncPromptGroups.ts | 4 +- src/lib/graphql/syncPromptPartials.ts | 4 +- .../parsePreferenceAndPurposeValuesFromCsv.ts | 4 +- src/lib/requests/approvePrivacyRequests.ts | 4 +- src/lib/requests/bulkRestartRequests.ts | 4 +- src/lib/requests/bulkRetryEnrichers.ts | 4 +- src/lib/requests/cancelPrivacyRequests.ts | 4 +- .../requests/downloadPrivacyRequestFiles.ts | 4 +- .../getFileMetadataForPrivacyRequests.ts | 4 +- src/lib/requests/markSilentPrivacyRequests.ts | 4 +- .../notifyPrivacyRequestsAdditionalTime.ts | 4 +- src/lib/requests/pullPrivacyRequests.ts | 4 +- src/lib/requests/retryRequestDataSilos.ts | 4 +- src/lib/requests/skipPreflightJobs.ts | 4 +- src/lib/requests/skipRequestDataSilos.ts | 4 +- src/lib/requests/streamPrivacyRequestFiles.ts | 4 +- .../requests/uploadPrivacyRequestsFromCsv.ts | 4 +- 26 files changed, 132 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index e81fb2f0..284b5e83 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --origin transcend request approve \ --auth="$TRANSCEND_API_KEY" \ --actions=SALE_OPT_OUT \ - --silentModeBefore=2024-05-03T00:00:00.000Z + --silentModeBefore=2025-05-03T00:00:00.000Z ``` **Increase the concurrency (defaults to 50)** @@ -245,8 +245,8 @@ transcend request approve --auth="$TRANSCEND_API_KEY" --actions=ERASURE --concur transcend request approve \ --auth="$TRANSCEND_API_KEY" \ --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z + --createdAtBefore=2025-05-03T00:00:00.000Z \ + --createdAtAfter=2025-04-03T00:00:00.000Z ``` ### `transcend request upload` @@ -453,8 +453,8 @@ transcend request download-files --auth="$TRANSCEND_API_KEY" --concurrency=100 ```sh transcend request download-files \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z + --createdAtBefore=2025-05-03T00:00:00.000Z \ + --createdAtAfter=2025-04-03T00:00:00.000Z ``` **Download specific requests** @@ -520,7 +520,7 @@ transcend request cancel --auth="$TRANSCEND_API_KEY" --actions=ERASURE --cancell transcend request cancel \ --auth="$TRANSCEND_API_KEY" \ --actions=SALE_OPT_OUT \ - --silentModeBefore=2024-05-03T00:00:00.000Z + --silentModeBefore=2025-05-03T00:00:00.000Z ``` **Cancel all open SALE_OPT_OUT, within a specific time frame** @@ -529,8 +529,8 @@ transcend request cancel \ transcend request cancel \ --auth="$TRANSCEND_API_KEY" \ --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z + --createdAtBefore=2025-05-03T00:00:00.000Z \ + --createdAtAfter=2025-04-03T00:00:00.000Z ``` **Increase the concurrency (defaults to 50)** @@ -644,7 +644,7 @@ transcend request restart \ --auth="$TRANSCEND_API_KEY" \ --statuses=COMPILING,ENRICHING \ --actions=ACCESS,ERASURE \ - --createdAt=2024-05-11T00:00:00.000Z + --createdAt=2025-05-11T00:00:00.000Z ``` **Restart requests and place everything in silent mode submitted before a certain date** @@ -654,7 +654,7 @@ transcend request restart \ --auth="$TRANSCEND_API_KEY" \ --statuses=COMPILING,ENRICHING \ --actions=ACCESS,ERASURE \ - --silentModeBefore=2024-12-05T00:00:00.000Z + --silentModeBefore=2025-12-05T00:00:00.000Z ``` **Restart requests within a specific timeframe** @@ -664,8 +664,8 @@ transcend request restart \ --auth="$TRANSCEND_API_KEY" \ --statuses=COMPILING,ENRICHING \ --actions=ACCESS,ERASURE \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z + --createdAtBefore=2025-04-05T00:00:00.000Z \ + --createdAtAfter=2025-02-21T00:00:00.000Z ``` **Send email receipts to the restarted requests** @@ -726,7 +726,7 @@ FLAGS **Notify all request types that were made before 01/01/2024** ```sh -transcend request notify-additional-time --auth="$TRANSCEND_API_KEY" --createdAtBefore=2024-01-01T00:00:00.000Z +transcend request notify-additional-time --auth="$TRANSCEND_API_KEY" --createdAtBefore=2025-01-01T00:00:00.000Z ``` **Notify all request types that were made during a date range** @@ -734,8 +734,8 @@ transcend request notify-additional-time --auth="$TRANSCEND_API_KEY" --createdAt ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ - --createdAtAfter=2024-12-15T00:00:00.000Z + --createdAtBefore=2025-01-01T00:00:00.000Z \ + --createdAtAfter=2025-12-15T00:00:00.000Z ``` **Notify certain request types** @@ -743,7 +743,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --actions=SALE_OPT_OUT,ERASURE ``` @@ -752,7 +752,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --transcendUrl=https://api.us.transcend.io ``` @@ -761,7 +761,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --requestIds=c3ae78c9-2768-4666-991a-d2f729503337,342e4bd1-64ea-4af0-a4ad-704b5a07cfe4 ``` @@ -770,7 +770,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --daysLeft=3 ``` @@ -779,7 +779,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --days=30 ``` @@ -788,7 +788,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --emailTemplate="Custom Email Template" ``` @@ -797,7 +797,7 @@ transcend request notify-additional-time \ ```sh transcend request notify-additional-time \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-01-01T00:00:00.000Z \ + --createdAtBefore=2025-01-01T00:00:00.000Z \ --concurrency=500 ``` @@ -861,8 +861,8 @@ transcend request mark-silent \ transcend request mark-silent \ --auth="$TRANSCEND_API_KEY" \ --actions=SALE_OPT_OUT \ - --createdAtBefore=2024-05-03T00:00:00.000Z \ - --createdAtAfter=2024-04-03T00:00:00.000Z + --createdAtBefore=2025-05-03T00:00:00.000Z \ + --createdAtAfter=2025-04-03T00:00:00.000Z ``` **Increase the concurrency (defaults to 50)** @@ -937,8 +937,8 @@ transcend request enricher-restart \ transcend request enricher-restart \ --auth="$TRANSCEND_API_KEY" \ --enricherId=3be5e898-fea9-4614-84de-88cd5265c557 \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z + --createdAtBefore=2025-04-05T00:00:00.000Z \ + --createdAtAfter=2025-02-21T00:00:00.000Z ``` **Restart requests that are in an error state** @@ -1061,8 +1061,8 @@ transcend request export --auth="$TRANSCEND_API_KEY" --showTests=false ```sh transcend request export \ --auth="$TRANSCEND_API_KEY" \ - --createdAtBefore=2024-04-05T00:00:00.000Z \ - --createdAtAfter=2024-02-21T00:00:00.000Z + --createdAtBefore=2025-04-05T00:00:00.000Z \ + --createdAtAfter=2025-02-21T00:00:00.000Z ``` **Write to a specific file location** @@ -1759,7 +1759,7 @@ FLAGS **Pull consent manager metrics for a Transcend account** ```sh -transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z +transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2025-01-01T00:00:00.000Z ``` **Specifying the backend URL, needed for US hosted backend infrastructure** @@ -1767,7 +1767,7 @@ transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024- ```sh transcend consent pull-consent-metrics \ --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ + --start=2025-01-01T00:00:00.000Z \ --transcendUrl=https://api.us.transcend.io ``` @@ -1776,8 +1776,8 @@ transcend consent pull-consent-metrics \ ```sh transcend consent pull-consent-metrics \ --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ - --end=2024-03-01T00:00:00.000Z + --start=2025-01-01T00:00:00.000Z \ + --end=2025-03-01T00:00:00.000Z ``` **Save to an explicit folder** @@ -1785,15 +1785,15 @@ transcend consent pull-consent-metrics \ ```sh transcend consent pull-consent-metrics \ --auth="$TRANSCEND_API_KEY" \ - --start=2024-01-01T00:00:00.000Z \ - --end=2024-03-01T00:00:00.000Z \ + --start=2025-01-01T00:00:00.000Z \ + --end=2025-03-01T00:00:00.000Z \ --folder=./my-folder/ ``` **Bin data hourly vs daily** ```sh -transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2024-01-01T00:00:00.000Z --bin=1h +transcend consent pull-consent-metrics --auth="$TRANSCEND_API_KEY" --start=2025-01-01T00:00:00.000Z --bin=1h ``` ### `transcend consent pull-consent-preferences` @@ -1849,7 +1849,7 @@ transcend consent pull-consent-preferences \ transcend consent pull-consent-preferences \ --auth="$TRANSCEND_API_KEY" \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --timestampBefore=2024-04-03T00:00:00.000Z + --timestampBefore=2025-04-03T00:00:00.000Z ``` **Filter by consent collection time (timestampAfter)** @@ -1858,7 +1858,7 @@ transcend consent pull-consent-preferences \ transcend consent pull-consent-preferences \ --auth="$TRANSCEND_API_KEY" \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --timestampAfter=2024-04-03T00:00:00.000Z + --timestampAfter=2025-04-03T00:00:00.000Z ``` **Filter by last update time (system.updatedAt window)** @@ -1867,8 +1867,8 @@ transcend consent pull-consent-preferences \ transcend consent pull-consent-preferences \ --auth="$TRANSCEND_API_KEY" \ --partition=4d1c5daa-90b7-4d18-aa40-f86a43d2c726 \ - --updatedAfter=2024-08-26T00:00:00.000Z \ - --updatedBefore=2024-08-27T00:00:00.000Z + --updatedAfter=2025-08-26T00:00:00.000Z \ + --updatedBefore=2025-08-27T00:00:00.000Z ``` **Filter specific users by identifiers (name:value). Default name=email if omitted.** @@ -2179,7 +2179,7 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [--viewerMode] + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [--skipMetadata] [--viewerMode] transcend consent upload-preferences --help Upload preference management data to your Preference Store. @@ -2197,7 +2197,7 @@ Parallel preference uploader (Node 22+ ESM/TS) by the presence of a CLI flag ('--as-child'). FLAGS - --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings" + --auth The Transcend API key. Requires scopes: "Modify User Stored Preferences", "View Managed Consent Database Admin API", "View Preference Store Settings", "View Identity Verification Settings" --partition The partition key to download consent preferences to [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] @@ -2222,6 +2222,7 @@ FLAGS --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. + [--skipMetadata] Whether to skip uploading metadata fields. Use this for subsequent batch uploads to avoid replacing existing metadata. [default = false] [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] -h --help Print help information and exit ``` diff --git a/src/commands/consent/pull-consent-metrics/impl.ts b/src/commands/consent/pull-consent-metrics/impl.ts index 015a5615..4892986c 100644 --- a/src/commands/consent/pull-consent-metrics/impl.ts +++ b/src/commands/consent/pull-consent-metrics/impl.ts @@ -1,7 +1,7 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; import colors from 'colors'; -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { join } from 'node:path'; import fs, { existsSync, mkdirSync } from 'node:fs'; import { @@ -14,6 +14,8 @@ import { pullConsentManagerMetrics } from '../../../lib/consent-manager'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { writeCsv } from '../../../lib/helpers'; +const { map, mapSeries } = Bluebird; + export interface PullConsentMetricsCommandFlags { auth: string; start: Date; diff --git a/src/commands/consent/upload-preferences/upload/batchUploader.ts b/src/commands/consent/upload-preferences/upload/batchUploader.ts index 249fbd18..88ba735a 100644 --- a/src/commands/consent/upload-preferences/upload/batchUploader.ts +++ b/src/commands/consent/upload-preferences/upload/batchUploader.ts @@ -14,8 +14,6 @@ type Entry = [string, PreferenceUpdateItem]; export interface BatchUploadPreferenceOptions { /** When true - don't trigger workflow runs */ skipWorkflowTriggers: boolean; - /** Always trigger a workflow run regardless of whether a purpose changed */ - forceTriggerWorkflows: boolean; } export interface BatchUploaderDeps { diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index 85aefd66..b678420e 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -177,6 +177,7 @@ export async function buildInteractiveUploadPreferencePlan({ timestampColumn: schema.getTimestampColumn(), columnToPurposeName: schema.getColumnToPurposeName(), columnToIdentifier: schema.getColumnToIdentifier(), + columnToMetadata: schema.getColumnToMetadata(), }, }; } diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 1a3a9868..1d2446f4 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -42,6 +42,7 @@ export async function interactivePreferenceUploaderFromPlan( skipWorkflowTriggers = false, skipConflictUpdates = false, forceTriggerWorkflows = false, + skipMetadata = false, uploadLogInterval = 1_000, maxChunkSize = 25, uploadConcurrency = 20, @@ -62,6 +63,8 @@ export async function interactivePreferenceUploaderFromPlan( skipConflictUpdates?: boolean; /** Force triggering workflows for each update (use sparingly) */ forceTriggerWorkflows?: boolean; + /** Skip uploading metadata fields to avoid replacing existing metadata */ + skipMetadata?: boolean; /** Log/persist cadence for progress updates */ uploadLogInterval?: number; /** Max records in a single batch PUT to v1/preferences */ @@ -83,12 +86,15 @@ export async function interactivePreferenceUploaderFromPlan( timestampColumn: schema.timestampColumn, columnToPurposeName: schema.columnToPurposeName, columnToIdentifier: schema.columnToIdentifier, + // Skip metadata when flag is set to avoid replacing existing metadata + columnToMetadata: skipMetadata ? undefined : schema.columnToMetadata, preferenceTopics, purposes, partition, workflowAttrs: parsedAttributes, isSilent, skipWorkflowTriggers, + forceTriggerWorkflows, }); // Seed pending uploads into receipts (first 10 expanded to keep file size small) @@ -317,13 +323,12 @@ export async function interactivePreferenceUploaderFromPlan( json: { records: updates, skipWorkflowTriggers: opts.skipWorkflowTriggers, - forceTriggerWorkflows: opts.forceTriggerWorkflows, }, }) .json(); }, retryPolicy, - options: { skipWorkflowTriggers, forceTriggerWorkflows }, + options: { skipWorkflowTriggers }, isRetryableStatus: (s) => // eslint-disable-next-line @typescript-eslint/no-explicit-any !!s && RETRYABLE_BATCH_STATUSES.has(s as any), diff --git a/src/commands/consent/upload-preferences/upload/tests/batchUploader.test.ts b/src/commands/consent/upload-preferences/upload/tests/batchUploader.test.ts index 16aebfc3..c995ad2c 100644 --- a/src/commands/consent/upload-preferences/upload/tests/batchUploader.test.ts +++ b/src/commands/consent/upload-preferences/upload/tests/batchUploader.test.ts @@ -87,7 +87,7 @@ describe('uploadChunkWithSplit', () => { const deps: BatchUploaderDeps = { putBatch, retryPolicy: { maxAttempts: 3, delayMs: 10, shouldRetry: () => false }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn(() => false), }; @@ -136,7 +136,7 @@ describe('uploadChunkWithSplit', () => { delayMs: 1, shouldRetry: () => true, // retryable by default }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn((s?: number) => s === 503), }; @@ -177,7 +177,7 @@ describe('uploadChunkWithSplit', () => { const deps: BatchUploaderDeps = { putBatch, retryPolicy: { maxAttempts: 2, delayMs: 1, shouldRetry: () => true }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn((s?: number) => s === 429), }; @@ -226,7 +226,7 @@ describe('uploadChunkWithSplit', () => { const deps: BatchUploaderDeps = { putBatch, retryPolicy: { maxAttempts: 1, delayMs: 1, shouldRetry: () => false }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn(() => false), }; @@ -266,7 +266,7 @@ describe('uploadChunkWithSplit', () => { const deps: BatchUploaderDeps = { putBatch, retryPolicy: { maxAttempts: 1, delayMs: 1, shouldRetry: () => false }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn(() => false), }; @@ -301,7 +301,7 @@ describe('uploadChunkWithSplit', () => { const deps: BatchUploaderDeps = { putBatch, retryPolicy: { maxAttempts: 2, delayMs: 1, shouldRetry: () => true }, - options: { skipWorkflowTriggers: false, forceTriggerWorkflows: false }, + options: { skipWorkflowTriggers: false }, isRetryableStatus: vi.fn(() => false), // not retryable by status, but soft-rate-limit triggers retry anyway }; diff --git a/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts b/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts index 39d6939e..64084bf3 100644 --- a/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts +++ b/src/commands/consent/upload-preferences/upload/transform/buildPendingUpdates.ts @@ -13,8 +13,10 @@ import type { import { getPreferenceIdentifiersFromRow, getPreferenceUpdatesFromRow, + getPreferenceMetadataFromRow, NONE_PREFERENCE_MAP, type ColumnIdentifierMap, + type ColumnMetadataMap, type ColumnPurposeMap, type PendingSafePreferenceUpdates, type PendingWithConflictPreferenceUpdates, @@ -33,6 +35,8 @@ export interface BuildPendingParams { columnToPurposeName: ColumnPurposeMap; /** CSV column -> identifier mapping */ columnToIdentifier: ColumnIdentifierMap; + /** CSV column -> metadata key mapping (optional) */ + columnToMetadata?: ColumnMetadataMap; /** Full set of preference topics for resolving row → preference values */ preferenceTopics: PreferenceTopic[]; /** Full set of purposes for resolving slugs/trackingTypes */ @@ -45,6 +49,8 @@ export interface BuildPendingParams { isSilent: boolean; /** If true, skip triggering workflows downstream */ skipWorkflowTriggers: boolean; + /** If true, force trigger workflows even if preferences haven't changed */ + forceTriggerWorkflows: boolean; } /** @@ -65,12 +71,14 @@ export function buildPendingUpdates( timestampColumn, columnToPurposeName, columnToIdentifier, + columnToMetadata, preferenceTopics, purposes, partition, workflowAttrs, isSilent, skipWorkflowTriggers, + forceTriggerWorkflows, } = params; // If conflicts are to be included, normalize the shape to match `safe` rows. @@ -108,6 +116,11 @@ export function buildPendingUpdates( columnToIdentifier, }); + // Resolve metadata from mapped columns (if any) + const metadata = columnToMetadata + ? getPreferenceMetadataFromRow({ row, columnToMetadata }) + : undefined; + out[userId] = { identifiers, partition, @@ -119,8 +132,11 @@ export function buildPendingUpdates( attributes: workflowAttrs, isSilent, skipWorkflowTrigger: skipWorkflowTriggers, + forceTriggerWorkflow: forceTriggerWorkflows, }, })), + // Only include metadata if there are values + ...(metadata && metadata.length > 0 ? { metadata } : {}), }; } diff --git a/src/lib/graphql/syncProcessingActivities.ts b/src/lib/graphql/syncProcessingActivities.ts index 68a7d85f..1c2f8b34 100644 --- a/src/lib/graphql/syncProcessingActivities.ts +++ b/src/lib/graphql/syncProcessingActivities.ts @@ -1,6 +1,6 @@ import { ProcessingActivityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_PROCESSING_ACTIVITIES, CREATE_PROCESSING_ACTIVITY, @@ -14,6 +14,8 @@ import { ProcessingActivity, } from './fetchAllProcessingActivities'; +const { mapSeries } = Bluebird; + /** * Create a new processing activity, setting only title and description * diff --git a/src/lib/graphql/syncProcessingPurposes.ts b/src/lib/graphql/syncProcessingPurposes.ts index 89805f71..c9171dfe 100644 --- a/src/lib/graphql/syncProcessingPurposes.ts +++ b/src/lib/graphql/syncProcessingPurposes.ts @@ -1,6 +1,6 @@ import { ProcessingPurposeInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_PROCESSING_PURPOSE_SUB_CATEGORIES, CREATE_PROCESSING_PURPOSE_SUB_CATEGORY, @@ -14,6 +14,8 @@ import { ProcessingPurposeSubCategory, } from './fetchAllProcessingPurposes'; +const { mapSeries } = Bluebird; + /** * Input to create a new processing purpose * diff --git a/src/lib/graphql/syncPromptGroups.ts b/src/lib/graphql/syncPromptGroups.ts index b717a326..bb1a3c0f 100644 --- a/src/lib/graphql/syncPromptGroups.ts +++ b/src/lib/graphql/syncPromptGroups.ts @@ -3,12 +3,14 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_GROUPS, CREATE_PROMPT_GROUP } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchAllPromptGroups } from './fetchPromptGroups'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; import { fetchAllPrompts } from './fetchPrompts'; +const { map } = Bluebird; + export interface EditPromptGroupInput { /** Title of prompt group */ title: string; diff --git a/src/lib/graphql/syncPromptPartials.ts b/src/lib/graphql/syncPromptPartials.ts index 98ee67a6..21d5211a 100644 --- a/src/lib/graphql/syncPromptPartials.ts +++ b/src/lib/graphql/syncPromptPartials.ts @@ -3,11 +3,13 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPT_PARTIALS, CREATE_PROMPT_PARTIAL } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchAllPromptPartials } from './fetchPromptPartials'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; +const { map } = Bluebird; + /** * Create a new prompt partial * diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index a9bc13a8..9fdca3fd 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -3,12 +3,14 @@ import colors from 'colors'; import inquirer from 'inquirer'; import { FileFormatState } from './codecs'; import { logger } from '../../logger'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { PreferenceTopic } from '../graphql'; import { PreferenceTopicType } from '@transcend-io/privacy-types'; import { splitCsvToList } from '../requests'; import type { PersistedState } from '@transcend-io/persisted-state'; +const { mapSeries } = Bluebird; + /** * Parse out the purpose.enabled and preference values from a CSV file * diff --git a/src/lib/requests/approvePrivacyRequests.ts b/src/lib/requests/approvePrivacyRequests.ts index added1e8..60d62bdc 100644 --- a/src/lib/requests/approvePrivacyRequests.ts +++ b/src/lib/requests/approvePrivacyRequests.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { @@ -16,6 +16,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Approve a set of privacy requests * diff --git a/src/lib/requests/bulkRestartRequests.ts b/src/lib/requests/bulkRestartRequests.ts index 04704ee6..2252a8ca 100644 --- a/src/lib/requests/bulkRestartRequests.ts +++ b/src/lib/requests/bulkRestartRequests.ts @@ -1,6 +1,6 @@ import { PersistedState } from '@transcend-io/persisted-state'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import cliProgress from 'cli-progress'; import colors from 'colors'; import * as t from 'io-ts'; @@ -18,6 +18,8 @@ import { SuccessfulRequest } from './constants'; import { extractClientError } from './extractClientError'; import { restartPrivacyRequest } from './restartPrivacyRequest'; +const { map } = Bluebird; + /** Minimal state we need to keep a list of requests */ const ErrorRequest = t.intersection([ SuccessfulRequest, diff --git a/src/lib/requests/bulkRetryEnrichers.ts b/src/lib/requests/bulkRetryEnrichers.ts index d1be5fd3..ac6b77f8 100644 --- a/src/lib/requests/bulkRetryEnrichers.ts +++ b/src/lib/requests/bulkRetryEnrichers.ts @@ -3,7 +3,7 @@ import { RequestEnricherStatus, RequestStatus, } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import cliProgress from 'cli-progress'; import colors from 'colors'; import { difference } from 'lodash-es'; @@ -16,6 +16,8 @@ import { } from '../graphql'; import { logger } from '../../logger'; +const { map } = Bluebird; + /** * Restart a bunch of request enrichers * diff --git a/src/lib/requests/cancelPrivacyRequests.ts b/src/lib/requests/cancelPrivacyRequests.ts index 198afedb..1534f413 100644 --- a/src/lib/requests/cancelPrivacyRequests.ts +++ b/src/lib/requests/cancelPrivacyRequests.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -14,6 +14,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Cancel a set of privacy requests * diff --git a/src/lib/requests/downloadPrivacyRequestFiles.ts b/src/lib/requests/downloadPrivacyRequestFiles.ts index 11d2d520..4cc902a9 100644 --- a/src/lib/requests/downloadPrivacyRequestFiles.ts +++ b/src/lib/requests/downloadPrivacyRequestFiles.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import colors from 'colors'; @@ -16,6 +16,8 @@ import { DEFAULT_TRANSCEND_API } from '../../constants'; import { getFileMetadataForPrivacyRequests } from './getFileMetadataForPrivacyRequests'; import { streamPrivacyRequestFiles } from './streamPrivacyRequestFiles'; +const { map } = Bluebird; + /** * Download a set of privacy requests to disk * diff --git a/src/lib/requests/getFileMetadataForPrivacyRequests.ts b/src/lib/requests/getFileMetadataForPrivacyRequests.ts index 53a255ad..0ab25c89 100644 --- a/src/lib/requests/getFileMetadataForPrivacyRequests.ts +++ b/src/lib/requests/getFileMetadataForPrivacyRequests.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import cliProgress from 'cli-progress'; @@ -9,6 +9,8 @@ import { decodeCodec, valuesOf } from '@transcend-io/type-utils'; import { logger } from '../../logger'; import { TableEncryptionType } from '@transcend-io/privacy-types'; +const { map } = Bluebird; + export const IntlMessage = t.type({ /** The message key */ defaultMessage: t.string, diff --git a/src/lib/requests/markSilentPrivacyRequests.ts b/src/lib/requests/markSilentPrivacyRequests.ts index 8b56d748..6ccb6a4e 100644 --- a/src/lib/requests/markSilentPrivacyRequests.ts +++ b/src/lib/requests/markSilentPrivacyRequests.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -11,6 +11,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Mark a set of privacy requests to be in silent mode * diff --git a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts index 861be0e1..29c92101 100644 --- a/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts +++ b/src/lib/requests/notifyPrivacyRequestsAdditionalTime.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction } from '@transcend-io/privacy-types'; @@ -12,6 +12,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Mark a set of privacy requests to be in silent mode. * Note requests in silent mode are ignored diff --git a/src/lib/requests/pullPrivacyRequests.ts b/src/lib/requests/pullPrivacyRequests.ts index 0865f60d..2f317a40 100644 --- a/src/lib/requests/pullPrivacyRequests.ts +++ b/src/lib/requests/pullPrivacyRequests.ts @@ -1,5 +1,5 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { groupBy } from 'lodash-es'; @@ -14,6 +14,8 @@ import { } from '../graphql'; import { logger } from '../../logger'; +const { map } = Bluebird; + export interface ExportedPrivacyRequest extends PrivacyRequest { /** Request identifiers */ requestIdentifiers: RequestIdentifier[]; diff --git a/src/lib/requests/retryRequestDataSilos.ts b/src/lib/requests/retryRequestDataSilos.ts index c6132a94..3b1336f5 100644 --- a/src/lib/requests/retryRequestDataSilos.ts +++ b/src/lib/requests/retryRequestDataSilos.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -12,6 +12,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Retry a set of RequestDataSilos * diff --git a/src/lib/requests/skipPreflightJobs.ts b/src/lib/requests/skipPreflightJobs.ts index 59516f75..ebaed16a 100644 --- a/src/lib/requests/skipPreflightJobs.ts +++ b/src/lib/requests/skipPreflightJobs.ts @@ -1,4 +1,4 @@ -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { @@ -15,6 +15,8 @@ import { } from '@transcend-io/privacy-types'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { mapSeries, map } = Bluebird; + /** * Given an enricher ID, mark all open request enrichers as skipped * diff --git a/src/lib/requests/skipRequestDataSilos.ts b/src/lib/requests/skipRequestDataSilos.ts index cdd56209..5691cdb5 100644 --- a/src/lib/requests/skipRequestDataSilos.ts +++ b/src/lib/requests/skipRequestDataSilos.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { @@ -12,6 +12,8 @@ import cliProgress from 'cli-progress'; import { RequestStatus } from '@transcend-io/privacy-types'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Given a data silo ID, mark all open request data silos as skipped * diff --git a/src/lib/requests/streamPrivacyRequestFiles.ts b/src/lib/requests/streamPrivacyRequestFiles.ts index 4f94ba22..fdbf7486 100644 --- a/src/lib/requests/streamPrivacyRequestFiles.ts +++ b/src/lib/requests/streamPrivacyRequestFiles.ts @@ -1,9 +1,11 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { RequestFileMetadata } from './getFileMetadataForPrivacyRequests'; import type { Got } from 'got'; import { logger } from '../../logger'; +const { map } = Bluebird; + /** * This function will take in a set of file metadata for privacy requests * call the Transcend API to stream the file metadata for these requests diff --git a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts index 550b0131..dcc91c0b 100644 --- a/src/lib/requests/uploadPrivacyRequestsFromCsv.ts +++ b/src/lib/requests/uploadPrivacyRequestsFromCsv.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import * as t from 'io-ts'; import { uniq } from 'lodash-es'; import cliProgress from 'cli-progress'; @@ -25,6 +25,8 @@ import { filterRows } from './filterRows'; import { extractClientError } from './extractClientError'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Upload a set of privacy requests from CSV * From 7aa12b6d9fa6bd9d311696245cfa095bdc5fbe86 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 22 Feb 2026 20:36:49 -0800 Subject: [PATCH 67/72] bluebird --- src/commands/consent/update-consent-manager/impl.ts | 4 +++- .../consent/upload-preferences/buildTaskOptions.ts | 3 +++ src/commands/consent/upload-preferences/command.ts | 7 +++++++ src/commands/consent/upload-preferences/impl.ts | 1 + src/commands/consent/upload-preferences/schemaState.ts | 5 +++++ src/commands/request/cron/pull-profiles/impl.ts | 4 +++- src/lib/api-keys/generateCrossAccountApiKeys.ts | 4 +++- src/lib/consent-manager/buildXdiSyncEndpoint.ts | 4 +++- .../consent-manager/updateConsentManagerVersionToLatest.ts | 4 +++- src/lib/consent-manager/uploadConsents.ts | 4 +++- src/lib/cron/markRequestDataSiloIdsCompleted.ts | 4 +++- .../cron/pullChunkedCustomSiloOutstandingIdentifiers.ts | 4 +++- src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts | 4 +++- src/lib/data-inventory/pullAllDatapoints.ts | 4 +++- src/lib/graphql/createPreferenceAccessTokens.ts | 4 +++- src/lib/graphql/fetchDataSubjects.ts | 4 +++- src/lib/graphql/fetchIdentifiers.ts | 4 +++- src/lib/graphql/syncActionItemCollections.ts | 4 +++- src/lib/graphql/syncActionItems.ts | 4 +++- src/lib/graphql/syncAgentFiles.ts | 4 +++- src/lib/graphql/syncAgentFunctions.ts | 4 +++- src/lib/graphql/syncAgents.ts | 4 +++- src/lib/graphql/syncAttribute.ts | 4 +++- src/lib/graphql/syncBusinessEntities.ts | 4 +++- src/lib/graphql/syncCodePackages.ts | 4 +++- src/lib/graphql/syncConfigurationToTranscend.ts | 4 +++- src/lib/graphql/syncConsentManager.ts | 4 +++- src/lib/graphql/syncCookies.ts | 4 +++- src/lib/graphql/syncDataCategories.ts | 4 +++- src/lib/graphql/syncDataFlows.ts | 4 +++- src/lib/graphql/syncDataSilos.ts | 4 +++- src/lib/graphql/syncIntlMessages.ts | 4 +++- src/lib/graphql/syncPartitions.ts | 4 +++- src/lib/graphql/syncPolicies.ts | 4 +++- src/lib/graphql/syncPrompts.ts | 4 +++- src/lib/graphql/syncRepositories.ts | 4 +++- src/lib/graphql/syncSoftwareDevelopmentKits.ts | 4 +++- src/lib/graphql/syncTeams.ts | 4 +++- src/lib/graphql/syncVendors.ts | 4 +++- src/lib/graphql/uploadSiloDiscoveryResults.ts | 4 +++- .../pullManualEnrichmentIdentifiersToCsv.ts | 4 +++- .../pushManualEnrichmentIdentifiersFromCsv.ts | 4 +++- .../helpers/syncOneTrustAssessmentsFromOneTrust.ts | 4 +++- src/lib/preference-management/index.ts | 1 + .../parsePreferenceIdentifiersFromCsv.ts | 4 +++- src/lib/requests/removeUnverifiedRequestIdentifiers.ts | 4 +++- 46 files changed, 140 insertions(+), 41 deletions(-) diff --git a/src/commands/consent/update-consent-manager/impl.ts b/src/commands/consent/update-consent-manager/impl.ts index 705fc629..c283b536 100644 --- a/src/commands/consent/update-consent-manager/impl.ts +++ b/src/commands/consent/update-consent-manager/impl.ts @@ -1,13 +1,15 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { ConsentBundleType } from '@transcend-io/privacy-types'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { logger } from '../../../logger'; import { updateConsentManagerVersionToLatest } from '../../../lib/consent-manager'; import { validateTranscendAuth } from '../../../lib/api-keys'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; +const { mapSeries } = Bluebird; + export interface UpdateConsentManagerCommandFlags { auth: string; bundleTypes: ConsentBundleType[]; diff --git a/src/commands/consent/upload-preferences/buildTaskOptions.ts b/src/commands/consent/upload-preferences/buildTaskOptions.ts index 5819acee..b601723d 100644 --- a/src/commands/consent/upload-preferences/buildTaskOptions.ts +++ b/src/commands/consent/upload-preferences/buildTaskOptions.ts @@ -25,6 +25,7 @@ export type TaskCommonOpts = Pick< | 'allowedIdentifierNames' | 'identifierColumns' | 'columnsToIgnore' + | 'skipMetadata' > & { schemaFile: string; receiptsFolder: string; @@ -65,6 +66,7 @@ export function buildCommonOpts( maxRecordsToReceipt, uploadLogInterval, columnsToIgnore = [], + skipMetadata = false, } = flags; return { @@ -91,5 +93,6 @@ export function buildCommonOpts( maxRecordsToReceipt, uploadLogInterval, columnsToIgnore, + skipMetadata, }; } diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index 41423d4a..c0ab1e05 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -18,6 +18,7 @@ export const uploadPreferencesCommand = buildCommand({ ScopeName.ManageStoredPreferences, ScopeName.ViewManagedConsentDatabaseAdminApi, ScopeName.ViewPreferenceStoreSettings, + ScopeName.ViewRequestIdentitySettings, ], }), partition: { @@ -177,6 +178,12 @@ export const uploadPreferencesCommand = buildCommand({ 'Columns in the CSV that should be ignored. Comma-separated list of column names.', optional: true, }, + skipMetadata: { + kind: 'boolean', + brief: + 'Whether to skip uploading metadata fields. Use this for subsequent batch uploads to avoid replacing existing metadata.', + default: false, + }, viewerMode: { kind: 'boolean', brief: diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index dac5a0de..a69b0886 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -121,6 +121,7 @@ export interface UploadPreferencesCommandFlags { allowedIdentifierNames: string[]; identifierColumns: string[]; columnsToIgnore?: string[]; + skipMetadata: boolean; viewerMode: boolean; } diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts index 95917fe3..816afd74 100644 --- a/src/commands/consent/upload-preferences/schemaState.ts +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -2,6 +2,7 @@ import { PersistedState } from '@transcend-io/persisted-state'; import { FileFormatState, type ColumnIdentifierMap, + type ColumnMetadataMap, type ColumnPurposeMap, } from '../../../lib/preference-management'; import { @@ -16,6 +17,8 @@ export interface PreferenceSchemaInterface { getColumnToPurposeName(): ColumnPurposeMap; /** CSV column name -> Identifier mapping */ getColumnToIdentifier(): ColumnIdentifierMap; + /** CSV column name -> Metadata key mapping */ + getColumnToMetadata(): ColumnMetadataMap | undefined; /** The persisted cache */ // FIXME remove this state: PersistedState; } @@ -82,6 +85,8 @@ export async function makeSchemaState( state.getValue('columnToPurposeName'), getColumnToIdentifier: (): ColumnIdentifierMap => state.getValue('columnToIdentifier'), + getColumnToMetadata: (): ColumnMetadataMap | undefined => + state.getValue('columnToMetadata'), }; } catch (err) { throw new Error( diff --git a/src/commands/request/cron/pull-profiles/impl.ts b/src/commands/request/cron/pull-profiles/impl.ts index 94bf1d01..673bd3a8 100644 --- a/src/commands/request/cron/pull-profiles/impl.ts +++ b/src/commands/request/cron/pull-profiles/impl.ts @@ -2,7 +2,7 @@ import type { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../../../logger'; import colors from 'colors'; import { uniq, chunk } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { buildTranscendGraphQLClient, fetchRequestFilesForRequest, @@ -15,6 +15,8 @@ import { import { doneInputValidation } from '../../../../lib/cli/done-input-validation'; import { parseFilePath, writeLargeCsv } from '../../../../lib/helpers'; +const { map } = Bluebird; + export interface PullProfilesCommandFlags { file: string; fileTarget: string; diff --git a/src/lib/api-keys/generateCrossAccountApiKeys.ts b/src/lib/api-keys/generateCrossAccountApiKeys.ts index 4de7ba33..36f6e83c 100644 --- a/src/lib/api-keys/generateCrossAccountApiKeys.ts +++ b/src/lib/api-keys/generateCrossAccountApiKeys.ts @@ -1,4 +1,4 @@ -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { buildTranscendGraphQLClientGeneric, loginUser, @@ -13,6 +13,8 @@ import { StoredApiKey } from '../../codecs'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { mapSeries } = Bluebird; + export interface ApiKeyGenerateError { /** Name of instance */ organizationName: string; diff --git a/src/lib/consent-manager/buildXdiSyncEndpoint.ts b/src/lib/consent-manager/buildXdiSyncEndpoint.ts index 32e490cb..4fcadebd 100644 --- a/src/lib/consent-manager/buildXdiSyncEndpoint.ts +++ b/src/lib/consent-manager/buildXdiSyncEndpoint.ts @@ -2,12 +2,14 @@ import colors from 'colors'; import { buildTranscendGraphQLClient, fetchConsentManager } from '../graphql'; import { difference } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { StoredApiKey } from '../../codecs'; import { DEFAULT_TRANSCEND_API } from '../../constants'; import { logger } from '../../logger'; import { domainToHost } from './domainToHost'; +const { map } = Bluebird; + /** * Sync group configuration mapping * e.g. diff --git a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts index a103db6e..4951480f 100644 --- a/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts +++ b/src/lib/consent-manager/updateConsentManagerVersionToLatest.ts @@ -1,5 +1,5 @@ import { ConsentBundleType } from '@transcend-io/privacy-types'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { updateConsentManagerToLatest, buildTranscendGraphQLClient, @@ -11,6 +11,8 @@ import colors from 'colors'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { mapSeries } = Bluebird; + /** * Update the consent manager to latest version * diff --git a/src/lib/consent-manager/uploadConsents.ts b/src/lib/consent-manager/uploadConsents.ts index 3b1b3b35..48df6b31 100644 --- a/src/lib/consent-manager/uploadConsents.ts +++ b/src/lib/consent-manager/uploadConsents.ts @@ -2,7 +2,7 @@ import { createTranscendConsentGotInstance } from '../graphql'; import colors from 'colors'; import * as t from 'io-ts'; import { DEFAULT_TRANSCEND_CONSENT_API } from '../../constants'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { createConsentToken } from './createConsentToken'; import { logger } from '../../logger'; import cliProgress from 'cli-progress'; @@ -10,6 +10,8 @@ import { decodeCodec } from '@transcend-io/type-utils'; import type { ConsentPreferenceUpload } from './types'; import { ConsentPreferencesBody } from '@transcend-io/airgap.js-types'; +const { map } = Bluebird; + export const USP_STRING_REGEX = /^[0-9][Y|N]([Y|N])[Y|N]$/; export const PurposeMap = t.record( diff --git a/src/lib/cron/markRequestDataSiloIdsCompleted.ts b/src/lib/cron/markRequestDataSiloIdsCompleted.ts index 07b46d2d..1ee6a6af 100644 --- a/src/lib/cron/markRequestDataSiloIdsCompleted.ts +++ b/src/lib/cron/markRequestDataSiloIdsCompleted.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { @@ -11,6 +11,8 @@ import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; import { RequestDataSiloStatus } from '@transcend-io/privacy-types'; +const { map } = Bluebird; + /** * Given a CSV of Request IDs, mark associated RequestDataSilos as completed * diff --git a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts index 95697b6c..015d993b 100644 --- a/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullChunkedCustomSiloOutstandingIdentifiers.ts @@ -13,7 +13,9 @@ import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; + +const { mapSeries } = Bluebird; /** * A CSV formatted identifier diff --git a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts index e2866c8f..6990a9c9 100644 --- a/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts +++ b/src/lib/cron/pullCustomSiloOutstandingIdentifiers.ts @@ -12,7 +12,9 @@ import { import { RequestAction } from '@transcend-io/privacy-types'; import { logger } from '../../logger'; import { DEFAULT_TRANSCEND_API } from '../../constants'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; + +const { mapSeries } = Bluebird; /** * A CSV formatted identifier diff --git a/src/lib/data-inventory/pullAllDatapoints.ts b/src/lib/data-inventory/pullAllDatapoints.ts index 302315af..07036c19 100644 --- a/src/lib/data-inventory/pullAllDatapoints.ts +++ b/src/lib/data-inventory/pullAllDatapoints.ts @@ -17,7 +17,9 @@ import { } from '../graphql'; import { logger } from '../../logger'; import type { DataCategoryInput, ProcessingPurposeInput } from '../../codecs'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; + +const { mapSeries } = Bluebird; export interface DataSiloCsvPreview { /** ID of dataSilo */ diff --git a/src/lib/graphql/createPreferenceAccessTokens.ts b/src/lib/graphql/createPreferenceAccessTokens.ts index b099b966..a83411cc 100644 --- a/src/lib/graphql/createPreferenceAccessTokens.ts +++ b/src/lib/graphql/createPreferenceAccessTokens.ts @@ -3,7 +3,9 @@ import { CREATE_PREFERENCE_ACCESS_TOKENS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import type { GraphQLClient } from 'graphql-request'; import { chunk } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; + +const { map } = Bluebird; export interface PreferenceAccessTokenInput { /** Slug of data subject to authenticate as */ diff --git a/src/lib/graphql/fetchDataSubjects.ts b/src/lib/graphql/fetchDataSubjects.ts index cb25b686..725f91f5 100644 --- a/src/lib/graphql/fetchDataSubjects.ts +++ b/src/lib/graphql/fetchDataSubjects.ts @@ -5,9 +5,11 @@ import { RequestActionObjectResolver } from '@transcend-io/privacy-types'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; +const { mapSeries } = Bluebird; + export interface DataSubject { /** ID of data subject */ id: string; diff --git a/src/lib/graphql/fetchIdentifiers.ts b/src/lib/graphql/fetchIdentifiers.ts index 34e0e1a7..d927bdd7 100644 --- a/src/lib/graphql/fetchIdentifiers.ts +++ b/src/lib/graphql/fetchIdentifiers.ts @@ -5,9 +5,11 @@ import { keyBy, uniq, flatten, difference } from 'lodash-es'; import { TranscendInput } from '../../codecs'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; +const { mapSeries } = Bluebird; + export interface Identifier { /** ID of identifier */ id: string; diff --git a/src/lib/graphql/syncActionItemCollections.ts b/src/lib/graphql/syncActionItemCollections.ts index 5c1f1682..0687cbd4 100644 --- a/src/lib/graphql/syncActionItemCollections.ts +++ b/src/lib/graphql/syncActionItemCollections.ts @@ -1,6 +1,6 @@ import { ActionItemCollectionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_ACTION_ITEM_COLLECTION, CREATE_ACTION_ITEM_COLLECTION, @@ -14,6 +14,8 @@ import { fetchAllActionItemCollections, } from './fetchAllActionItemCollections'; +const { mapSeries } = Bluebird; + /** * Input to create a new action item collection * diff --git a/src/lib/graphql/syncActionItems.ts b/src/lib/graphql/syncActionItems.ts index 5fc86a96..b83c09e1 100644 --- a/src/lib/graphql/syncActionItems.ts +++ b/src/lib/graphql/syncActionItems.ts @@ -1,7 +1,7 @@ import { ActionItemInput } from '../../codecs'; import { uniq, keyBy, chunk } from 'lodash-es'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_ACTION_ITEMS, CREATE_ACTION_ITEMS } from './gqls'; import { logger } from '../../logger'; import { makeGraphQLRequest } from './makeGraphQLRequest'; @@ -13,6 +13,8 @@ import { } from './fetchAllActionItemCollections'; import { Attribute, fetchAllAttributes } from './fetchAllAttributes'; +const { mapSeries } = Bluebird; + /** * Input to create a new actionItem * diff --git a/src/lib/graphql/syncAgentFiles.ts b/src/lib/graphql/syncAgentFiles.ts index 5ffd1f95..56cde9a3 100644 --- a/src/lib/graphql/syncAgentFiles.ts +++ b/src/lib/graphql/syncAgentFiles.ts @@ -1,6 +1,6 @@ import { AgentFileInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_AGENT_FILES, CREATE_AGENT_FILE } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -8,6 +8,8 @@ import { makeGraphQLRequest } from './makeGraphQLRequest'; import colors from 'colors'; import { fetchAllAgentFiles, AgentFile } from './fetchAllAgentFiles'; +const { mapSeries } = Bluebird; + /** * Input to create a new agent file * diff --git a/src/lib/graphql/syncAgentFunctions.ts b/src/lib/graphql/syncAgentFunctions.ts index ed5665e9..b0e78131 100644 --- a/src/lib/graphql/syncAgentFunctions.ts +++ b/src/lib/graphql/syncAgentFunctions.ts @@ -1,6 +1,6 @@ import { AgentFunctionInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_AGENT_FUNCTIONS, CREATE_AGENT_FUNCTION } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -11,6 +11,8 @@ import { AgentFunction, } from './fetchAllAgentFunctions'; +const { mapSeries } = Bluebird; + /** * Input to create a new agent function * diff --git a/src/lib/graphql/syncAgents.ts b/src/lib/graphql/syncAgents.ts index 755c6528..c8b3ad7b 100644 --- a/src/lib/graphql/syncAgents.ts +++ b/src/lib/graphql/syncAgents.ts @@ -1,6 +1,6 @@ import { AgentInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_AGENTS, CREATE_AGENT } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -8,6 +8,8 @@ import { makeGraphQLRequest } from './makeGraphQLRequest'; import colors from 'colors'; import { fetchAllAgents, Agent } from './fetchAllAgents'; +const { mapSeries } = Bluebird; + /** * Input to create a new agent * diff --git a/src/lib/graphql/syncAttribute.ts b/src/lib/graphql/syncAttribute.ts index beb3df03..9a73bc17 100644 --- a/src/lib/graphql/syncAttribute.ts +++ b/src/lib/graphql/syncAttribute.ts @@ -11,9 +11,11 @@ import { } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { Attribute } from './fetchAllAttributes'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { logger } from '../../logger'; +const { map } = Bluebird; + /** * Sync attribute * diff --git a/src/lib/graphql/syncBusinessEntities.ts b/src/lib/graphql/syncBusinessEntities.ts index 7709b1bb..e9834172 100644 --- a/src/lib/graphql/syncBusinessEntities.ts +++ b/src/lib/graphql/syncBusinessEntities.ts @@ -1,6 +1,6 @@ import { BusinessEntityInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_BUSINESS_ENTITIES, CREATE_BUSINESS_ENTITY } from './gqls'; import { logger } from '../../logger'; import { keyBy, chunk } from 'lodash-es'; @@ -11,6 +11,8 @@ import { } from './fetchAllBusinessEntities'; import colors from 'colors'; +const { mapSeries } = Bluebird; + /** * Input to create a new business entity * diff --git a/src/lib/graphql/syncCodePackages.ts b/src/lib/graphql/syncCodePackages.ts index 4aa0991c..88edf83c 100644 --- a/src/lib/graphql/syncCodePackages.ts +++ b/src/lib/graphql/syncCodePackages.ts @@ -4,13 +4,15 @@ import { GraphQLClient } from 'graphql-request'; import { CodePackage, fetchAllCodePackages } from './fetchAllCodePackages'; import { logger } from '../../logger'; import { syncSoftwareDevelopmentKits } from './syncSoftwareDevelopmentKits'; -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { CodePackageInput, RepositoryInput } from '../../codecs'; import { CodePackageType } from '@transcend-io/privacy-types'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { CREATE_CODE_PACKAGE, UPDATE_CODE_PACKAGES } from './gqls'; import { syncRepositories } from './syncRepositories'; +const { map, mapSeries } = Bluebird; + const CHUNK_SIZE = 100; const LOOKUP_SPLIT_KEY = '%%%%'; diff --git a/src/lib/graphql/syncConfigurationToTranscend.ts b/src/lib/graphql/syncConfigurationToTranscend.ts index 1d4cda0c..9d4c7114 100644 --- a/src/lib/graphql/syncConfigurationToTranscend.ts +++ b/src/lib/graphql/syncConfigurationToTranscend.ts @@ -3,7 +3,7 @@ import { TranscendInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchIdentifiersAndCreateMissing, Identifier, @@ -44,6 +44,8 @@ import { syncProcessingPurposes } from './syncProcessingPurposes'; import { syncProcessingActivities } from './syncProcessingActivities'; import { syncPartitions } from './syncPartitions'; +const { map } = Bluebird; + const CONCURRENCY = 10; /** diff --git a/src/lib/graphql/syncConsentManager.ts b/src/lib/graphql/syncConsentManager.ts index 151d063f..6f2ee7a0 100644 --- a/src/lib/graphql/syncConsentManager.ts +++ b/src/lib/graphql/syncConsentManager.ts @@ -24,7 +24,7 @@ import { fetchConsentManagerExperiences, } from './fetchConsentManagerId'; import { keyBy } from 'lodash-es'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { InitialViewState, OnConsentExpiry, @@ -34,6 +34,8 @@ import { fetchPrivacyCenterId } from './fetchPrivacyCenterId'; import { fetchPartitions } from './syncPartitions'; import { fetchAllPurposes } from './fetchAllPurposes'; +const { map } = Bluebird; + const PURPOSES_LINK = 'https://app.transcend.io/consent-manager/regional-experiences/purposes'; diff --git a/src/lib/graphql/syncCookies.ts b/src/lib/graphql/syncCookies.ts index 40cdba4d..4459b2f2 100644 --- a/src/lib/graphql/syncCookies.ts +++ b/src/lib/graphql/syncCookies.ts @@ -5,10 +5,12 @@ import colors from 'colors'; import { UPDATE_OR_CREATE_COOKIES } from './gqls'; import { chunk } from 'lodash-es'; import { fetchConsentManagerId } from './fetchConsentManagerId'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; // import { keyBy } from 'lodash-es'; import { makeGraphQLRequest } from './makeGraphQLRequest'; +const { mapSeries } = Bluebird; + const MAX_PAGE_SIZE = 100; /** diff --git a/src/lib/graphql/syncDataCategories.ts b/src/lib/graphql/syncDataCategories.ts index 6ed8d20c..d9170715 100644 --- a/src/lib/graphql/syncDataCategories.ts +++ b/src/lib/graphql/syncDataCategories.ts @@ -1,6 +1,6 @@ import { DataCategoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_DATA_SUB_CATEGORIES, CREATE_DATA_SUB_CATEGORY } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -11,6 +11,8 @@ import { DataSubCategory, } from './fetchAllDataCategories'; +const { mapSeries } = Bluebird; + /** * Input to create a new data category * diff --git a/src/lib/graphql/syncDataFlows.ts b/src/lib/graphql/syncDataFlows.ts index 52601f03..45d81277 100644 --- a/src/lib/graphql/syncDataFlows.ts +++ b/src/lib/graphql/syncDataFlows.ts @@ -1,7 +1,7 @@ import { GraphQLClient } from 'graphql-request'; import { CREATE_DATA_FLOWS, UPDATE_DATA_FLOWS } from './gqls'; import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { DataFlowInput } from '../../codecs'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchConsentManagerId } from './fetchConsentManagerId'; @@ -10,6 +10,8 @@ import colors from 'colors'; import { fetchAllDataFlows } from './fetchAllDataFlows'; import { ConsentTrackerStatus } from '@transcend-io/privacy-types'; +const { mapSeries } = Bluebird; + const MAX_PAGE_SIZE = 100; /** diff --git a/src/lib/graphql/syncDataSilos.ts b/src/lib/graphql/syncDataSilos.ts index 4536f9df..806dedc1 100644 --- a/src/lib/graphql/syncDataSilos.ts +++ b/src/lib/graphql/syncDataSilos.ts @@ -8,7 +8,7 @@ import { import { GraphQLClient } from 'graphql-request'; import { logger } from '../../logger'; import colors from 'colors'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; import { DATA_SILOS, CREATE_DATA_SILOS, @@ -37,6 +37,8 @@ import { sortBy, chunk, keyBy } from 'lodash-es'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { apply } from '@transcend-io/type-utils'; +const { mapSeries, map } = Bluebird; + export interface DataSiloAttributeValue { /** Key associated to value */ attributeKey: { diff --git a/src/lib/graphql/syncIntlMessages.ts b/src/lib/graphql/syncIntlMessages.ts index f5af9f00..c978f875 100644 --- a/src/lib/graphql/syncIntlMessages.ts +++ b/src/lib/graphql/syncIntlMessages.ts @@ -4,9 +4,11 @@ import { IntlMessageInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_INTL_MESSAGES } from './gqls'; import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; +const { mapSeries } = Bluebird; + const MAX_PAGE_SIZE = 100; /** diff --git a/src/lib/graphql/syncPartitions.ts b/src/lib/graphql/syncPartitions.ts index bca2bafa..6d258744 100644 --- a/src/lib/graphql/syncPartitions.ts +++ b/src/lib/graphql/syncPartitions.ts @@ -2,12 +2,14 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { CREATE_CONSENT_PARTITION, CONSENT_PARTITIONS } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { difference } from 'lodash-es'; import { logger } from '../../logger'; import { PartitionInput } from '../../codecs'; import { fetchConsentManagerId } from './fetchConsentManagerId'; +const { mapSeries } = Bluebird; + const PAGE_SIZE = 50; export interface TranscendPartition { diff --git a/src/lib/graphql/syncPolicies.ts b/src/lib/graphql/syncPolicies.ts index e0546efc..afce5cfc 100644 --- a/src/lib/graphql/syncPolicies.ts +++ b/src/lib/graphql/syncPolicies.ts @@ -4,11 +4,13 @@ import { PolicyInput } from '../../codecs'; import colors from 'colors'; import { UPDATE_POLICIES } from './gqls'; import { chunk, keyBy } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { fetchPrivacyCenterId } from './fetchPrivacyCenterId'; import { fetchAllPolicies } from './fetchAllPolicies'; +const { mapSeries } = Bluebird; + const MAX_PAGE_SIZE = 100; /** diff --git a/src/lib/graphql/syncPrompts.ts b/src/lib/graphql/syncPrompts.ts index 3b03c0c9..c3ab646f 100644 --- a/src/lib/graphql/syncPrompts.ts +++ b/src/lib/graphql/syncPrompts.ts @@ -3,11 +3,13 @@ import colors from 'colors'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_PROMPTS, CREATE_PROMPT } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchAllPrompts } from './fetchPrompts'; import { keyBy } from 'lodash-es'; import { logger } from '../../logger'; +const { map } = Bluebird; + /** * Create a new prompt * diff --git a/src/lib/graphql/syncRepositories.ts b/src/lib/graphql/syncRepositories.ts index ee3f063e..07c0f9e6 100644 --- a/src/lib/graphql/syncRepositories.ts +++ b/src/lib/graphql/syncRepositories.ts @@ -4,10 +4,12 @@ import { RepositoryInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; import { UPDATE_REPOSITORIES, CREATE_REPOSITORY } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchAllRepositories, Repository } from './fetchAllRepositories'; import { logger } from '../../logger'; +const { mapSeries, map } = Bluebird; + const CHUNK_SIZE = 100; /** diff --git a/src/lib/graphql/syncSoftwareDevelopmentKits.ts b/src/lib/graphql/syncSoftwareDevelopmentKits.ts index a02e76d2..0edd9d19 100644 --- a/src/lib/graphql/syncSoftwareDevelopmentKits.ts +++ b/src/lib/graphql/syncSoftwareDevelopmentKits.ts @@ -7,7 +7,7 @@ import { CREATE_SOFTWARE_DEVELOPMENT_KIT, } from './gqls'; import { makeGraphQLRequest } from './makeGraphQLRequest'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; import { fetchAllSoftwareDevelopmentKits, SoftwareDevelopmentKit, @@ -15,6 +15,8 @@ import { import { logger } from '../../logger'; import { CodePackageType } from '@transcend-io/privacy-types'; +const { mapSeries, map } = Bluebird; + const CHUNK_SIZE = 100; /** diff --git a/src/lib/graphql/syncTeams.ts b/src/lib/graphql/syncTeams.ts index eccf0c98..20c4a6b0 100644 --- a/src/lib/graphql/syncTeams.ts +++ b/src/lib/graphql/syncTeams.ts @@ -1,6 +1,6 @@ import { TeamInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_TEAM, CREATE_TEAM } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -8,6 +8,8 @@ import { makeGraphQLRequest } from './makeGraphQLRequest'; import colors from 'colors'; import { fetchAllTeams, Team } from './fetchAllTeams'; +const { mapSeries } = Bluebird; + /** * Input to create a new team * diff --git a/src/lib/graphql/syncVendors.ts b/src/lib/graphql/syncVendors.ts index 0fbb73d3..824a124a 100644 --- a/src/lib/graphql/syncVendors.ts +++ b/src/lib/graphql/syncVendors.ts @@ -1,6 +1,6 @@ import { VendorInput } from '../../codecs'; import { GraphQLClient } from 'graphql-request'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { UPDATE_VENDORS, CREATE_VENDOR } from './gqls'; import { logger } from '../../logger'; import { keyBy } from 'lodash-es'; @@ -8,6 +8,8 @@ import { makeGraphQLRequest } from './makeGraphQLRequest'; import colors from 'colors'; import { fetchAllVendors, Vendor } from './fetchAllVendors'; +const { mapSeries } = Bluebird; + /** * Input to create a new vendor * diff --git a/src/lib/graphql/uploadSiloDiscoveryResults.ts b/src/lib/graphql/uploadSiloDiscoveryResults.ts index 2fad990d..35d9096e 100644 --- a/src/lib/graphql/uploadSiloDiscoveryResults.ts +++ b/src/lib/graphql/uploadSiloDiscoveryResults.ts @@ -1,10 +1,12 @@ import { chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { ADD_SILO_DISCOVERY_RESULTS } from './gqls'; import { GraphQLClient } from 'graphql-request'; import { makeGraphQLRequest } from './makeGraphQLRequest'; import { SiloDiscoveryRawResults } from '../code-scanning/findFilesToScan'; +const { mapSeries } = Bluebird; + const CHUNK_SIZE = 1000; /** diff --git a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts index 314bf069..81deafeb 100644 --- a/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts +++ b/src/lib/manual-enrichment/pullManualEnrichmentIdentifiersToCsv.ts @@ -1,5 +1,5 @@ import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { groupBy, uniq } from 'lodash-es'; import { DEFAULT_TRANSCEND_API } from '../../constants'; @@ -16,6 +16,8 @@ import { } from '../graphql'; import { logger } from '../../logger'; +const { map } = Bluebird; + export interface PrivacyRequestWithIdentifiers extends PrivacyRequest { /** Request Enrichers */ requestEnrichers: RequestEnricher[]; diff --git a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts index 80aaeb5c..119644fd 100644 --- a/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts +++ b/src/lib/manual-enrichment/pushManualEnrichmentIdentifiersFromCsv.ts @@ -1,5 +1,5 @@ import colors from 'colors'; -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import { logger } from '../../logger'; import { UPDATE_PRIVACY_REQUEST, @@ -14,6 +14,8 @@ import { import { readCsv } from '../requests'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Push a CSV of enriched requests back into Transcend * diff --git a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts index ed91d269..0f2f86e3 100644 --- a/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts +++ b/src/lib/oneTrust/helpers/syncOneTrustAssessmentsFromOneTrust.ts @@ -6,7 +6,7 @@ import { getOneTrustRisk, getOneTrustUser, } from '../endpoints'; -import { mapSeries, map } from 'bluebird'; +import Bluebird from 'bluebird'; import { logger } from '../../../logger'; import { OneTrustAssessmentQuestion, @@ -21,6 +21,8 @@ import { syncOneTrustAssessmentToDisk } from './syncOneTrustAssessmentToDisk'; import { GraphQLClient } from 'graphql-request'; import { syncOneTrustAssessmentToTranscend } from './syncOneTrustAssessmentToTranscend'; +const { mapSeries, map } = Bluebird; + export interface AssessmentForm { /** ID of Assessment Form */ id: string; diff --git a/src/lib/preference-management/index.ts b/src/lib/preference-management/index.ts index ca8c7eec..05dbd474 100644 --- a/src/lib/preference-management/index.ts +++ b/src/lib/preference-management/index.ts @@ -2,6 +2,7 @@ export * from './codecs'; export * from './getPreferencesForIdentifiers'; export * from './parsePreferenceManagementCsv'; export * from './getPreferenceUpdatesFromRow'; +export * from './getPreferenceMetadataFromRow'; export * from './parsePreferenceManagementCsv'; export * from './parsePreferenceIdentifiersFromCsv'; export * from './parsePreferenceFileFormatFromCsv'; diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 081ae0ba..0b1283a1 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -8,11 +8,13 @@ import type { } from './codecs'; import { logger } from '../../logger'; import { inquirerConfirmBoolean } from '../helpers'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import type { Identifier } from '../graphql'; import type { PreferenceStoreIdentifier } from '@transcend-io/privacy-types'; import type { PersistedState } from '@transcend-io/persisted-state'; +const { mapSeries } = Bluebird; + /* eslint-disable no-param-reassign */ /** diff --git a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts index d641bec4..0618383b 100644 --- a/src/lib/requests/removeUnverifiedRequestIdentifiers.ts +++ b/src/lib/requests/removeUnverifiedRequestIdentifiers.ts @@ -1,4 +1,4 @@ -import { map } from 'bluebird'; +import Bluebird from 'bluebird'; import colors from 'colors'; import { logger } from '../../logger'; import { RequestAction, RequestStatus } from '@transcend-io/privacy-types'; @@ -12,6 +12,8 @@ import { import cliProgress from 'cli-progress'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map } = Bluebird; + /** * Remove a set of unverified request identifier * From cf11f60770386ab903cd01531d43f4feafd80846 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Sun, 22 Feb 2026 20:39:49 -0800 Subject: [PATCH 68/72] bluebird --- .../consent/upload-preferences/worker.ts | 1 + src/commands/inventory/pull/impl.ts | 4 +- src/commands/inventory/push/impl.ts | 4 +- src/lib/ai/TranscendPromptManager.ts | 4 +- src/lib/cron/pushCronIdentifiersFromCsv.ts | 4 +- src/lib/preference-management/codecs.ts | 23 +++++++++++ .../fetchConsentPreferencesChunked.ts | 3 +- .../getPreferenceMetadataFromRow.ts | 40 +++++++++++++++++++ .../getPreferencesForIdentifiers.ts | 1 + 9 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 src/lib/preference-management/getPreferenceMetadataFromRow.ts diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index ed96996a..6c6def79 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -125,6 +125,7 @@ export async function runChild(): Promise { skipWorkflowTriggers: options.skipWorkflowTriggers, skipConflictUpdates: options.skipConflictUpdates, forceTriggerWorkflows: options.forceTriggerWorkflows, + skipMetadata: options.skipMetadata, uploadLogInterval: options.uploadLogInterval, maxChunkSize: options.maxChunkSize, uploadConcurrency: options.uploadConcurrency, diff --git a/src/commands/inventory/pull/impl.ts b/src/commands/inventory/pull/impl.ts index ea0a560b..0df6d64e 100644 --- a/src/commands/inventory/pull/impl.ts +++ b/src/commands/inventory/pull/impl.ts @@ -8,7 +8,7 @@ import { import { logger } from '../../../logger'; import colors from 'colors'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { join } from 'node:path'; import fs from 'node:fs'; import { @@ -21,6 +21,8 @@ import { ADMIN_DASH_INTEGRATIONS } from '../../../constants'; import { validateTranscendAuth } from '../../../lib/api-keys'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; +const { mapSeries } = Bluebird; + export interface PullCommandFlags { auth: string; resources?: (TranscendPullResource | 'all')[]; diff --git a/src/commands/inventory/push/impl.ts b/src/commands/inventory/push/impl.ts index db44ef73..655e5b6f 100644 --- a/src/commands/inventory/push/impl.ts +++ b/src/commands/inventory/push/impl.ts @@ -1,7 +1,7 @@ import type { LocalContext } from '../../../context'; import { logger } from '../../../logger'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { existsSync, lstatSync } from 'node:fs'; import { join } from 'node:path'; import { readTranscendYaml } from '../../../lib/readTranscendYaml'; @@ -18,6 +18,8 @@ import { mergeTranscendInputs } from '../../../lib/mergeTranscendInputs'; import { parseVariablesFromString } from '../../../lib/helpers/parseVariablesFromString'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; +const { mapSeries } = Bluebird; + /** * Sync configuration to Transcend * diff --git a/src/lib/ai/TranscendPromptManager.ts b/src/lib/ai/TranscendPromptManager.ts index 6871f576..79349532 100644 --- a/src/lib/ai/TranscendPromptManager.ts +++ b/src/lib/ai/TranscendPromptManager.ts @@ -42,12 +42,14 @@ import { fetchAllLargeLanguageModels, } from '../graphql/fetchLargeLanguageModels'; import { groupBy, keyBy, uniq, chunk } from 'lodash-es'; -import { mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { PromptThread, fetchAllPromptThreads, } from '../graphql/fetchPromptThreads'; +const { mapSeries } = Bluebird; + /** * An LLM Prompt definition */ diff --git a/src/lib/cron/pushCronIdentifiersFromCsv.ts b/src/lib/cron/pushCronIdentifiersFromCsv.ts index 82027087..d25232da 100644 --- a/src/lib/cron/pushCronIdentifiersFromCsv.ts +++ b/src/lib/cron/pushCronIdentifiersFromCsv.ts @@ -1,4 +1,4 @@ -import { map, mapSeries } from 'bluebird'; +import Bluebird from 'bluebird'; import { chunk } from 'lodash-es'; import { createSombraGotInstance } from '../graphql'; import colors from 'colors'; @@ -11,6 +11,8 @@ import { logger } from '../../logger'; import { readCsv } from '../requests'; import { DEFAULT_TRANSCEND_API } from '../../constants'; +const { map, mapSeries } = Bluebird; + /** * Given a CSV of cron job outputs, mark all requests as completed in Transcend * diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index a097a0ba..8c2b33b0 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -79,6 +79,27 @@ export const ColumnIdentifierMap = t.record( /** Override type */ export type ColumnIdentifierMap = t.TypeOf; +/** + * Mapping for a single metadata field. + * Maps a CSV column to a metadata key in the API. + */ +export const MetadataMapping = t.type({ + /** The metadata key name in the API */ + key: t.string, +}); + +/** Override type */ +export type MetadataMapping = t.TypeOf; + +/** + * Mapping of CSV column names to metadata keys. + * This is used to map columns in the CSV to metadata fields in the preference store. + */ +export const ColumnMetadataMap = t.record(t.string, MetadataMapping); + +/** Override type */ +export type ColumnMetadataMap = t.TypeOf; + export const FileFormatState = t.intersection([ t.type({ /** @@ -94,6 +115,8 @@ export const FileFormatState = t.intersection([ t.partial({ /** Determine which column name in file maps to the timestamp */ timestampColumn: t.string, + /** Mapping of CSV column names to metadata keys */ + columnToMetadata: ColumnMetadataMap, }), ]); diff --git a/src/lib/preference-management/fetchConsentPreferencesChunked.ts b/src/lib/preference-management/fetchConsentPreferencesChunked.ts index d91d7a7c..6d8ab45a 100644 --- a/src/lib/preference-management/fetchConsentPreferencesChunked.ts +++ b/src/lib/preference-management/fetchConsentPreferencesChunked.ts @@ -1,5 +1,4 @@ import Bluebird from 'bluebird'; -const { map: pmap } = Bluebird; import type { Got } from 'got'; import cliProgress from 'cli-progress'; import colors from 'colors'; @@ -17,6 +16,8 @@ import { iterateConsentPages } from './iterateConsentPages'; import { logger } from '../../logger'; import { pickConsentChunkMode } from './pickConsentChunkMode'; +const { map: pmap } = Bluebird; + /** * Merge baseFilter with a window filter, taking care not to mix timestamp/updated fields improperly. * diff --git a/src/lib/preference-management/getPreferenceMetadataFromRow.ts b/src/lib/preference-management/getPreferenceMetadataFromRow.ts new file mode 100644 index 00000000..24b24a32 --- /dev/null +++ b/src/lib/preference-management/getPreferenceMetadataFromRow.ts @@ -0,0 +1,40 @@ +import type { ColumnMetadataMap } from './codecs'; + +/** + * Extract metadata values from a CSV row based on the column-to-metadata mapping. + * + * @param options - Options for extracting metadata + * @returns Array of metadata key-value pairs for the preference store API + */ +export function getPreferenceMetadataFromRow({ + row, + columnToMetadata, +}: { + /** The CSV row as a record of column name to value */ + row: Record; + /** Mapping from CSV column name to metadata key */ + columnToMetadata: ColumnMetadataMap; +}): Array<{ + /** */ key: string /** */; + /** */ + value: string; +}> { + return Object.entries(columnToMetadata) + .map(([columnName, { key }]) => { + const value = row[columnName]; + // Skip if no value in the row or empty string + if (value === undefined || value === '') { + return null; + } + return { key, value }; + }) + .filter( + ( + x, + ): x is { + /** */ key: string /** */; + /** */ + value: string; + } => x !== null, + ); +} diff --git a/src/lib/preference-management/getPreferencesForIdentifiers.ts b/src/lib/preference-management/getPreferencesForIdentifiers.ts index c98a07bf..d4722dbf 100644 --- a/src/lib/preference-management/getPreferencesForIdentifiers.ts +++ b/src/lib/preference-management/getPreferencesForIdentifiers.ts @@ -111,6 +111,7 @@ export async function getPreferencesForIdentifiers( .post(`v1/preferences/${partitionKey}/query`, { json: { filter: { identifiers: group }, + // FIXME // limit: group.length, }, }) From db14c399a1205e9883be8d9595690839e82a393d Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 24 Feb 2026 17:05:24 -0800 Subject: [PATCH 69/72] Add configure-preference-upload command and refactor upload pipeline - New `consent configure-preference-upload` command that scans all CSV files to discover headers and unique values, then interactively builds the column mapping config (identifiers, ignored columns, timestamps, purposes/preferences and value mappings). - Move `columnsToIgnore` and `identifierColumns` into the persisted config file (FileFormatState) instead of CLI flags. - Remove `allowedIdentifierNames`, `identifierColumns`, `columnsToIgnore`, and `skipMetadata` CLI flags from `upload-preferences`. - Add `--regenerate` flag to force re-running config generation and `--chunkSizeMB` flag for auto-chunking oversized CSV files. - Add `nonInteractive` mode to all three parse* functions so worker processes throw instead of prompting when config is incomplete. - Workers now derive identifier/ignore columns from the schema config. Co-authored-by: Cursor --- README.md | 43 ++- .../configure-preference-upload/command.ts | 57 ++++ .../configure-preference-upload/impl.ts | 317 ++++++++++++++++++ src/commands/consent/routes.ts | 2 + .../upload-preferences/buildTaskOptions.ts | 12 - .../consent/upload-preferences/command.ts | 38 +-- .../consent/upload-preferences/impl.ts | 118 ++++--- .../consent/upload-preferences/schemaState.ts | 4 + .../upload/buildInteractiveUploadPlan.ts | 6 +- .../interactivePreferenceUploaderFromPlan.ts | 6 +- .../consent/upload-preferences/worker.ts | 19 +- src/lib/preference-management/codecs.ts | 58 +--- .../parsePreferenceAndPurposeValuesFromCsv.ts | 20 ++ .../parsePreferenceFileFormatFromCsv.ts | 13 + .../parsePreferenceIdentifiersFromCsv.ts | 19 +- .../parsePreferenceManagementCsv.ts | 9 +- 16 files changed, 585 insertions(+), 156 deletions(-) create mode 100644 src/commands/consent/configure-preference-upload/command.ts create mode 100644 src/commands/consent/configure-preference-upload/impl.ts diff --git a/README.md b/README.md index 404e05d0..8f1e066e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ A command line interface that allows you to programatically interact with the Tr - [`transcend request cron pull-identifiers`](#transcend-request-cron-pull-identifiers) - [`transcend request cron mark-identifiers-completed`](#transcend-request-cron-mark-identifiers-completed) - [`transcend consent build-xdi-sync-endpoint`](#transcend-consent-build-xdi-sync-endpoint) + - [`transcend consent configure-preference-upload`](#transcend-consent-configure-preference-upload) - [`transcend consent generate-access-tokens`](#transcend-consent-generate-access-tokens) - [`transcend consent pull-consent-metrics`](#transcend-consent-pull-consent-metrics) - [`transcend consent pull-consent-preferences`](#transcend-consent-pull-consent-preferences) @@ -1666,6 +1667,33 @@ transcend consent build-xdi-sync-endpoint \ --transcendUrl=https://api.us.transcend.io ``` +### `transcend consent configure-preference-upload` + +```txt +USAGE + transcend consent configure-preference-upload (--auth value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--schemaFilePath value] (--partition value) + transcend consent configure-preference-upload --help + +Interactively configure the column mapping for preference CSV uploads. + +Scans ALL CSV files in the given directory to discover every column header +and every unique value per column, then walks through an interactive editor +to build the full mapping config (identifiers, ignored columns, timestamp, +purposes/preferences and their value mappings). + +The resulting config JSON is reused by 'upload-preferences' so subsequent +uploads run fully non-interactively. + +FLAGS + --auth The Transcend API key. Requires scopes: "View Preference Store Settings", "View Identity Verification Settings" + [--sombraAuth] The Sombra internal key, use for additional authentication when self-hosting Sombra + [--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io] + --directory Path to the directory of CSV files to scan for column headers and unique values + [--schemaFilePath] Path to the config JSON file. Defaults to /../preference-upload-schema.json + --partition The partition key for the preference store + -h --help Print help information and exit +``` + ### `transcend consent generate-access-tokens` ```txt @@ -2182,14 +2210,17 @@ transcend consent upload-data-flows-from-csv \ ```txt USAGE - transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] (--allowedIdentifierNames value) (--identifierColumns value) [--columnsToIgnore value] [--skipMetadata] [--viewerMode] + transcend consent upload-preferences (--auth value) (--partition value) [--sombraAuth value] [--transcendUrl value] (--directory value) [--dryRun] [--skipExistingRecordCheck] [--receiptFileDir value] [--schemaFilePath value] [--skipWorkflowTriggers] [--forceTriggerWorkflows] [--skipConflictUpdates] [--isSilent] [--attributes value] [--receiptFilepath value] [--concurrency value] [--uploadConcurrency value] [--maxChunkSize value] [--rateLimitRetryDelay value] [--uploadLogInterval value] [--downloadIdentifierConcurrency value] [--maxRecordsToReceipt value] [--regenerate] [--chunkSizeMB value] [--viewerMode] transcend consent upload-preferences --help Upload preference management data to your Preference Store. -This 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. +Requires a config file (generated by 'configure-preference-upload') that maps +CSV columns to identifiers, purposes, and preferences. If no config exists, +pass --regenerate to run the interactive configure flow first. -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. +Large files are automatically chunked into smaller pieces (controlled by +--chunkSizeMB) before uploading. Parallel preference uploader (Node 22+ ESM/TS) ----------------------------------------------------------------------------- @@ -2222,10 +2253,8 @@ FLAGS [--uploadLogInterval] When uploading preferences to v1/preferences - this is the number of records after which to log progress. Output will be logged to console and also to the receipt file. Setting this value lower will allow for you to more easily pick up where you left off. Setting this value higher can avoid excessive i/o operations slowing down the upload. Default is a good optimization for most cases. [default = 1000] [--downloadIdentifierConcurrency] When downloading identifiers for the upload - this is the number of concurrent requests to make. This is only used if the records are not already cached in the preference store. [default = 30] [--maxRecordsToReceipt] When writing out successful and pending records to the receipt file - this is the maximum number of records to write out. This is to avoid the receipt file getting too large for JSON.parse/stringify. [default = 10] - --allowedIdentifierNames Identifiers configured for the run. Comma-separated list of identifier names. - --identifierColumns Columns in the CSV that should be used as identifiers. Comma-separated list of column names. - [--columnsToIgnore] Columns in the CSV that should be ignored. Comma-separated list of column names. - [--skipMetadata] Whether to skip uploading metadata fields. Use this for subsequent batch uploads to avoid replacing existing metadata. [default = false] + [--regenerate] Force re-generation of the schema config file before uploading. Runs the interactive configure flow even if a config already exists. [default = false] + [--chunkSizeMB] Auto-chunk threshold in MB. Any CSV file larger than this will be split into smaller files before uploading. Set to 0 to disable. [default = 10] [--viewerMode] Run in non-interactive viewer mode (no attach UI, auto-artifacts) [default = false] -h --help Print help information and exit ``` diff --git a/src/commands/consent/configure-preference-upload/command.ts b/src/commands/consent/configure-preference-upload/command.ts new file mode 100644 index 00000000..d4d7b541 --- /dev/null +++ b/src/commands/consent/configure-preference-upload/command.ts @@ -0,0 +1,57 @@ +import { buildCommand } from '@stricli/core'; +import { ScopeName } from '@transcend-io/privacy-types'; +import { + createAuthParameter, + createSombraAuthParameter, + createTranscendUrlParameter, +} from '../../../lib/cli/common-parameters'; + +export const configurePreferenceUploadCommand = buildCommand({ + loader: async () => { + const { configurePreferenceUpload } = await import('./impl'); + return configurePreferenceUpload; + }, + parameters: { + flags: { + auth: createAuthParameter({ + scopes: [ + ScopeName.ViewPreferenceStoreSettings, + ScopeName.ViewRequestIdentitySettings, + ], + }), + sombraAuth: createSombraAuthParameter(), + transcendUrl: createTranscendUrlParameter(), + directory: { + kind: 'parsed', + parse: String, + brief: + 'Path to the directory of CSV files to scan for column headers and unique values', + }, + schemaFilePath: { + kind: 'parsed', + parse: String, + brief: + 'Path to the config JSON file. Defaults to /../preference-upload-schema.json', + optional: true, + }, + partition: { + kind: 'parsed', + parse: String, + brief: 'The partition key for the preference store', + }, + }, + }, + docs: { + brief: + 'Interactively configure the column mapping for preference CSV uploads', + fullDescription: `Interactively configure the column mapping for preference CSV uploads. + +Scans ALL CSV files in the given directory to discover every column header +and every unique value per column, then walks through an interactive editor +to build the full mapping config (identifiers, ignored columns, timestamp, +purposes/preferences and their value mappings). + +The resulting config JSON is reused by 'upload-preferences' so subsequent +uploads run fully non-interactively.`, + }, +}); diff --git a/src/commands/consent/configure-preference-upload/impl.ts b/src/commands/consent/configure-preference-upload/impl.ts new file mode 100644 index 00000000..47049375 --- /dev/null +++ b/src/commands/consent/configure-preference-upload/impl.ts @@ -0,0 +1,317 @@ +import type { LocalContext } from '../../../context'; +import colors from 'colors'; +import * as t from 'io-ts'; +import { createReadStream } from 'node:fs'; +import { parse as csvParse } from 'csv-parse'; +import inquirer from 'inquirer'; +import { PersistedState } from '@transcend-io/persisted-state'; +import { logger } from '../../../logger'; +import { doneInputValidation } from '../../../lib/cli/done-input-validation'; +import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; +import { buildTranscendGraphQLClient } from '../../../lib/graphql'; +import { loadReferenceData } from '../upload-preferences/upload/loadReferenceData'; +import { computeSchemaFile } from '../upload-preferences/artifacts'; +import { + FileFormatState, + parsePreferenceIdentifiersFromCsv, + parsePreferenceFileFormatFromCsv, + parsePreferenceAndPurposeValuesFromCsv, +} from '../../../lib/preference-management'; +import { readCsv } from '../../../lib/requests'; + +export interface ConfigurePreferenceUploadFlags { + auth: string; + sombraAuth?: string; + transcendUrl: string; + directory: string; + schemaFilePath?: string; + partition: string; +} + +/** + * Scan all CSV files in a directory and collect the union of column headers + * plus all unique values per non-identifier column. Uses streaming so large + * files don't need to be held in memory. + * + * @param files - CSV file paths to scan + * @returns headers and uniqueValuesByColumn + */ +async function scanCsvFiles(files: string[]): Promise<{ + /** Union of all column headers */ + headers: string[]; + /** Map of column name to its unique values (trimmed, non-empty) */ + uniqueValuesByColumn: Record>; +}> { + const allHeaders = new Set(); + const uniqueValuesByColumn: Record> = {}; + + for (const file of files) { + await new Promise((resolve, reject) => { + const parser = createReadStream(file).pipe( + csvParse({ columns: true, skip_empty_lines: true }), + ); + parser.on('data', (row: Record) => { + for (const [col, val] of Object.entries(row)) { + allHeaders.add(col); + if (!uniqueValuesByColumn[col]) { + uniqueValuesByColumn[col] = new Set(); + } + const trimmed = (val || '').trim(); + if (trimmed) { + uniqueValuesByColumn[col].add(trimmed); + } + } + }); + parser.on('end', resolve); + parser.on('error', reject); + }); + } + + return { + headers: [...allHeaders], + uniqueValuesByColumn, + }; +} + +/** + * Build synthetic preference rows from the scanned unique values so + * the existing parse functions see every value at least once. + * + * @param headers - all column headers + * @param uniqueValuesByColumn - unique values per column + * @returns synthetic rows covering all unique values + */ +function buildSyntheticRows( + headers: string[], + uniqueValuesByColumn: Record>, +): Record[] { + const maxRows = Math.max( + 1, + ...headers.map((h) => uniqueValuesByColumn[h]?.size ?? 0), + ); + const rows: Record[] = []; + for (let i = 0; i < maxRows; i += 1) { + const row: Record = {}; + for (const h of headers) { + const vals = uniqueValuesByColumn[h] + ? [...uniqueValuesByColumn[h]] + : ['']; + row[h] = vals[i % vals.length] ?? ''; + } + rows.push(row); + } + return rows; +} + +/** + * Interactively configure the column mapping for preference CSV uploads. + * + * Scans ALL CSV files in a directory, discovers every header and unique value, + * then walks the user through mapping identifiers, ignored columns, timestamps, + * and purpose/preference value mappings. Saves the result as a reusable config. + * + * @param flags - CLI flags + */ +export async function configurePreferenceUpload( + this: LocalContext, + flags: ConfigurePreferenceUploadFlags, +): Promise { + const { auth, transcendUrl, directory, schemaFilePath } = flags; + + const files = collectCsvFilesOrExit(directory, this); + doneInputValidation(this.process.exit); + + logger.info( + colors.green( + `Scanning ${files.length} CSV file(s) for headers and unique values...`, + ), + ); + + // 1) Scan all files to discover the full column/value universe + const { headers, uniqueValuesByColumn } = await scanCsvFiles(files); + logger.info( + colors.green(`Discovered ${headers.length} columns across all files.`), + ); + + // 2) Fetch org reference data + const client = buildTranscendGraphQLClient(transcendUrl, auth); + const { purposes, preferenceTopics, identifiers } = await loadReferenceData( + client, + ); + + const allIdentifierNames = identifiers.map((id) => id.name); + logger.info( + colors.green( + `Loaded ${purposes.length} purposes, ${preferenceTopics.length} preference topics, ${identifiers.length} identifiers from org.`, + ), + ); + + // 3) Create or load persisted schema state + const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); + const initial = { + columnToPurposeName: {}, + lastFetchedAt: new Date().toISOString(), + columnToIdentifier: {}, + } as const; + const schemaState = new PersistedState(schemaFile, FileFormatState, initial); + + // 4) Interactive: select identifier columns + const existingIdentifierCols = Object.keys( + schemaState.getValue('columnToIdentifier'), + ); + let identifierColumns: string[]; + if (existingIdentifierCols.length > 0) { + logger.info( + colors.magenta( + `Existing identifier columns: ${existingIdentifierCols.join(', ')}`, + ), + ); + const { reuse } = await inquirer.prompt<{ reuse: boolean }>([ + { + name: 'reuse', + type: 'confirm', + message: `Keep existing identifier column selection? (${existingIdentifierCols.join( + ', ', + )})`, + default: true, + }, + ]); + identifierColumns = reuse + ? existingIdentifierCols + : ( + await inquirer.prompt<{ cols: string[] }>([ + { + name: 'cols', + type: 'checkbox', + message: 'Select columns that are identifiers', + choices: headers, + validate: (v: string[]) => + v.length > 0 || 'Select at least one identifier column', + }, + ]) + ).cols; + } else { + identifierColumns = ( + await inquirer.prompt<{ cols: string[] }>([ + { + name: 'cols', + type: 'checkbox', + message: 'Select columns that are identifiers', + choices: headers, + validate: (v: string[]) => + v.length > 0 || 'Select at least one identifier column', + }, + ]) + ).cols; + } + + // 5) Map identifier columns to org identifier names (reuses existing parse logic) + // We need a small sample of real rows so the identifier parser can validate. + const sampleRows = readCsv(files[0], t.record(t.string, t.string)); + await parsePreferenceIdentifiersFromCsv(sampleRows, { + schemaState, + orgIdentifiers: identifiers, + allowedIdentifierNames: allIdentifierNames, + identifierColumns, + }); + + // 6) Interactive: select columns to ignore + const mappedSoFar = [ + ...Object.keys(schemaState.getValue('columnToIdentifier')), + ]; + const remainingForIgnore = headers.filter((h) => !mappedSoFar.includes(h)); + const existingIgnored = schemaState.getValue('columnsToIgnore') ?? []; + + let columnsToIgnore: string[]; + if (existingIgnored.length > 0) { + logger.info( + colors.magenta(`Existing ignored columns: ${existingIgnored.join(', ')}`), + ); + const { reuse } = await inquirer.prompt<{ reuse: boolean }>([ + { + name: 'reuse', + type: 'confirm', + message: `Keep existing ignored columns? (${existingIgnored.join( + ', ', + )})`, + default: true, + }, + ]); + columnsToIgnore = reuse + ? existingIgnored + : ( + await inquirer.prompt<{ cols: string[] }>([ + { + name: 'cols', + type: 'checkbox', + message: + 'Select columns to ignore (will not be mapped to purposes)', + choices: remainingForIgnore, + }, + ]) + ).cols; + } else { + columnsToIgnore = ( + await inquirer.prompt<{ cols: string[] }>([ + { + name: 'cols', + type: 'checkbox', + message: 'Select columns to ignore (will not be mapped to purposes)', + choices: remainingForIgnore, + }, + ]) + ).cols; + } + schemaState.setValue(columnsToIgnore, 'columnsToIgnore'); + + // 7) Build synthetic rows covering all unique values from every file + const syntheticRows = buildSyntheticRows(headers, uniqueValuesByColumn); + + // 8) Select timestamp column + await parsePreferenceFileFormatFromCsv(syntheticRows, schemaState); + + // 9) Map remaining columns to purposes/preferences + value mappings + await parsePreferenceAndPurposeValuesFromCsv(syntheticRows, schemaState, { + purposeSlugs: purposes.map((p) => p.trackingType), + preferenceTopics, + forceTriggerWorkflows: false, + columnsToIgnore, + }); + + // 10) Validate completeness + const identifierCols = Object.keys( + schemaState.getValue('columnToIdentifier'), + ); + const timestampCol = schemaState.getValue('timestampColumn'); + const purposeCols = Object.keys(schemaState.getValue('columnToPurposeName')); + const ignoredCols = schemaState.getValue('columnsToIgnore') ?? []; + const allMapped = new Set([ + ...identifierCols, + ...purposeCols, + ...ignoredCols, + ...(timestampCol ? [timestampCol] : []), + ]); + const unmapped = headers.filter((h) => !allMapped.has(h)); + if (unmapped.length > 0) { + logger.warn( + colors.yellow( + `Warning: the following columns are not mapped: ${unmapped.join( + ', ', + )}. ` + + 'They will cause errors during upload. Re-run this command to fix.', + ), + ); + } + + schemaState.setValue(new Date().toISOString(), 'lastFetchedAt'); + + logger.info(colors.green(`\nConfiguration saved to: ${schemaFile}`)); + logger.info( + colors.green( + ` Identifiers: ${identifierCols.join(', ')}\n` + + ` Ignored: ${ignoredCols.join(', ') || '(none)'}\n` + + ` Timestamp: ${timestampCol || '(none)'}\n` + + ` Purpose columns: ${purposeCols.join(', ')}`, + ), + ); +} diff --git a/src/commands/consent/routes.ts b/src/commands/consent/routes.ts index 1dede578..40bfb335 100644 --- a/src/commands/consent/routes.ts +++ b/src/commands/consent/routes.ts @@ -1,5 +1,6 @@ import { buildRouteMap } from '@stricli/core'; import { buildXdiSyncEndpointCommand } from './build-xdi-sync-endpoint/command'; +import { configurePreferenceUploadCommand } from './configure-preference-upload/command'; import { pullConsentMetricsCommand } from './pull-consent-metrics/command'; import { pullConsentPreferencesCommand } from './pull-consent-preferences/command'; import { updateConsentManagerCommand } from './update-consent-manager/command'; @@ -13,6 +14,7 @@ import { deletePreferenceRecordsCommand } from './delete-preference-records/comm export const consentRoutes = buildRouteMap({ routes: { 'build-xdi-sync-endpoint': buildXdiSyncEndpointCommand, + 'configure-preference-upload': configurePreferenceUploadCommand, 'generate-access-tokens': generateAccessTokensCommand, 'pull-consent-metrics': pullConsentMetricsCommand, 'pull-consent-preferences': pullConsentPreferencesCommand, diff --git a/src/commands/consent/upload-preferences/buildTaskOptions.ts b/src/commands/consent/upload-preferences/buildTaskOptions.ts index b601723d..696f1ff8 100644 --- a/src/commands/consent/upload-preferences/buildTaskOptions.ts +++ b/src/commands/consent/upload-preferences/buildTaskOptions.ts @@ -22,10 +22,6 @@ export type TaskCommonOpts = Pick< | 'dryRun' | 'attributes' | 'forceTriggerWorkflows' - | 'allowedIdentifierNames' - | 'identifierColumns' - | 'columnsToIgnore' - | 'skipMetadata' > & { schemaFile: string; receiptsFolder: string; @@ -58,15 +54,11 @@ export function buildCommonOpts( dryRun, attributes, forceTriggerWorkflows, - allowedIdentifierNames, - identifierColumns, uploadConcurrency, maxChunkSize, rateLimitRetryDelay, maxRecordsToReceipt, uploadLogInterval, - columnsToIgnore = [], - skipMetadata = false, } = flags; return { @@ -85,14 +77,10 @@ export function buildCommonOpts( dryRun, attributes, forceTriggerWorkflows, - allowedIdentifierNames, - identifierColumns, uploadConcurrency, maxChunkSize, rateLimitRetryDelay, maxRecordsToReceipt, uploadLogInterval, - columnsToIgnore, - skipMetadata, }; } diff --git a/src/commands/consent/upload-preferences/command.ts b/src/commands/consent/upload-preferences/command.ts index c0ab1e05..32eddcaf 100644 --- a/src/commands/consent/upload-preferences/command.ts +++ b/src/commands/consent/upload-preferences/command.ts @@ -158,31 +158,20 @@ export const uploadPreferencesCommand = buildCommand({ 'This is to avoid the receipt file getting too large for JSON.parse/stringify.', default: '10', }, - // FIXME - allowedIdentifierNames: { - kind: 'parsed', - parse: (value: string) => value.split(',').map((s) => s.trim()), - brief: - 'Identifiers configured for the run. Comma-separated list of identifier names.', - }, - identifierColumns: { - kind: 'parsed', - parse: (value: string) => value.split(',').map((s) => s.trim()), + regenerate: { + kind: 'boolean', brief: - 'Columns in the CSV that should be used as identifiers. Comma-separated list of column names.', + 'Force re-generation of the schema config file before uploading. ' + + 'Runs the interactive configure flow even if a config already exists.', + default: false, }, - columnsToIgnore: { + chunkSizeMB: { kind: 'parsed', - parse: (value: string) => value.split(',').map((s) => s.trim()), - brief: - 'Columns in the CSV that should be ignored. Comma-separated list of column names.', - optional: true, - }, - skipMetadata: { - kind: 'boolean', + parse: numberParser, brief: - 'Whether to skip uploading metadata fields. Use this for subsequent batch uploads to avoid replacing existing metadata.', - default: false, + 'Auto-chunk threshold in MB. Any CSV file larger than this will be ' + + 'split into smaller files before uploading. Set to 0 to disable.', + default: '10', }, viewerMode: { kind: 'boolean', @@ -196,9 +185,12 @@ export const uploadPreferencesCommand = buildCommand({ brief: 'Upload preference management data to your Preference Store', fullDescription: `Upload preference management data to your Preference Store. -This 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. +Requires a config file (generated by 'configure-preference-upload') that maps +CSV columns to identifiers, purposes, and preferences. If no config exists, +pass --regenerate to run the interactive configure flow first. -The script will also produce a JSON cache file that allows for the mappings to be preserved between runs. +Large files are automatically chunked into smaller pieces (controlled by +--chunkSizeMB) before uploading. Parallel preference uploader (Node 22+ ESM/TS) ----------------------------------------------------------------------------- diff --git a/src/commands/consent/upload-preferences/impl.ts b/src/commands/consent/upload-preferences/impl.ts index a69b0886..5856435e 100644 --- a/src/commands/consent/upload-preferences/impl.ts +++ b/src/commands/consent/upload-preferences/impl.ts @@ -2,9 +2,11 @@ import type { LocalContext } from '../../../context'; import colors from 'colors'; import { logger } from '../../../logger'; import { join } from 'node:path'; +import { statSync, existsSync } from 'node:fs'; import { doneInputValidation } from '../../../lib/cli/done-input-validation'; import { collectCsvFilesOrExit } from '../../../lib/helpers/collectCsvFilesOrExit'; +import { chunkOneCsvFile } from '../../../lib/helpers/chunkOneCsvFile'; import { computePoolSize, @@ -118,10 +120,8 @@ export interface UploadPreferencesCommandFlags { uploadLogInterval: number; downloadIdentifierConcurrency: number; maxRecordsToReceipt: number; - allowedIdentifierNames: string[]; - identifierColumns: string[]; - columnsToIgnore?: string[]; - skipMetadata: boolean; + regenerate: boolean; + chunkSizeMB: number; viewerMode: boolean; } @@ -151,33 +151,50 @@ export async function uploadPreferences( sombraAuth, transcendUrl, directory, - dryRun, skipExistingRecordCheck, receiptFileDir, schemaFilePath, - isSilent, concurrency, - attributes, - receiptFilepath, - uploadConcurrency, - maxChunkSize, - rateLimitRetryDelay, - uploadLogInterval, - downloadIdentifierConcurrency, - maxRecordsToReceipt, - allowedIdentifierNames, - identifierColumns, - columnsToIgnore, - skipWorkflowTriggers, - forceTriggerWorkflows, - skipConflictUpdates, + regenerate, + chunkSizeMB, viewerMode, } = flags; /* 1) Validate & find inputs */ - const files = collectCsvFilesOrExit(directory, this); + let files = collectCsvFilesOrExit(directory, this); doneInputValidation(this.process.exit); + /* 1b) Auto-chunk oversized files */ + if (chunkSizeMB > 0) { + const chunkThreshold = chunkSizeMB * 1024 * 1024; + const oversized = files.filter((f) => { + try { + return statSync(f).size > chunkThreshold; + } catch { + return false; + } + }); + if (oversized.length > 0) { + logger.info( + colors.yellow( + `Auto-chunking ${oversized.length} file(s) exceeding ${chunkSizeMB}MB...`, + ), + ); + for (const file of oversized) { + await chunkOneCsvFile({ + filePath: file, + outputDir: directory, + clearOutputDir: false, + chunkSizeMB, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onProgress: () => {}, + }); + } + // Re-collect after chunking (new chunk files will be in the directory) + files = collectCsvFilesOrExit(directory, this); + } + } + logger.info( colors.green( `Processing ${files.length} consent preferences files for partition: ${partition}`, @@ -196,39 +213,40 @@ export async function uploadPreferences( const receiptsFolder = computeReceiptsFolder(receiptFileDir, directory); const schemaFile = computeSchemaFile(schemaFilePath, directory, files[0]); + /* 1c) Auto-configure if needed */ + const configExists = existsSync(schemaFile); + if (!configExists || regenerate) { + if (!configExists && !regenerate) { + logger.error( + colors.red( + `No config file found at: ${schemaFile}\n` + + "Run 'transcend consent configure-preference-upload' to create one, " + + 'or pass --regenerate to run the interactive setup now.', + ), + ); + this.process.exit(1); + } + if (regenerate) { + logger.info(colors.yellow('Running interactive config generation...')); + const { configurePreferenceUpload } = await import( + '../configure-preference-upload/impl' + ); + await configurePreferenceUpload.call(this, { + auth, + sombraAuth, + transcendUrl, + directory, + schemaFilePath, + partition, + }); + } + } + /* 2) Pool size */ const { poolSize, cpuCount } = computePoolSize(concurrency, files.length); /* 3) Build shared worker options and queue */ - const common = buildCommonOpts( - { - ...flags, - // explicit for clarity (even if buildCommonOpts infers these): - auth, - partition, - sombraAuth, - transcendUrl, - dryRun, - skipExistingRecordCheck, - skipWorkflowTriggers, - forceTriggerWorkflows, - skipConflictUpdates, - isSilent, - attributes, - receiptFilepath, - uploadConcurrency, - maxChunkSize, - rateLimitRetryDelay, - uploadLogInterval, - downloadIdentifierConcurrency, - maxRecordsToReceipt, - allowedIdentifierNames, - identifierColumns, - columnsToIgnore, - }, - schemaFile, - receiptsFolder, - ); + const common = buildCommonOpts(flags, schemaFile, receiptsFolder); // FIFO queue: one task per file const queue = files.map((filePath) => ({ diff --git a/src/commands/consent/upload-preferences/schemaState.ts b/src/commands/consent/upload-preferences/schemaState.ts index 816afd74..1e7a28b1 100644 --- a/src/commands/consent/upload-preferences/schemaState.ts +++ b/src/commands/consent/upload-preferences/schemaState.ts @@ -19,6 +19,8 @@ export interface PreferenceSchemaInterface { getColumnToIdentifier(): ColumnIdentifierMap; /** CSV column name -> Metadata key mapping */ getColumnToMetadata(): ColumnMetadataMap | undefined; + /** CSV columns to ignore during upload */ + getColumnsToIgnore(): string[]; /** The persisted cache */ // FIXME remove this state: PersistedState; } @@ -87,6 +89,8 @@ export async function makeSchemaState( state.getValue('columnToIdentifier'), getColumnToMetadata: (): ColumnMetadataMap | undefined => state.getValue('columnToMetadata'), + getColumnsToIgnore: (): string[] => + state.getValue('columnsToIgnore') ?? [], }; } catch (err) { throw new Error( diff --git a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts index b678420e..f88f1567 100644 --- a/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts +++ b/src/commands/consent/upload-preferences/upload/buildInteractiveUploadPlan.ts @@ -76,6 +76,7 @@ export async function buildInteractiveUploadPreferencePlan({ identifierColumns, columnsToIgnore = [], attributes = [], + nonInteractive = false, onProgress, }: { /** Transcend GraphQL client */ @@ -108,6 +109,8 @@ export async function buildInteractiveUploadPreferencePlan({ identifierDownloadLogInterval?: number; /** Maximum records to write out to the receipt file */ maxRecordsToReceipt?: number; + /** When true, throw instead of prompting (for worker processes) */ + nonInteractive?: boolean; /** on progress callback */ onProgress?: (info: PreferenceUploadProgress) => void; }): Promise { @@ -126,7 +129,7 @@ export async function buildInteractiveUploadPreferencePlan({ ); // Build clients + reference data (purposes/topics/identifiers) - const references = await loadReferenceData(client, forceTriggerWorkflows); + const references = await loadReferenceData(client); // Read in the file logger.info(colors.magenta(`Reading in file: "${file}"`)); @@ -151,6 +154,7 @@ export async function buildInteractiveUploadPreferencePlan({ identifierDownloadLogInterval, columnsToIgnore, onProgress, + nonInteractive, }, schema.state, ); diff --git a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts index 1d2446f4..246798e7 100644 --- a/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts +++ b/src/commands/consent/upload-preferences/upload/interactivePreferenceUploaderFromPlan.ts @@ -42,7 +42,6 @@ export async function interactivePreferenceUploaderFromPlan( skipWorkflowTriggers = false, skipConflictUpdates = false, forceTriggerWorkflows = false, - skipMetadata = false, uploadLogInterval = 1_000, maxChunkSize = 25, uploadConcurrency = 20, @@ -63,8 +62,6 @@ export async function interactivePreferenceUploaderFromPlan( skipConflictUpdates?: boolean; /** Force triggering workflows for each update (use sparingly) */ forceTriggerWorkflows?: boolean; - /** Skip uploading metadata fields to avoid replacing existing metadata */ - skipMetadata?: boolean; /** Log/persist cadence for progress updates */ uploadLogInterval?: number; /** Max records in a single batch PUT to v1/preferences */ @@ -86,8 +83,7 @@ export async function interactivePreferenceUploaderFromPlan( timestampColumn: schema.timestampColumn, columnToPurposeName: schema.columnToPurposeName, columnToIdentifier: schema.columnToIdentifier, - // Skip metadata when flag is set to avoid replacing existing metadata - columnToMetadata: skipMetadata ? undefined : schema.columnToMetadata, + columnToMetadata: schema.columnToMetadata, preferenceTopics, purposes, partition, diff --git a/src/commands/consent/upload-preferences/worker.ts b/src/commands/consent/upload-preferences/worker.ts index 6c6def79..aaf7ead9 100644 --- a/src/commands/consent/upload-preferences/worker.ts +++ b/src/commands/consent/upload-preferences/worker.ts @@ -85,7 +85,16 @@ export async function runChild(): Promise { options.sombraAuth, ); - // Step 1: Build the upload plan (validation-only) + // Derive identifierColumns and columnsToIgnore from config + const columnToIdentifier = schema.getColumnToIdentifier(); + const identifierColumns = Object.keys(columnToIdentifier); + const allowedIdentifierNames = [ + ...new Set(Object.values(columnToIdentifier).map((v) => v.name)), + ]; + const columnsToIgnore = + schema.state.getValue('columnsToIgnore') ?? []; + + // Step 1: Build the upload plan (validation-only, non-interactive) const plan = await buildInteractiveUploadPreferencePlan({ sombra, client, @@ -98,11 +107,12 @@ export async function runChild(): Promise { options.downloadIdentifierConcurrency, skipExistingRecordCheck: options.skipExistingRecordCheck, forceTriggerWorkflows: options.forceTriggerWorkflows, - allowedIdentifierNames: options.allowedIdentifierNames, + allowedIdentifierNames, maxRecordsToReceipt: options.maxRecordsToReceipt, - identifierColumns: options.identifierColumns, - columnsToIgnore: options.columnsToIgnore, + identifierColumns, + columnsToIgnore, attributes: splitCsvToList(options.attributes), + nonInteractive: true, // Report progress to parent process onProgress: ({ successTotal, fileTotal }) => { process.send?.({ @@ -125,7 +135,6 @@ export async function runChild(): Promise { skipWorkflowTriggers: options.skipWorkflowTriggers, skipConflictUpdates: options.skipConflictUpdates, forceTriggerWorkflows: options.forceTriggerWorkflows, - skipMetadata: options.skipMetadata, uploadLogInterval: options.uploadLogInterval, maxChunkSize: options.maxChunkSize, uploadConcurrency: options.uploadConcurrency, diff --git a/src/lib/preference-management/codecs.ts b/src/lib/preference-management/codecs.ts index fd29fcfa..76f3b0c6 100644 --- a/src/lib/preference-management/codecs.ts +++ b/src/lib/preference-management/codecs.ts @@ -118,6 +118,8 @@ export const FileFormatState = t.intersection([ timestampColumn: t.string, /** Mapping of CSV column names to metadata keys */ columnToMetadata: ColumnMetadataMap, + /** CSV columns that should be ignored during upload */ + columnsToIgnore: t.array(t.string), }), ]); @@ -226,61 +228,17 @@ export type SkippedPreferenceUpdates = t.TypeOf< export const RequestUploadReceipts = t.type({ /** Last time the file was last parsed at */ lastFetchedAt: t.string, - /** - * Mapping of primaryKey to the rows in the file that need to be uploaded - * - * These uploads are overwriting non-existent preferences and are not in - * conflict with existing consent preferences. - * - * Note: If --skipExistingRecordCheck=true is set, there will not be on check - * for existing record conflicts in order to speed up the upload. - * So this will say the updates were safe when in fact we don't know. - * We just let the default consent resolution logic handle it. - */ + /** Safe updates (no conflict with existing preferences) keyed by primaryKey */ pendingSafeUpdates: PendingSafePreferenceUpdates, - /** - * Mapping of primaryKey to the rows in the file that need to be uploaded - * these records have conflicts with existing consent preferences. - * Normally the default consent resolution logic will handle these - * conflicts, but these are useful situations in which to investigate - * and ensure consent resolution is working as expected. - * - * Note: If --skipExistingRecordCheck=true is set, there will not be on check - * for existing record conflicts in order to speed up the upload. and this will - * be under-counted. - * - * Set to `--skipExistingRecordCheck=false --dryRun=true` to get the list of conflicts. - */ + /** Conflict updates (existing preferences differ) keyed by primaryKey */ pendingConflictUpdates: PendingWithConflictPreferenceUpdates, - /** - * Mapping of primaryKey to the rows in the file that can be skipped because - * their preferences are already in the store. These records may be skipped - * as they could be a duplicate row in the CSV file. - * - * If `--skipExistingRecordCheck=false` - then no-ops will be filtered out. - */ + /** Skipped rows (already in store or duplicates) keyed by primaryKey */ skippedUpdates: SkippedPreferenceUpdates, - /** - * The set of failing updates - * Mapping from primaryKey to the request payload, time upload happened - * and error message. - */ + /** Failed uploads keyed by primaryKey */ failingUpdates: FailingPreferenceUpdates, - /** - * The set of uploads that were pending at the time that the cache file - * was last written to. When using `--dryRun=true` this list will be full. - * - * When running `--dryRun=false` this set will shrink as updates are processed. - */ + /** Pending uploads at time of last cache write; shrinks as processed */ pendingUpdates: PreferenceUpdateMap, - /** - * The updates that were successfully processed - * Mapping from primaryKey to the request response. - * - * This will be empty if `--dryRun=true` is set. - * If `--dryRun=false` is set, this will contain - * the updates that were successfully processed. - */ + /** Successfully processed uploads keyed by primaryKey */ successfulUpdates: PreferenceUpdateMap, }); diff --git a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts index 1ee2bf5e..d472e708 100644 --- a/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceAndPurposeValuesFromCsv.ts @@ -25,6 +25,7 @@ export async function parsePreferenceAndPurposeValuesFromCsv( preferenceTopics, forceTriggerWorkflows, columnsToIgnore, + nonInteractive = false, }: { /** The purpose slugs that are allowed to be updated */ purposeSlugs: string[]; @@ -34,6 +35,8 @@ export async function parsePreferenceAndPurposeValuesFromCsv( forceTriggerWorkflows: boolean; /** Columns to ignore in the CSV file */ columnsToIgnore: string[]; + /** When true, throw instead of prompting (for worker processes) */ + nonInteractive?: boolean; }, ): Promise> { // Determine columns to map @@ -76,6 +79,13 @@ export async function parsePreferenceAndPurposeValuesFromCsv( ), ); } else { + if (nonInteractive) { + throw new Error( + `Column "${col}" has no purpose mapping in the config. ` + + "Run 'transcend consent configure-preference-upload' to update the config.", + ); + } + const { purposeName } = await inquirer.prompt<{ /** purpose name */ purposeName: string; @@ -106,6 +116,16 @@ export async function parsePreferenceAndPurposeValuesFromCsv( ); return; } + + if (nonInteractive) { + logger.warn( + colors.yellow( + `Value "${value}" for column "${col}" has no mapping in the config. Skipping.`, + ), + ); + return; + } + // if preference is null, this column is just for the purpose if (purposeMapping.preference === null) { const { purposeValue } = await inquirer.prompt<{ diff --git a/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts b/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts index fc835e81..47aeaa6d 100644 --- a/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceFileFormatFromCsv.ts @@ -17,11 +17,17 @@ export const NONE_PREFERENCE_MAP = '[NONE]'; * * @param preferences - List of preferences * @param currentState - The current file metadata state for parsing this list + * @param options - Options * @returns The updated file metadata state */ export async function parsePreferenceFileFormatFromCsv( preferences: Record[], currentState: PersistedState, + { + nonInteractive = false, + }: { + /** When true, throw instead of prompting */ nonInteractive?: boolean; + } = {}, ): Promise> { // Determine columns to map const columnNames = uniq(preferences.map((x) => Object.keys(x)).flat()); @@ -34,6 +40,13 @@ export async function parsePreferenceFileFormatFromCsv( // Determine the timestamp column to work off of if (!currentState.getValue('timestampColumn')) { + if (nonInteractive) { + throw new Error( + 'No timestamp column configured. ' + + "Run 'transcend consent configure-preference-upload' to set it.", + ); + } + const { timestampName } = await inquirer.prompt<{ /** timestamp name */ timestampName: string; diff --git a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts index 0b1283a1..90396c61 100644 --- a/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts +++ b/src/lib/preference-management/parsePreferenceIdentifiersFromCsv.ts @@ -34,6 +34,7 @@ export async function parsePreferenceIdentifiersFromCsv( orgIdentifiers, allowedIdentifierNames, identifierColumns, + nonInteractive = false, }: { /** The current state of the schema metadata */ schemaState: PersistedState; @@ -43,6 +44,8 @@ export async function parsePreferenceIdentifiersFromCsv( allowedIdentifierNames: string[]; /** The columns in the CSV that should be used as identifiers */ identifierColumns: string[]; + /** When true, throw instead of prompting (for worker processes) */ + nonInteractive?: boolean; }, ): Promise<{ /** The updated state */ @@ -85,8 +88,8 @@ export async function parsePreferenceIdentifiersFromCsv( ).length === 0 ) { throw new Error( - 'No unique identifier we provided as part of allowedIdentifierNames. Please ensure that at least one of the allowed ' + - 'identifiers is configured as unique on the preference store.', + 'No unique identifier was provided. Please ensure that at least one ' + + 'of the allowed identifiers is configured as unique on the preference store.', ); } @@ -103,6 +106,14 @@ export async function parsePreferenceIdentifiersFromCsv( ); return; } + + if (nonInteractive) { + throw new Error( + `Column "${col}" has no identifier mapping in the config. ` + + "Run 'transcend consent configure-preference-upload' to update the config.", + ); + } + // If the column is not mapped, ask the user to map it const { identifierName } = await inquirer.prompt<{ /** Identifier name */ @@ -148,6 +159,10 @@ export async function parsePreferenceIdentifiersFromCsv( )}".`; logger.warn(colors.yellow(msg)); + if (nonInteractive) { + throw new Error(msg); + } + // Ask user if they would like to skip rows missing an identifier const skip = await inquirerConfirmBoolean({ message: 'Would you like to skip rows missing unique identifiers?', diff --git a/src/lib/preference-management/parsePreferenceManagementCsv.ts b/src/lib/preference-management/parsePreferenceManagementCsv.ts index 9570106c..a4b18f52 100644 --- a/src/lib/preference-management/parsePreferenceManagementCsv.ts +++ b/src/lib/preference-management/parsePreferenceManagementCsv.ts @@ -51,6 +51,7 @@ export async function parsePreferenceManagementCsvWithCache( identifierDownloadLogInterval, columnsToIgnore, onProgress, + nonInteractive = false, }: { /** File to parse */ file: string; @@ -80,6 +81,8 @@ export async function parsePreferenceManagementCsvWithCache( downloadIdentifierConcurrency: number; /** on progress callback */ onProgress?: (info: PreferenceUploadProgress) => void; + /** When true, throw instead of prompting (for worker processes) */ + nonInteractive?: boolean; }, schemaState: PersistedState, ): Promise<{ @@ -94,7 +97,9 @@ export async function parsePreferenceManagementCsvWithCache( const t0 = new Date().getTime(); // Validate that all timestamps are present in the file - await parsePreferenceFileFormatFromCsv(rawPreferences, schemaState); + await parsePreferenceFileFormatFromCsv(rawPreferences, schemaState, { + nonInteractive, + }); // Validate that all identifiers are present and unique const result = await parsePreferenceIdentifiersFromCsv(rawPreferences, { @@ -102,6 +107,7 @@ export async function parsePreferenceManagementCsvWithCache( orgIdentifiers, allowedIdentifierNames, identifierColumns, + nonInteractive, }); const { preferences } = result; @@ -111,6 +117,7 @@ export async function parsePreferenceManagementCsvWithCache( purposeSlugs, forceTriggerWorkflows, columnsToIgnore, + nonInteractive, }); // Grab existing preference store records From dbc3c4f65dcf5f426a4f06810310c4594695eece Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 24 Feb 2026 17:38:46 -0800 Subject: [PATCH 70/72] Move find-exact into a proper admin CLI command - Create `admin find-exact` command with stricli flags (--needle, --root, --exts, --noParquet, --concurrency, --maxBytes). - Fix all ESLint violations from the original script (JSDoc, no-plusplus, no-return-await, no-param-reassign, no-explicit-any, etc.). - Register in admin routes and delete the old standalone src/find-exact.ts. - Fix pre-existing ESLint issues in getPreferenceMetadataFromRow.ts and fetchConsentPreferencesChunked.test.ts. Co-authored-by: Cursor --- README.md | 26 ++ src/commands/admin/find-exact/command.ts | 71 ++++ src/commands/admin/find-exact/impl.ts | 292 +++++++++++++++ src/commands/admin/routes.ts | 2 + src/find-exact.ts | 341 ------------------ .../getPreferenceMetadataFromRow.ts | 8 +- .../fetchConsentPreferencesChunked.test.ts | 2 +- 7 files changed, 396 insertions(+), 346 deletions(-) create mode 100644 src/commands/admin/find-exact/command.ts create mode 100644 src/commands/admin/find-exact/impl.ts delete mode 100644 src/find-exact.ts diff --git a/README.md b/README.md index 8f1e066e..f3363329 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ A command line interface that allows you to programatically interact with the Tr - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) - [`transcend admin chunk-csv`](#transcend-admin-chunk-csv) + - [`transcend admin find-exact`](#transcend-admin-find-exact) - [`transcend admin parquet-to-csv`](#transcend-admin-parquet-to-csv) - [`transcend migration sync-ot`](#transcend-migration-sync-ot) - [Prompt Manager](#prompt-manager) @@ -3485,6 +3486,31 @@ transcend admin chunk-csv \ transcend admin chunk-csv --directory=./working/files ``` +### `transcend admin find-exact` + +```txt +USAGE + transcend admin find-exact (--needle value) [--root value] [--exts value] [--noParquet] [--concurrency value] [--maxBytes value] + transcend admin find-exact --help + +Recursively searches a directory for files that contain the given needle string (case-insensitive). + +Supported file types: +- Text-based files (csv, json, txt, ndjson, log, etc.) are scanned via streaming byte comparison. +- Parquet files are scanned via DuckDB (must be on PATH unless --noParquet is set). + +Outputs one matching file path per line to stdout as hits are found. + +FLAGS + --needle The exact string to search for (case-insensitive) + [--root] Root directory to search [default = .] + [--exts] Comma-separated file extensions to search (without leading dots) [default = csv,json,txt,ndjson,log] + [--noParquet] Skip parquet file scanning (requires duckdb on PATH) [default = false] + [--concurrency] Max number of files to scan concurrently [default = 16] + [--maxBytes] Stop scanning each file after this many bytes (useful for huge files) + -h --help Print help information and exit +``` + ### `transcend admin parquet-to-csv` ```txt diff --git a/src/commands/admin/find-exact/command.ts b/src/commands/admin/find-exact/command.ts new file mode 100644 index 00000000..e4d94687 --- /dev/null +++ b/src/commands/admin/find-exact/command.ts @@ -0,0 +1,71 @@ +import { buildCommand } from '@stricli/core'; + +export const findExactCommand = buildCommand({ + loader: async () => { + const { findExact } = await import('./impl'); + return findExact; + }, + parameters: { + flags: { + needle: { + kind: 'parsed', + parse: String, + brief: 'The exact string to search for (case-insensitive)', + }, + root: { + kind: 'parsed', + parse: String, + brief: 'Root directory to search', + default: '.', + }, + exts: { + kind: 'parsed', + parse: String, + brief: + 'Comma-separated file extensions to search (without leading dots)', + default: 'csv,json,txt,ndjson,log', + }, + noParquet: { + kind: 'boolean', + brief: 'Skip parquet file scanning (requires duckdb on PATH)', + default: false, + }, + concurrency: { + kind: 'parsed', + parse: (v: string) => { + const n = Number(v); + if (!Number.isFinite(n) || n <= 0) { + throw new Error('concurrency must be a positive number'); + } + return n; + }, + brief: 'Max number of files to scan concurrently', + default: '16', + }, + maxBytes: { + kind: 'parsed', + parse: (v: string) => { + const n = Number(v); + if (!Number.isFinite(n) || n <= 0) { + throw new Error('maxBytes must be a positive number'); + } + return n; + }, + brief: + 'Stop scanning each file after this many bytes (useful for huge files)', + optional: true, + }, + }, + }, + docs: { + brief: + 'Search files for an exact string match across CSV, JSON, text, and parquet files', + fullDescription: `Recursively searches a directory for files that contain the given needle string (case-insensitive). + +Supported file types: +- Text-based files (csv, json, txt, ndjson, log, etc.) are scanned via streaming byte comparison. +- Parquet files are scanned via DuckDB (must be on PATH unless --noParquet is set). + +Outputs one matching file path per line to stdout as hits are found.`, + }, +}); diff --git a/src/commands/admin/find-exact/impl.ts b/src/commands/admin/find-exact/impl.ts new file mode 100644 index 00000000..4d13459e --- /dev/null +++ b/src/commands/admin/find-exact/impl.ts @@ -0,0 +1,292 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; +import fg from 'fast-glob'; +import colors from 'colors'; +import type { LocalContext } from '../../../context'; +import { logger } from '../../../logger'; +import { doneInputValidation } from '../../../lib/cli/done-input-validation'; + +/** CLI flags accepted by the `find-exact` command. */ +export type FindExactCommandFlags = { + /** The exact string to search for */ + needle: string; + /** Root directory to search */ + root: string; + /** Comma-separated file extensions */ + exts: string; + /** Skip parquet file scanning */ + noParquet: boolean; + /** Max concurrent file scans */ + concurrency: number; + /** Stop scanning each file after N bytes */ + maxBytes?: number; +}; + +/** + * Streams through a file checking if it contains the needle (case-insensitive). + * + * @param filePath - Absolute path to the file to scan + * @param needle - Lowercased needle as a Buffer + * @param maxBytes - Optional byte limit per file + * @returns Whether the file contains the needle + */ +function fileContainsExactBytes( + filePath: string, + needle: Buffer, + maxBytes?: number, +): Promise { + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(filePath); + let carry = Buffer.alloc(0); + const n = needle.length; + let seen = 0; + + stream.on('data', (raw: Buffer) => { + let chunk = raw; + + if (maxBytes) { + const remaining = maxBytes - seen; + if (remaining <= 0) { + stream.destroy(); + resolve(false); + return; + } + if (chunk.length > remaining) { + chunk = chunk.subarray(0, remaining); + } + seen += chunk.length; + } + + const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk; + const haystack = buf.toString('utf8').toLowerCase(); + if (haystack.includes(needle.toString('utf8'))) { + stream.destroy(); + resolve(true); + return; + } + + // Keep last n-1 bytes to catch boundary matches + if (n > 1) { + carry = Buffer.from(buf.subarray(Math.max(0, buf.length - (n - 1)))); + } else { + carry = Buffer.alloc(0); + } + }); + + stream.on('error', reject); + stream.on('close', () => resolve(false)); + stream.on('end', () => resolve(false)); + }); +} + +/** + * Run async workers over items with bounded concurrency. + * + * @param items - Array of items to process + * @param limit - Maximum concurrent workers + * @param worker - Async function to run per item + * @returns Resolves when all items are processed + */ +async function runWithConcurrency( + items: T[], + limit: number, + worker: (item: T) => Promise, +): Promise { + let idx = 0; + const runners = Array.from( + { length: Math.min(limit, items.length) }, + async () => { + // eslint-disable-next-line no-constant-condition + while (true) { + const current = idx; + idx += 1; + if (current >= items.length) return; + await worker(items[current]); + } + }, + ); + await Promise.all(runners); +} + +/** + * Execute a DuckDB query and return stdout. + * + * @param duckdbPath - Path to the duckdb binary + * @param sql - SQL query to execute + * @returns The stdout output from duckdb + */ +function duckdbQuery(duckdbPath: string, sql: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn(duckdbPath, ['-noheader', '-batch', '-cmd', sql], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (d) => { + stdout += String(d); + }); + child.stderr.on('data', (d) => { + stderr += String(d); + }); + + child.on('error', reject); + child.on('close', (code) => { + if (code === 0) resolve(stdout); + else reject(new Error(`duckdb exited ${code}: ${stderr}`)); + }); + }); +} + +/** + * Get all VARCHAR/STRING column names from a parquet file. + * + * @param duckdbPath - Path to the duckdb binary + * @param filePath - Absolute path to the parquet file + * @returns Array of string column names + */ +async function duckdbGetParquetStringColumns( + duckdbPath: string, + filePath: string, +): Promise { + const escaped = filePath.replace(/'/g, "''"); + const sql = [ + 'SELECT column_name', + `FROM parquet_schema('${escaped}')`, + "WHERE lower(column_type) LIKE '%varchar%'", + " OR lower(column_type) LIKE '%string%';", + ].join('\n'); + + const out = await duckdbQuery(duckdbPath, sql); + return out + .split('\n') + .map((l) => l.trim()) + .filter(Boolean); +} + +/** + * Check if any string column in a parquet file contains the needle value. + * + * @param duckdbPath - Path to the duckdb binary + * @param filePath - Absolute path to the parquet file + * @param needle - The string to search for (exact equality per column) + * @returns Whether any row/column matches + */ +async function parquetFileHasExactString( + duckdbPath: string, + filePath: string, + needle: string, +): Promise { + const cols = await duckdbGetParquetStringColumns(duckdbPath, filePath); + if (cols.length === 0) return false; + + const escaped = filePath.replace(/'/g, "''"); + const orChain = cols + .map((c) => `"${c.replace(/"/g, '""')}" = '${needle.replace(/'/g, "''")}'`) + .join(' OR '); + + const sql = [ + `SELECT 1 AS hit FROM read_parquet('${escaped}')`, + `WHERE ${orChain}`, + 'LIMIT 1;', + ].join('\n'); + + const out = await duckdbQuery(duckdbPath, sql); + return out.trim().length > 0; +} + +/** + * Entrypoint for the `admin find-exact` command. + * + * Recursively searches files under --root for the --needle string, outputting + * matching file paths. Supports text-based files via streaming byte scan and + * parquet files via DuckDB. + * + * @param this - Bound CLI context + * @param flags - CLI flags for the run + */ +export async function findExact( + this: LocalContext, + flags: FindExactCommandFlags, +): Promise { + doneInputValidation(this.process.exit); + + const { needle, root, exts, noParquet, concurrency, maxBytes } = flags; + const rootAbs = path.resolve(root); + + const extSet = new Set( + exts + .split(',') + .map((x) => x.trim().replace(/^\./, '').toLowerCase()) + .filter(Boolean), + ); + const patterns = Array.from(extSet).map((e) => `**/*.${e}`); + + logger.info( + colors.green( + `Searching for "${needle}" in ${rootAbs} (exts: ${[...extSet].join( + ', ', + )})`, + ), + ); + + const normalFiles = await fg(patterns, { + cwd: rootAbs, + absolute: true, + onlyFiles: true, + followSymbolicLinks: false, + suppressErrors: true, + }); + + const needleBuf = Buffer.from(needle.toLowerCase(), 'utf8'); + const hits: string[] = []; + + await runWithConcurrency(normalFiles, concurrency, async (file) => { + try { + const ok = await fileContainsExactBytes(file, needleBuf, maxBytes); + if (ok) { + hits.push(file); + this.process.stdout.write(`${file}\n`); + } + } catch { + // ignore unreadable files + } + }); + + if (!noParquet) { + const parquetFiles = await fg(['**/*.parquet'], { + cwd: rootAbs, + absolute: true, + onlyFiles: true, + followSymbolicLinks: false, + suppressErrors: true, + }); + + if (parquetFiles.length > 0) { + logger.info( + colors.green( + `Scanning ${parquetFiles.length} parquet file(s) via DuckDB...`, + ), + ); + + await runWithConcurrency( + parquetFiles, + Math.max(2, Math.floor(concurrency / 4)), + async (file) => { + try { + const ok = await parquetFileHasExactString('duckdb', file, needle); + if (ok) { + hits.push(file); + this.process.stdout.write(`${file}\n`); + } + } catch { + // ignore parquet read issues + } + }, + ); + } + } + + logger.info(colors.green(`Done. Found ${hits.length} matching file(s).`)); +} diff --git a/src/commands/admin/routes.ts b/src/commands/admin/routes.ts index b97e74ec..730beec5 100644 --- a/src/commands/admin/routes.ts +++ b/src/commands/admin/routes.ts @@ -1,12 +1,14 @@ import { buildRouteMap } from '@stricli/core'; import { generateApiKeysCommand } from './generate-api-keys/command'; import { chunkCsvCommand } from './chunk-csv/command'; +import { findExactCommand } from './find-exact/command'; import { parquetToCsvCommand } from './parquet-to-csv/command'; export const adminRoutes = buildRouteMap({ routes: { 'generate-api-keys': generateApiKeysCommand, 'chunk-csv': chunkCsvCommand, + 'find-exact': findExactCommand, 'parquet-to-csv': parquetToCsvCommand, }, docs: { diff --git a/src/find-exact.ts b/src/find-exact.ts deleted file mode 100644 index 5b467b40..00000000 --- a/src/find-exact.ts +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env node -import fs from 'node:fs'; -import path from 'node:path'; -import { spawn } from 'node:child_process'; -import fg from 'fast-glob'; - -/** - * - */ -type Options = { - /** */ - root: string; - /** */ - needle: string; - /** */ - exts: Set; - /** */ - includeParquet: boolean; - /** */ - concurrency: number; - /** */ - maxBytes?: number; // optional: stop scanning a file after N bytes -}; - -/** - * - */ -function parseArgs(): Options { - const args = process.argv.slice(2); - - const get = (flag: string) => { - const idx = args.indexOf(flag); - if (idx === -1) return undefined; - return args[idx + 1]; - }; - - const root = get('--root') ?? '.'; - const needle = get('--needle'); - if (!needle) { - console.error('Missing --needle "..."'); - process.exit(2); - } - - const extsRaw = get('--exts') ?? 'csv,json,txt,ndjson,log'; - const includeParquet = !args.includes('--no-parquet'); - const concurrency = Number(get('--concurrency') ?? '16'); - const maxBytesStr = get('--max-bytes'); - const maxBytes = maxBytesStr ? Number(maxBytesStr) : undefined; - - return { - root, - needle, - exts: new Set( - extsRaw - .split(',') - .map((x) => x.trim().replace(/^\./, '').toLowerCase()) - .filter(Boolean), - ), - includeParquet, - concurrency: - Number.isFinite(concurrency) && concurrency > 0 ? concurrency : 16, - maxBytes: maxBytes && maxBytes > 0 ? maxBytes : undefined, - }; -} - -/** - * - * @param filePath - * @param needle - * @param maxBytes - */ -async function fileContainsExactBytes( - filePath: string, - needle: Buffer, - maxBytes?: number, -): Promise { - return await new Promise((resolve, reject) => { - const stream = fs.createReadStream(filePath); - let carry = Buffer.alloc(0); - const n = needle.length; - - let seen = 0; - - stream.on('data', (chunk: Buffer) => { - if (maxBytes) { - const remaining = maxBytes - seen; - if (remaining <= 0) { - stream.destroy(); - resolve(false); - return; - } - if (chunk.length > remaining) { - chunk = chunk.subarray(0, remaining); - } - seen += chunk.length; - } - - const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk; - const haystack = buf.toString('utf8').toLowerCase(); - if (haystack.includes(needle.toString('utf8'))) { - stream.destroy(); - resolve(true); - return; - } - - // keep last n-1 bytes to catch boundary matches - if (n > 1) { - carry = buf.subarray(Math.max(0, buf.length - (n - 1))) as any; - } else { - carry = Buffer.alloc(0); - } - }); - - stream.on('error', reject); - stream.on('close', () => resolve(false)); - stream.on('end', () => resolve(false)); - }); -} - -/** - * - * @param items - * @param limit - * @param worker - */ -async function runWithConcurrency( - items: T[], - limit: number, - worker: (item: T) => Promise, -): Promise { - let i = 0; - const runners = Array.from( - { length: Math.min(limit, items.length) }, - async () => { - while (true) { - const idx = i++; - if (idx >= items.length) return; - await worker(items[idx]); - } - }, - ); - await Promise.all(runners); -} - -/** - * Parquet search strategy: - * - DuckDB can read parquet quickly. - * - We ask DuckDB to scan each parquet file and return 1 row if any string column contains the needle. - * - * Note: exact byte match inside parquet isn’t meaningful (compressed/encoded). We do exact string match: - * col = 'needle' OR contains(col, 'needle') depending on your preference. - * - * Here: we use exact equality on ANY string column. (You can change to LIKE/contains if you want.) - * - * @param duckdbPath - * @param filePath - * @param needle - */ -async function parquetFileHasExactString( - duckdbPath: string, - filePath: string, - needle: string, -): Promise { - // Build a DuckDB query that: - // 1) introspects schema - // 2) checks any VARCHAR column for equality to needle - // - // We do it in a single DuckDB invocation for the file. - const sql = ` -WITH cols AS ( - SELECT column_name - FROM parquet_schema('${filePath.replace(/'/g, "''")}') - WHERE lower(column_type) LIKE '%varchar%' OR lower(column_type) LIKE '%string%' -), -q AS ( - SELECT 1 AS hit - FROM read_parquet('${filePath.replace(/'/g, "''")}') - WHERE ${/* OR-chain across string cols */ ''} - ${ - // If no string cols, make it FALSE - '(SELECT count(*) FROM cols) = 0' - } = FALSE - AND ( - ${'__OR_CHAIN__'} - ) - LIMIT 1 -) -SELECT hit FROM q; -`.trim(); - - // We need to replace __OR_CHAIN__ with OR conditions dynamically. - // DuckDB doesn't easily allow dynamic SQL without a second layer, so we do a 2-step: - // - First: get string column names - // - Second: run the OR query - const cols = await duckdbGetParquetStringColumns(duckdbPath, filePath); - if (cols.length === 0) return false; - - const orChain = cols - .map((c) => `"${c.replace(/"/g, '""')}" = '${needle.replace(/'/g, "''")}'`) - .join(' OR '); - - const finalSql = sql.replace('__OR_CHAIN__', orChain); - - const out = await duckdbQuery(duckdbPath, finalSql); - return out.trim().length > 0; // any output row means hit -} - -/** - * - * @param duckdbPath - * @param filePath - */ -async function duckdbGetParquetStringColumns( - duckdbPath: string, - filePath: string, -): Promise { - const sql = ` -SELECT column_name -FROM parquet_schema('${filePath.replace(/'/g, "''")}') -WHERE lower(column_type) LIKE '%varchar%' OR lower(column_type) LIKE '%string%'; -`.trim(); - const out = await duckdbQuery(duckdbPath, sql); - // output is tab-separated by default - return out - .split('\n') - .map((l) => l.trim()) - .filter(Boolean); -} - -/** - * - * @param duckdbPath - * @param sql - */ -async function duckdbQuery(duckdbPath: string, sql: string): Promise { - return await new Promise((resolve, reject) => { - const child = spawn(duckdbPath, ['-noheader', '-batch', '-cmd', sql], { - stdio: ['ignore', 'pipe', 'pipe'], - }); - - let stdout = ''; - let stderr = ''; - child.stdout.on('data', (d) => (stdout += String(d))); - child.stderr.on('data', (d) => (stderr += String(d))); - - child.on('error', reject); - child.on('close', (code) => { - if (code === 0) resolve(stdout); - else reject(new Error(`duckdb exited ${code}: ${stderr}`)); - }); - }); -} - -/** - * - */ -function findDuckdbBinary(): string | null { - // rely on PATH - return 'duckdb'; -} - -/** - * - */ -async function main() { - const opts = parseArgs(); - const rootAbs = path.resolve(opts.root); - - const patterns = Array.from(opts.exts).map((e) => `**/*.${e}`); - // always include parquet separately - const parquetPattern = '**/*.parquet'; - - const normalFiles = await fg(patterns, { - cwd: rootAbs, - absolute: true, - onlyFiles: true, - followSymbolicLinks: false, - suppressErrors: true, - }); - - const needleBuf = Buffer.from(opts.needle.toLowerCase(), 'utf8'); - - const hits: string[] = []; - await runWithConcurrency(normalFiles, opts.concurrency, async (file) => { - try { - const ok = await fileContainsExactBytes(file, needleBuf, opts.maxBytes); - if (ok) { - hits.push(file); - process.stdout.write(`${file}\n`); - } - } catch { - // ignore unreadable files - } - }); - - if (opts.includeParquet) { - const duckdbPath = findDuckdbBinary(); - if (!duckdbPath) { - console.error( - 'DuckDB not found in PATH; install duckdb or run with --no-parquet', - ); - process.exit(2); - } - - const parquetFiles = await fg([parquetPattern], { - cwd: rootAbs, - absolute: true, - onlyFiles: true, - followSymbolicLinks: false, - suppressErrors: true, - }); - - await runWithConcurrency( - parquetFiles, - Math.max(2, Math.floor(opts.concurrency / 4)), - async (file) => { - try { - const ok = await parquetFileHasExactString( - duckdbPath, - file, - opts.needle, - ); - if (ok) { - hits.push(file); - process.stdout.write(`${file}\n`); - } - } catch { - // ignore parquet read issues - } - }, - ); - } - - // If you want a summary at end: - // console.error(`Done. Hits: ${hits.length}`); -} - -main().catch((err) => { - console.error(err?.stack ?? String(err)); - process.exit(1); -}); diff --git a/src/lib/preference-management/getPreferenceMetadataFromRow.ts b/src/lib/preference-management/getPreferenceMetadataFromRow.ts index 24b24a32..2bb518c2 100644 --- a/src/lib/preference-management/getPreferenceMetadataFromRow.ts +++ b/src/lib/preference-management/getPreferenceMetadataFromRow.ts @@ -15,8 +15,8 @@ export function getPreferenceMetadataFromRow({ /** Mapping from CSV column name to metadata key */ columnToMetadata: ColumnMetadataMap; }): Array<{ - /** */ key: string /** */; - /** */ + /** Metadata key name */ key: string; + /** Metadata value from the CSV row */ value: string; }> { return Object.entries(columnToMetadata) @@ -32,8 +32,8 @@ export function getPreferenceMetadataFromRow({ ( x, ): x is { - /** */ key: string /** */; - /** */ + /** Metadata key name */ key: string; + /** Metadata value from the CSV row */ value: string; } => x !== null, ); diff --git a/src/lib/preference-management/tests/fetchConsentPreferencesChunked.test.ts b/src/lib/preference-management/tests/fetchConsentPreferencesChunked.test.ts index 7a1b7b53..1a41b519 100644 --- a/src/lib/preference-management/tests/fetchConsentPreferencesChunked.test.ts +++ b/src/lib/preference-management/tests/fetchConsentPreferencesChunked.test.ts @@ -51,7 +51,7 @@ const H = vi.hoisted(() => ({ AsyncGenerator >, makeIter: (pages: PreferenceQueryResponseItem[][]) => - // eslint-disable-next-line wrap-iife + // eslint-disable-next-line wrap-iife, func-names (async function* () { for (const p of pages) yield p; })(), From b53d5b3dfab9bab281dfe2cefbc42bb724236c97 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 24 Feb 2026 23:27:26 -0800 Subject: [PATCH 71/72] removes find-exact --- src/commands/admin/find-exact/command.ts | 71 ------ src/commands/admin/find-exact/impl.ts | 292 ----------------------- 2 files changed, 363 deletions(-) delete mode 100644 src/commands/admin/find-exact/command.ts delete mode 100644 src/commands/admin/find-exact/impl.ts diff --git a/src/commands/admin/find-exact/command.ts b/src/commands/admin/find-exact/command.ts deleted file mode 100644 index e4d94687..00000000 --- a/src/commands/admin/find-exact/command.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { buildCommand } from '@stricli/core'; - -export const findExactCommand = buildCommand({ - loader: async () => { - const { findExact } = await import('./impl'); - return findExact; - }, - parameters: { - flags: { - needle: { - kind: 'parsed', - parse: String, - brief: 'The exact string to search for (case-insensitive)', - }, - root: { - kind: 'parsed', - parse: String, - brief: 'Root directory to search', - default: '.', - }, - exts: { - kind: 'parsed', - parse: String, - brief: - 'Comma-separated file extensions to search (without leading dots)', - default: 'csv,json,txt,ndjson,log', - }, - noParquet: { - kind: 'boolean', - brief: 'Skip parquet file scanning (requires duckdb on PATH)', - default: false, - }, - concurrency: { - kind: 'parsed', - parse: (v: string) => { - const n = Number(v); - if (!Number.isFinite(n) || n <= 0) { - throw new Error('concurrency must be a positive number'); - } - return n; - }, - brief: 'Max number of files to scan concurrently', - default: '16', - }, - maxBytes: { - kind: 'parsed', - parse: (v: string) => { - const n = Number(v); - if (!Number.isFinite(n) || n <= 0) { - throw new Error('maxBytes must be a positive number'); - } - return n; - }, - brief: - 'Stop scanning each file after this many bytes (useful for huge files)', - optional: true, - }, - }, - }, - docs: { - brief: - 'Search files for an exact string match across CSV, JSON, text, and parquet files', - fullDescription: `Recursively searches a directory for files that contain the given needle string (case-insensitive). - -Supported file types: -- Text-based files (csv, json, txt, ndjson, log, etc.) are scanned via streaming byte comparison. -- Parquet files are scanned via DuckDB (must be on PATH unless --noParquet is set). - -Outputs one matching file path per line to stdout as hits are found.`, - }, -}); diff --git a/src/commands/admin/find-exact/impl.ts b/src/commands/admin/find-exact/impl.ts deleted file mode 100644 index 4d13459e..00000000 --- a/src/commands/admin/find-exact/impl.ts +++ /dev/null @@ -1,292 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { spawn } from 'node:child_process'; -import fg from 'fast-glob'; -import colors from 'colors'; -import type { LocalContext } from '../../../context'; -import { logger } from '../../../logger'; -import { doneInputValidation } from '../../../lib/cli/done-input-validation'; - -/** CLI flags accepted by the `find-exact` command. */ -export type FindExactCommandFlags = { - /** The exact string to search for */ - needle: string; - /** Root directory to search */ - root: string; - /** Comma-separated file extensions */ - exts: string; - /** Skip parquet file scanning */ - noParquet: boolean; - /** Max concurrent file scans */ - concurrency: number; - /** Stop scanning each file after N bytes */ - maxBytes?: number; -}; - -/** - * Streams through a file checking if it contains the needle (case-insensitive). - * - * @param filePath - Absolute path to the file to scan - * @param needle - Lowercased needle as a Buffer - * @param maxBytes - Optional byte limit per file - * @returns Whether the file contains the needle - */ -function fileContainsExactBytes( - filePath: string, - needle: Buffer, - maxBytes?: number, -): Promise { - return new Promise((resolve, reject) => { - const stream = fs.createReadStream(filePath); - let carry = Buffer.alloc(0); - const n = needle.length; - let seen = 0; - - stream.on('data', (raw: Buffer) => { - let chunk = raw; - - if (maxBytes) { - const remaining = maxBytes - seen; - if (remaining <= 0) { - stream.destroy(); - resolve(false); - return; - } - if (chunk.length > remaining) { - chunk = chunk.subarray(0, remaining); - } - seen += chunk.length; - } - - const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk; - const haystack = buf.toString('utf8').toLowerCase(); - if (haystack.includes(needle.toString('utf8'))) { - stream.destroy(); - resolve(true); - return; - } - - // Keep last n-1 bytes to catch boundary matches - if (n > 1) { - carry = Buffer.from(buf.subarray(Math.max(0, buf.length - (n - 1)))); - } else { - carry = Buffer.alloc(0); - } - }); - - stream.on('error', reject); - stream.on('close', () => resolve(false)); - stream.on('end', () => resolve(false)); - }); -} - -/** - * Run async workers over items with bounded concurrency. - * - * @param items - Array of items to process - * @param limit - Maximum concurrent workers - * @param worker - Async function to run per item - * @returns Resolves when all items are processed - */ -async function runWithConcurrency( - items: T[], - limit: number, - worker: (item: T) => Promise, -): Promise { - let idx = 0; - const runners = Array.from( - { length: Math.min(limit, items.length) }, - async () => { - // eslint-disable-next-line no-constant-condition - while (true) { - const current = idx; - idx += 1; - if (current >= items.length) return; - await worker(items[current]); - } - }, - ); - await Promise.all(runners); -} - -/** - * Execute a DuckDB query and return stdout. - * - * @param duckdbPath - Path to the duckdb binary - * @param sql - SQL query to execute - * @returns The stdout output from duckdb - */ -function duckdbQuery(duckdbPath: string, sql: string): Promise { - return new Promise((resolve, reject) => { - const child = spawn(duckdbPath, ['-noheader', '-batch', '-cmd', sql], { - stdio: ['ignore', 'pipe', 'pipe'], - }); - - let stdout = ''; - let stderr = ''; - child.stdout.on('data', (d) => { - stdout += String(d); - }); - child.stderr.on('data', (d) => { - stderr += String(d); - }); - - child.on('error', reject); - child.on('close', (code) => { - if (code === 0) resolve(stdout); - else reject(new Error(`duckdb exited ${code}: ${stderr}`)); - }); - }); -} - -/** - * Get all VARCHAR/STRING column names from a parquet file. - * - * @param duckdbPath - Path to the duckdb binary - * @param filePath - Absolute path to the parquet file - * @returns Array of string column names - */ -async function duckdbGetParquetStringColumns( - duckdbPath: string, - filePath: string, -): Promise { - const escaped = filePath.replace(/'/g, "''"); - const sql = [ - 'SELECT column_name', - `FROM parquet_schema('${escaped}')`, - "WHERE lower(column_type) LIKE '%varchar%'", - " OR lower(column_type) LIKE '%string%';", - ].join('\n'); - - const out = await duckdbQuery(duckdbPath, sql); - return out - .split('\n') - .map((l) => l.trim()) - .filter(Boolean); -} - -/** - * Check if any string column in a parquet file contains the needle value. - * - * @param duckdbPath - Path to the duckdb binary - * @param filePath - Absolute path to the parquet file - * @param needle - The string to search for (exact equality per column) - * @returns Whether any row/column matches - */ -async function parquetFileHasExactString( - duckdbPath: string, - filePath: string, - needle: string, -): Promise { - const cols = await duckdbGetParquetStringColumns(duckdbPath, filePath); - if (cols.length === 0) return false; - - const escaped = filePath.replace(/'/g, "''"); - const orChain = cols - .map((c) => `"${c.replace(/"/g, '""')}" = '${needle.replace(/'/g, "''")}'`) - .join(' OR '); - - const sql = [ - `SELECT 1 AS hit FROM read_parquet('${escaped}')`, - `WHERE ${orChain}`, - 'LIMIT 1;', - ].join('\n'); - - const out = await duckdbQuery(duckdbPath, sql); - return out.trim().length > 0; -} - -/** - * Entrypoint for the `admin find-exact` command. - * - * Recursively searches files under --root for the --needle string, outputting - * matching file paths. Supports text-based files via streaming byte scan and - * parquet files via DuckDB. - * - * @param this - Bound CLI context - * @param flags - CLI flags for the run - */ -export async function findExact( - this: LocalContext, - flags: FindExactCommandFlags, -): Promise { - doneInputValidation(this.process.exit); - - const { needle, root, exts, noParquet, concurrency, maxBytes } = flags; - const rootAbs = path.resolve(root); - - const extSet = new Set( - exts - .split(',') - .map((x) => x.trim().replace(/^\./, '').toLowerCase()) - .filter(Boolean), - ); - const patterns = Array.from(extSet).map((e) => `**/*.${e}`); - - logger.info( - colors.green( - `Searching for "${needle}" in ${rootAbs} (exts: ${[...extSet].join( - ', ', - )})`, - ), - ); - - const normalFiles = await fg(patterns, { - cwd: rootAbs, - absolute: true, - onlyFiles: true, - followSymbolicLinks: false, - suppressErrors: true, - }); - - const needleBuf = Buffer.from(needle.toLowerCase(), 'utf8'); - const hits: string[] = []; - - await runWithConcurrency(normalFiles, concurrency, async (file) => { - try { - const ok = await fileContainsExactBytes(file, needleBuf, maxBytes); - if (ok) { - hits.push(file); - this.process.stdout.write(`${file}\n`); - } - } catch { - // ignore unreadable files - } - }); - - if (!noParquet) { - const parquetFiles = await fg(['**/*.parquet'], { - cwd: rootAbs, - absolute: true, - onlyFiles: true, - followSymbolicLinks: false, - suppressErrors: true, - }); - - if (parquetFiles.length > 0) { - logger.info( - colors.green( - `Scanning ${parquetFiles.length} parquet file(s) via DuckDB...`, - ), - ); - - await runWithConcurrency( - parquetFiles, - Math.max(2, Math.floor(concurrency / 4)), - async (file) => { - try { - const ok = await parquetFileHasExactString('duckdb', file, needle); - if (ok) { - hits.push(file); - this.process.stdout.write(`${file}\n`); - } - } catch { - // ignore parquet read issues - } - }, - ); - } - } - - logger.info(colors.green(`Done. Found ${hits.length} matching file(s).`)); -} From 7c0f8a35c67a67d5e88ef0ac35ca32b3089e6af7 Mon Sep 17 00:00:00 2001 From: michaelfarrell76 Date: Tue, 24 Feb 2026 23:42:29 -0800 Subject: [PATCH 72/72] rev --- README.md | 26 -------------------------- src/commands/admin/routes.ts | 2 -- 2 files changed, 28 deletions(-) diff --git a/README.md b/README.md index f3363329..8f1e066e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ A command line interface that allows you to programatically interact with the Tr - [`transcend inventory consent-managers-to-business-entities`](#transcend-inventory-consent-managers-to-business-entities) - [`transcend admin generate-api-keys`](#transcend-admin-generate-api-keys) - [`transcend admin chunk-csv`](#transcend-admin-chunk-csv) - - [`transcend admin find-exact`](#transcend-admin-find-exact) - [`transcend admin parquet-to-csv`](#transcend-admin-parquet-to-csv) - [`transcend migration sync-ot`](#transcend-migration-sync-ot) - [Prompt Manager](#prompt-manager) @@ -3486,31 +3485,6 @@ transcend admin chunk-csv \ transcend admin chunk-csv --directory=./working/files ``` -### `transcend admin find-exact` - -```txt -USAGE - transcend admin find-exact (--needle value) [--root value] [--exts value] [--noParquet] [--concurrency value] [--maxBytes value] - transcend admin find-exact --help - -Recursively searches a directory for files that contain the given needle string (case-insensitive). - -Supported file types: -- Text-based files (csv, json, txt, ndjson, log, etc.) are scanned via streaming byte comparison. -- Parquet files are scanned via DuckDB (must be on PATH unless --noParquet is set). - -Outputs one matching file path per line to stdout as hits are found. - -FLAGS - --needle The exact string to search for (case-insensitive) - [--root] Root directory to search [default = .] - [--exts] Comma-separated file extensions to search (without leading dots) [default = csv,json,txt,ndjson,log] - [--noParquet] Skip parquet file scanning (requires duckdb on PATH) [default = false] - [--concurrency] Max number of files to scan concurrently [default = 16] - [--maxBytes] Stop scanning each file after this many bytes (useful for huge files) - -h --help Print help information and exit -``` - ### `transcend admin parquet-to-csv` ```txt diff --git a/src/commands/admin/routes.ts b/src/commands/admin/routes.ts index 730beec5..b97e74ec 100644 --- a/src/commands/admin/routes.ts +++ b/src/commands/admin/routes.ts @@ -1,14 +1,12 @@ import { buildRouteMap } from '@stricli/core'; import { generateApiKeysCommand } from './generate-api-keys/command'; import { chunkCsvCommand } from './chunk-csv/command'; -import { findExactCommand } from './find-exact/command'; import { parquetToCsvCommand } from './parquet-to-csv/command'; export const adminRoutes = buildRouteMap({ routes: { 'generate-api-keys': generateApiKeysCommand, 'chunk-csv': chunkCsvCommand, - 'find-exact': findExactCommand, 'parquet-to-csv': parquetToCsvCommand, }, docs: {