diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83384fe848..320c240310 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '22.x' - + - name: Enable Corepack run: corepack enable @@ -24,7 +24,7 @@ jobs: - name: Install root dependencies run: pnpm install - + - name: Reading Configuration id: release_config uses: rgarcia-phi/json-to-variables@v1.1.0 @@ -200,6 +200,10 @@ jobs: if: ${{env.release_releaseAll == 'true' || env.release_plugins_export-to-csv == 'true'}} working-directory: ./packages/contentstack-export-to-csv run: npm install + - name: Compiling export to csv + if: ${{ steps.export-to-csv-installation.conclusion == 'success' }} + working-directory: ./packages/contentstack-export-to-csv + run: npm run prepack - name: Publishing export to csv uses: JS-DevTools/npm-publish@v3 if: ${{ steps.export-to-csv-installation.conclusion == 'success' }} diff --git a/.gitignore b/.gitignore index db312beaf2..9dcc968270 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ contents-* *.http *.todo talisman_output.log -snyk_output.log \ No newline at end of file +snyk_output.log +*.logs \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index a7f6634fd6..4e2b280164 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,293 +1,6 @@ fileignoreconfig: + - filename: packages/contentstack-export-to-csv/src/types/index.ts + checksum: 28c19efb0c13969d40df964f86fa6de444316e320801f19dee7392df7a36851d - filename: package-lock.json - checksum: 48479bae69c08b32d71c6f749328e14d903a578061271d67edcb96617b8b0e3b - - filename: pnpm-lock.yaml - checksum: d0d9f53b699a4def858a869f2237b73543e3e4d3085e6a05266b882a4027ec5b - - filename: packages/contentstack-import-setup/test/unit/backup-handler.test.ts - checksum: 0582d62b88834554cf12951c8690a73ef3ddbb78b82d2804d994cf4148e1ef93 - - filename: packages/contentstack-import-setup/test/config.json - checksum: 187fd202d00e7d2c3d8b00f983ff21d8535e0fdb76cebec3f39c400258c88d05 - - filename: packages/contentstack-command/test/config.json - checksum: 7c15663b3a6562b99d3082ead5035932b0276e4fd53774b3f838372a19b291ef - - filename: packages/contentstack-import-setup/test/unit/modules/content-types.test.ts - checksum: ce8772281171927e7dee7d6a761a029c902393b808e2696624fdcf0f5b80ea5c - - filename: packages/contentstack-import-setup/test/unit/modules/entries.test.ts - checksum: 17652bfc125879bb37facf8ea9f54dc4f97627ca625ec148c9d551a20196d85b - - filename: packages/contentstack-import-setup/test/unit/modules/extensions.test.ts - checksum: eaafdf39fc8a947aa490232bfc7da950c882bd69b5b27a0362ef2bee21f6a177 - - filename: packages/contentstack-import-setup/test/unit/modules/global-fields.test.ts - checksum: fd49cfab6d374254c0c6eb4c7e7ee8ff4fe6c2b46e7b0d7f7437cbe665d1ce8b - - filename: packages/contentstack-import-setup/test/unit/modules/marketplace-apps.test.ts - checksum: c35dfe96d685fb12427de4b77c9240b34b9bee5e158ad7489acaa0d061ad562e - - filename: packages/contentstack-import-setup/test/unit/modules/taxonomies.test.ts - checksum: 3868ff9e8833a670350590f070c6f635807f2a1f534accba677af4709fab0e4a - - filename: packages/contentstack-import-setup/test/unit/import-config-handler.test.ts - checksum: f2f2c994543c388f2eecaf8128f789eab2895f1f78d659e58ef9491972c6f9a8 - - filename: packages/contentstack-import-setup/test/unit/common-helper.test.ts - checksum: a0c98c6f0ee88a398e3f1bd80cac0a6cc0ede7eee01957cf7d6e1f199f3da643 - - filename: packages/contentstack-import-setup/test/unit/base-setup.test.ts - checksum: 862c52e2bbd1975b963f45ce3e89c243d047858cdbe7339918395ce2fc52bf89 - - filename: packages/contentstack-import-setup/test/unit/import-setup.test.ts - checksum: 1eee4f461fa5b115894d1806a14af6f45336cbe6c0392f16078bd2877fadff67 - - filename: packages/contentstack-import-setup/test/unit/login-handler.test.ts - checksum: e549f9ca3a9aae0d93b7284f7e771d55c0610725ddcb4333612df2f215e92769 - - filename: packages/contentstack/README.md - checksum: 27e772e9b93e7841a36349827d604c9cfa21cbdfd53b340fda24d6d65fd9b679 - - filename: packages/contentstack-import-setup/test/unit/modules/assets.test.ts - checksum: 449a5e3383631a6f78d1291aa3c28c91681879289398f0a933158fba5c5d5acf - - filename: packages/contentstack-auth/env.example - checksum: 72c9ed18a449c42b03ec54795898f6bad4e15d23a3d701c05b96fb17c3bbd93b - - filename: packages/contentstack-auth/test/integration/auth.test.ts - checksum: 9933a64d17d6d6dd7dd87ff210ce5e8a215bf36fac0cfd333894612ed10fb81b - - filename: packages/contentstack-auth/src/utils/mfa-handler.ts - checksum: ca9c34a3fe6c3b957debff987aefbceb641bf4954f15541d07d901f91e5ff014 - - filename: packages/contentstack-auth/messages/index.json - checksum: 95856ad6273f17a9e853cda9c2cf0bdd782e47aeab93385e73ab870b5e814f89 - - filename: packages/contentstack-auth/test/utils/auth-handler.test.ts - checksum: f88dded3a326f191844e39258e7fe390a72fefeb387d09c7f97e4e8aed520c97 - - filename: packages/contentstack-auth/src/commands/auth/login.ts - checksum: 89204be8dfc1f670a568af992b54f34845e49bd4a8046c0cf041dd3759150718 - - filename: packages/contentstack-auth/test/unit/commands/tokens-add.test.ts - checksum: 1e7247908e1887998210381c03caca93a3983e1c8967483464cf1c3bd3209cd1 - - filename: packages/contentstack-auth/test/unit/commands/logout.test.ts - checksum: cd22dd04bd6a77cafa7dd0960cd4691201a3e228216d5a10041b8e39d7ebba1f - - filename: packages/contentstack-auth/src/utils/auth-handler.ts - checksum: 1261d02e8215da2db28557b77d6a8c8c604e11df88520e1cc5c8561e26bdd150 - - filename: packages/contentstack-auth/test/unit/commands/login.test.ts - checksum: f93aa9b0c964608b60c88d4c72ff33840b58ec900297c4bae1f4ea365aa51048 - - filename: packages/contentstack-auth/test/utils/mfa-handler.test.ts - checksum: b067f93cf0185d794e8419cc41e8fac96ed790dea8fc48dc083ee242ccacbd4d - - filename: packages/contentstack-import/src/import/module-importer.ts - checksum: 93fac2407e20070aa393f783e5a21093e99424e5fd2873aabc2099ac3ea02b27 - - filename: packages/contentstack-import/src/utils/import-config-handler.ts - checksum: bb8093633dc7de888541990623c3e02a482b7e6f5db0ba396bedc20c4c74b782 - - filename: packages/contentstack-import/src/utils/setup-branch.ts - checksum: a4a968a20d5ab7cbc08c266819907541bbf793cc098521a5e810ada3cbacbee6 - - filename: packages/contentstack-bulk-publish/src/producer/publish-unpublished-env.js - checksum: 44dbc966df086f835fdca11cb305d0a5f448ca0be811c14b894e0024f9491385 - - filename: packages/contentstack-import/src/import/modules/entries.ts - checksum: bdf26bd2b71c1b7a0d5540ba98c53bf917d8d7d3813447073a89439fb789970b - - filename: packages/contentstack-utilities/src/logger/logger.ts - checksum: 76429bc87e279624b386f00e7eb3f4ec25621ace7056289f812b9a076d6e184e - - filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts - checksum: e66a08cb3cd444071688fbad1e14da309f8504f584cfaed85499d32b623e29e8 - - filename: packages/contentstack-bootstrap/messages/index.json - checksum: c435ceaa709a7504da303a6ea674e07a89030d8ad4152e7917cd17e7f3e58052 - - filename: packages/contentstack-bootstrap/src/config.ts - checksum: cc3270acd9d37479b24792f45a108e0f1c99265f92d59c35c0ec3ee2d1cc390d - - filename: packages/contentstack-bulk-publish/src/util/generate-bulk-publish-url.js - checksum: 5f7c1e2fac3e7fab21e861d609c54ca7191ee09fd076dd0adc66604043bf7a43 - - filename: packages/contentstack-import/src/utils/interactive.ts - checksum: b401a6166313c184712ff623ea8d95d5548fb3d8b8229c053ae44a1850b54a72 - - filename: packages/contentstack-import-setup/src/utils/backup-handler.ts - checksum: 7db02c6f2627400b28fc96d505bf074d477080a45ba13943709d4845b6ca0908 - - filename: packages/contentstack-import/src/utils/backup-handler.ts - checksum: 0a9accdafce01837166223ed00cd801e2ebb39a4ef952231f67232859a5beea8 - - filename: packages/contentstack-audit/src/modules/global-fields.ts - checksum: 556bd27f78e8261491a7f918919128b8c2cc9d2d55113f440b89384a30481e5f - - filename: packages/contentstack-audit/src/audit-base-command.ts - checksum: 2c710267332619d310dd24461076fc9ca00cc1c991c2913e74a98808fac42c39 - - filename: packages/contentstack-audit/src/modules/custom-roles.ts - checksum: bbe1130f5f5ebf2fa452daef743fe4d40ae9f8fc05c7f8c59c82a3d3d1ed69e8 - - filename: packages/contentstack-audit/src/modules/extensions.ts - checksum: 32af019f0df8288448d11559fe9f7ef61d3e43c3791d45eeec25fd0937c6baad - - filename: packages/contentstack-audit/src/modules/assets.ts - checksum: 5a007804c75976dd192ed2284b7b7edbc5b5fc269fc0e883908b52e4d4f206a8 - - filename: packages/contentstack-audit/src/modules/workflows.ts - checksum: 20d1f1985ea2657d3f9fc41b565a44000cbda47e2a60a576fee2aaff06f49352 - - filename: packages/contentstack-audit/src/modules/field_rules.ts - checksum: 3eaca968126c9e0e12115491f7942341124c9962d5285dd1cfb355d9e60c6106 - - filename: packages/contentstack-audit/src/modules/entries.ts - checksum: 305af34194771343fee4e1d4bef60d065f1b8d1d8c1059a332f5d6c52e637ff1 - - filename: packages/contentstack-audit/test/unit/base-command.test.ts - checksum: b0fa8088fcbb17510fa275bd0dde3f6f4246f2525741c30426f07dd62fe497b0 - - filename: packages/contentstack-audit/src/modules/content-types.ts - checksum: ddf7b08e6a80af09c6a7019a637c26089fb76572c7c3d079a8af244b02985f16 - - filename: packages/contentstack-import/test/unit/utils/asset-helper.test.ts - checksum: 8e83200ac8028f9289ff1bd3a50d191b35c8e28f1854141c90fa1b0134d6bf8a - - filename: packages/contentstack-import/test/unit/import/modules/marketplace-apps.test.ts - checksum: 0d4db99c346e35f49c9da647b4e60c2e3c0203471772e1897affb71cb28f53d8 - - filename: packages/contentstack-import/test/unit/import/modules/mock-data/entries/empty-environments.json - checksum: 1db7db30b8491f79f2881bb862986748c54f75d63d7ee6343517083f7e42a6bf - - filename: packages/contentstack-import/test/unit/import/modules/mock-data/entries/environments.json - checksum: 17f94f500dcb265575b60f8d2cb7464372a234e452527b3bdec6052c606cee28 - - filename: packages/contentstack-import/test/unit/import/modules/entries.test.ts - checksum: d8e4f6ad185b36b6f84b38dce169144e7d5a195668aac11f914eed5e1e4b5478 - - filename: packages/contentstack-import/test/unit/import/modules/labels.test.ts - checksum: 46fe0d1602ab386f7eaee9839bc376b98ab8d4262f823784eda9cfa2bf893758 - - filename: packages/contentstack-export/test/unit/export/modules/assets.test.ts - checksum: 9245c4d4842493e0599e0e5044404be5a01907e64f11825ff169e537758f2cb2 - - filename: packages/contentstack-export/test/unit/export/modules/base-class.test.ts - checksum: c7f9801faeb300f8bd97534ac72441bde5aac625dd4beaf5531945d14d9d4db0 - - filename: packages/contentstack-import/test/unit/import/modules/environments.test.ts - checksum: 58165d06d92f55be8abb04c4ecc47df775a1a47f1cee529f1be5277187700f97 - - filename: packages/contentstack-import/test/unit/import/modules/locales.test.ts - checksum: 354827729c24456de3f38f70aed09ae65a15461e9ec7227aa20bb3878ff22add - - filename: packages/contentstack-export/test/unit/export/modules/environments.test.ts - checksum: 530573c4c92387b755ca1b4eef88ae8bb2ae076be9a726bba7b67a525cba23e9 - - filename: packages/contentstack-export/test/unit/export/modules/extensions.test.ts - checksum: 857978a21ea981183254245f6b3cb5f51778d68fc726ddb26005ac96c706650f - - filename: packages/contentstack-export/test/unit/export/modules/webhooks.test.ts - checksum: 2e2d75281a57f873fb7f5fff0e5a9e863b631efd2fd92c4d2c81d9c8aeb3e252 - - filename: packages/contentstack-export/test/unit/export/modules/locales.test.ts - checksum: 93bdd99ee566fd38545b38a8b528947af1d42a31908aca85e2cb221e39a5b6cc - - filename: packages/contentstack-export/test/unit/export/modules/stack.test.ts - checksum: bb0f20845d85fd56197f1a8c67b8f71c57dcd1836ed9cfd86d1f49f41e84d3a0 - - filename: packages/contentstack-export/test/unit/export/modules/custom-roles.test.ts - checksum: 39f0166a8030ee8f504301f3a42cc71b46ddc027189b90029ef19800b79a46e5 - - filename: packages/contentstack-export/test/unit/export/modules/workflows.test.ts - checksum: c5ddb72558ffbe044abd2da7c1e2a922dbc0a99b3f31fa9df743ad1628ffd1e5 - - filename: packages/contentstack-export/test/unit/export/modules/content-types.test.ts - checksum: 457912f0f1ad3cadabbdf19cff6c325164e76063f12b968a00af37ec15a875e9 - - filename: packages/contentstack-export/test/unit/export/modules/global-fields.test.ts - checksum: 64d204d0ff6232d161275b1df5b2ea5612b53c72d9ba2c22bd13564229353c4d - - filename: packages/contentstack-import/test/unit/import/modules/webhooks.test.ts - checksum: 9f6dc9fb12f0d30600dac28846c7a9972e1dafe7c7bf5385ea677100a1d8fbd1 - - filename: packages/contentstack-import/test/unit/import/module-importer.test.ts - checksum: aa265917b806286c8d4d1d3f422cf5d6736a0cf6a5f50f2e9c04ec0f81eee376 - - filename: packages/contentstack-import/test/unit/import/modules/index.test.ts - checksum: aab773ccbe05b990a4b934396ee2fcd2a780e7d886d080740cfddd8a4d4f73f7 - - filename: packages/contentstack-import/test/unit/import/modules/personalize.test.ts - checksum: ea4140a1516630fbfcdd61c4fe216414b733b4df2410b5d090d58ab1a22e7dbf - - filename: packages/contentstack-import/test/unit/import/modules/variant-entries.test.ts - checksum: abcc2ce0b305afb655eb46a1652b3d9e807a2a2e0eef1caeb16c8ae83af4f1a1 - - filename: packages/contentstack-export/test/unit/utils/common-helper.test.ts - checksum: 276e850e4caddc89372f09f4eee5832cc4ab5b513da2a662a821f5feb8561349 - - filename: packages/contentstack-export/test/unit/utils/file-helper.test.ts - checksum: a16f5833515ececd93c582b35d19b8a5df4880f22126fba18f110692c679025b - - filename: packages/contentstack-export/test/unit/utils/export-config-handler.test.ts - checksum: ba02c3d580e02fc4ecd5e6a0fc59e6c7d56d7de735339aa00e2c2241ffe22176 - - filename: packages/contentstack-export/test/unit/utils/interactive.test.ts - checksum: b619744ebba28dbafe3a0e65781a61a6823ccaa3eb84e2b380a323c105324c1a - - filename: packages/contentstack-import/test/unit/utils/backup-handler.test.ts - checksum: 696aea5f9a4ccd75fe22e4a839f9ad279077f59d738ed62864b91aed7b54f053 - - filename: packages/contentstack-import/test/unit/utils/mock-data/common-helper/import-configs.json - checksum: 1f48841db580d53ec39db163c8ef45bff26545dd51cdeb9b201a66ff96c31693 - - filename: packages/contentstack-import/test/unit/utils/mock-data/file-helper/test-data.json - checksum: db64a1f13a3079080ffd0aeea36a3a7576e56f27b57befc6e077aa45f147a3de - - filename: packages/contentstack-import/test/unit/utils/file-helper.test.ts - checksum: a5cd371d7f327c083027da4157b3c5b4df548f2c2c3ad6193aa133031994252e - - filename: packages/contentstack-import/test/unit/utils/common-helper.test.ts - checksum: 61b3cfe0c0571dcc366e372990e3c11ced2b49703ac88155110d33897e58ca5d - - filename: packages/contentstack-import/test/unit/utils/import-path-resolver.test.ts - checksum: 05436c24619b2d79b51eda9ce9a338182cc69b078ede60d310bfd55a62db8369 - - filename: packages/contentstack-import/test/unit/utils/interactive.test.ts - checksum: 77a45bd7326062053b98d1333fa59147757a5a8abdb34057a347ca2a1b95b343 - - filename: packages/contentstack-import/test/unit/utils/import-config-handler.test.ts - checksum: 20bbfb405a183b577f8ae8f2b47013bc42729aa817d617264e0c3a70b3fa752b - - filename: packages/contentstack-import/test/unit/utils/login-handler.test.ts - checksum: bea00781cdffc2d085b3c85d6bde75f12faa3ee51930c92e59777750a6727325 - - filename: packages/contentstack-import/test/unit/utils/marketplace-app-helper.test.ts - checksum: eca2702d1f7ed075b9b857964b9e56f69b16e4a31942423d6b1265e4bf398db5 - - filename: packages/contentstack-import/test/unit/utils/logger.test.ts - checksum: 794e06e657a7337c8f094d6042fb04c779683f97b860efae14e075098d2af024 - - filename: packages/contentstack-export/test/unit/utils/marketplace-app-helper.test.ts - checksum: c454b0f52739cb9adef3d44f5ce123826f6c78d7709bd84eb281f84fb3a46606 - - filename: packages/contentstack-export/test/unit/export/modules/labels.test.ts - checksum: 978b2e96ee2682286b9aee6d76de0caa5a42c9e9371cb4041340f4e5f6f6ca1b - - filename: packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts - checksum: 720d0b915078ceecc153e352fdb28850e7ea29fe0cd317643f55906f83e0bdbd - - filename: packages/contentstack-import-setup/src/import/modules/taxonomies.ts - checksum: 49dd8e754a0d3635585a74e943ab097593f061089a7cddc22683ec6caddbb3c5 - - filename: packages/contentstack-export/test/unit/export/modules/personalize.test.ts - checksum: 83cf034fabee00b42b4243a8c0b8ba280ab7c1e68ffd741c49c31aaee8ca0315 - - filename: packages/contentstack-audit/test/unit/audit-base-command.test.ts - checksum: 17a16b4457c820494442f335d94d0949961e68e8ca72ca0f1fa9d4d0eeb0c17a - - filename: packages/contentstack-import/src/import/modules/taxonomies.ts - checksum: ed7037a220754ecfdf800f6b747059ad309e23e7bafa09f6938021246f67d998 - - filename: packages/contentstack-export/test/unit/utils/logger.test.ts - checksum: ce8438d323fd0e42a88ece3dfd826313993c0198dbeffe01d046f835d6aad95f - - filename: packages/contentstack-export/test/unit/export/modules/taxonomies.test.ts - checksum: 57566af0bbd95a28b454e4436b0396dfec2fe05ddd5b448d46e3a8b68db8b9cb - - filename: packages/contentstack-export/test/unit/export/modules/entries.test.ts - checksum: 5950c6f697224e11bec32736e6a967b0ab7ac98e9c8f8bb8eaaf10af60913e40 - - filename: packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts - checksum: 86b11c2a2dd8c0b14aa558e4e52d6d721cd7707422c26a68e96cc5b55b9fefd8 - - filename: packages/contentstack-import-setup/src/utils/login-handler.ts - checksum: 3860c96e31677356963e67049762f944aef7c7b22fabb75a70ff5c64cf1ac274 - - filename: packages/contentstack-export/src/export/modules/environments.ts - checksum: 2777e15f32d61fcdc0fd395cedf4413cc5b7494a99cfb6c1b68fffa2269908ab - - filename: packages/contentstack-import/src/import/modules/environments.ts - checksum: 0e49cf0fb017e39c5d0eead3e388c323559f9057dd961dea61740915395deab3 - - filename: packages/contentstack-migration/examples/06-update-environment.js - checksum: 4a7d2c2f1ee6bf76066932661ed9674c6aff7d959b26ea14d79949ab5dda43d9 - - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish.js - checksum: 0a0e6ddd4aa0de09b3a66bf53c6ea079bb51726a64f96606b117a6e990b90d92 - - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish-non-localized-fields.js - checksum: f854cb2ddfafe1c250ec66a326fe620661142ea24282ec3c7b0f105156cbcc1a - - filename: packages/contentstack-export/src/utils/export-config-handler.ts - checksum: 2b7fc04762752729d33f77ebb35a12dd12a65f4fee893a04f96ba19bca521040 - - filename: packages/contentstack-variants/src/import/events.ts - checksum: c38a91e2d89b872287c178efc067dff89a061aad38c402d6485b85bd46784c33 - - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/unpublish.js - checksum: 9efdf1cbc372858d771feef2bbef1a6828418497d4a3c31e99760b33ef9dd4a4 - - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/update-and-publish.js - checksum: 3e13e34f25eac722f2bcf841057bec7471fd7ff89098d0477ccf143e5487e423 - - filename: packages/contentstack-export/src/export/modules/custom-roles.ts - checksum: 19b46d3bf4edeeb10e2f6c81bc0caaac4e1f8c3b4e4f91db2592c2a005ed08a5 - - filename: packages/contentstack-bulk-publish/src/commands/cm/bulk-publish/cross-publish.js - checksum: 0cc612eb2b9be5308682f0e24f8f1c1a5b81d4516d84a4a06d62286e97596cea - - filename: packages/contentstack-export/src/export/modules/composable-studio.ts - checksum: 1ad61df3110dd99f14cef6382d7820dd32538a8d85ffac4bfd631761a1bdf7af - - filename: packages/contentstack-import/test/unit/utils/extension-helper.test.ts - checksum: 8cbcb6f192edb034a01a405437276f5dafb6cb235fa9c7f8e5b936f006f451ea - - filename: packages/contentstack-export/src/export/modules/labels.ts - checksum: c3060a5ae784e886505e1b736d89f054a92131bfef4b00268500326643edb43d - - filename: packages/contentstack-export/src/export/modules/locales.ts - checksum: 6e400596635741a7013ad1317f29307777dd70855d5c2f16fe0388f667254a17 - - filename: packages/contentstack-auth/src/utils/tokens-validation.ts - checksum: 3aab82beb51b1dc01e644b4c07a965a49b80cc3282c8e914fbd2671440e9b28d - - filename: packages/contentstack-config/src/commands/config/remove/base-branch.ts - checksum: 6a6dc3ff9088b4d219d6e93717b5907b0679720228474cb37f32f05d96d4634b - - filename: packages/contentstack-bulk-publish/src/commands/cm/assets/unpublish.js - checksum: 0f8f2ea8107725a68fc8de19273247c632c9d5902b03067a4915e4ae1c8d3d66 - - filename: packages/contentstack-bulk-publish/src/commands/cm/assets/publish.js - checksum: 63f2f414738fbdd109ff5bb56aff26a93a8f76798112ad6830358f743a7749af - - filename: packages/contentstack-auth/src/commands/auth/tokens/add.ts - checksum: 0b10b5a80431160b57052930bba8431ecc5635aff9a5309af1b59b4beea8a0bb - - filename: packages/contentstack-export/src/export/modules/extensions.ts - checksum: d02dbdf57f2009111d5b56e646fc33fd349f2f247e6eec96dfce34280b55a220 - - filename: packages/contentstack-bulk-publish/src/commands/cm/stacks/publish-configure.js - checksum: 900fe4c398e181e1f035d410b510d8ec4a7afa0a5cac7b009bce4c3edaa691f1 - - filename: packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js - checksum: e7cfb7b35f1425359e6e064a77afa6ba54685dda055880af9f02995b00911041 - - filename: packages/contentstack-bulk-publish/src/commands/cm/entries/publish-modified.js - checksum: 512fad49bf40dc16907bfbeb836ff71a13eb9f67918ae280e1cf243b7e9aff0e - - filename: packages/contentstack-variants/src/import/audiences.ts - checksum: e0380352bb945cc694c7988574d3a3682e7ed71b5d3aa07e01007f6fe0137ce0 - - filename: packages/contentstack-import/src/import/modules/composable-studio.ts - checksum: 9b83875b8d82086f13e0b7ab44ff7fe95486fced95b0c22d5c73fa69fbe35d4a - - filename: packages/contentstack-bulk-publish/src/producer/add-fields.js - checksum: 3e70b11978fc5f29a6a6c90b725c28c9df8d15bcc6fd74e2253fca23a3630160 - - filename: packages/contentstack-export/src/export/modules-js/environments.js - checksum: e8714ef41940f3a9be782dfaa43a15df57bd1eb4c3f0e4d5f305e68681c1bd93 - - filename: packages/contentstack-import/src/import/modules-js/environments.js - checksum: d484342c25462a7052c8aae6cad0baed9a01e1eaa67d6a09f175981c53092301 - - filename: packages/contentstack-clone/.eslintrc - checksum: a7230ffa600c58047ac73f2dec7a23ca5862e36e68f04f2671379496739bd818 - - filename: packages/contentstack-clone/test/commands/cm/stacks/clone.test.ts - checksum: b30adfbbd25aa76fe41b0ffebdb3bc61eb4981063c3a38b890c3cb3a6660ecca - - filename: packages/contentstack-clone/test/lib/util/clone-handler.execution.test.ts - checksum: 7ed8fa62d9aba7135a142134c36535e3722471cabc81d2ea3437cd67b2e87d58 - - filename: packages/contentstack-clone/test/lib/util/clone-handler.commands.test.ts - checksum: d0427ddfa6d338e6b5c4e6f8d94f32332e3e9858626356d07e5690471e062f49 - - filename: packages/contentstack-audit/test/unit/mock/contents/composable_studio/environments/environments.json - checksum: 0402604e5919a7e38ecb5ff0916d6ae5ab7d98fe78ff6ac9eba8a9b8130af34d - - filename: packages/contentstack-audit/test/unit/mock/contents/composable_studio/composable_studio.json - checksum: 6912e5ea32b4456ad04d1645750c72bbb29ab1895368c3a242ab39e9350ec531 - - filename: packages/contentstack-audit/test/unit/mock/contents/composable_studio/invalid_composable_studio.json - checksum: e6465aa0011d1565a2de848d9cca74395d11419e6ac840e7dfb52e1d255b1c4f - - filename: packages/contentstack-audit/src/modules/modulesData.ts - checksum: 1e6c1fba1172512401038d5454c8d218201ec62262449c5c878609592e0124c4 - - filename: packages/contentstack-audit/src/modules/composable-studio.ts - checksum: 4fc97ff582d6dff9a54b3a50dfa3cbb5febd38a55aeb8737034b97188ad543ba - - filename: packages/contentstack-config/src/utils/interactive.ts - checksum: 09ebc9918e5161e423238df8879b782daf673e52a5ca97e000a9618517a40b96 - - filename: packages/contentstack-config/src/commands/config/set/proxy.ts - checksum: 058c98f96a2de7e75a65536933e5fcd88f0fb7e8a487d9c172f4daacd0d05bea - - filename: packages/contentstack-utilities/src/proxy-helper.ts - checksum: 2a8379d7a34acb3c14093599fb4ba7307c94b0f280ea70d6d862ecb2448fe924 - - filename: packages/contentstack-config/src/commands/config/get/proxy.ts - checksum: 9c49c2e6246ecdc5a4141c7e5f413d34f1fc799e25b3bbf60b61372c7a9a6f36 + checksum: faa364dac78a89d142916b71b5ceba7fdc80acbf8dc71f15dfb38d622c9dc664 version: '1.0' - diff --git a/package-lock.json b/package-lock.json index 7849b731f8..292c9cba3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11175,16 +11175,16 @@ } }, "node_modules/eslint-config-oclif/node_modules/eslint-config-xo/node_modules/@stylistic/eslint-plugin": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.0.tgz", - "integrity": "sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.1.tgz", + "integrity": "sha512-zjTUwIsEfT+k9BmXwq1QEFYsb4afBlsI1AXFyWQBgggMzwBFOuu92pGrE5OFx90IOjNl+lUbQoTG7f8S0PkOdg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/types": "^8.52.0", - "eslint-visitor-keys": "^5.0.0", - "espree": "^11.0.0", + "@typescript-eslint/types": "^8.53.1", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, @@ -11195,37 +11195,6 @@ "eslint": ">=9.0.0" } }, - "node_modules/eslint-config-oclif/node_modules/eslint-config-xo/node_modules/eslint-visitor-keys": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", - "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-oclif/node_modules/eslint-config-xo/node_modules/espree": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz", - "integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint-config-oclif/node_modules/eslint-config-xo/node_modules/globals": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", @@ -18019,23 +17988,23 @@ "license": "MIT" }, "node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^8.1.0" } }, "node_modules/nise/node_modules/@sinonjs/fake-timers": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", - "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -18043,11 +18012,15 @@ } }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/no-case": { "version": "3.0.4", @@ -26827,7 +26800,7 @@ "@contentstack/cli-cm-bulk-publish": "~1.10.6", "@contentstack/cli-cm-clone": "~1.20.0", "@contentstack/cli-cm-export": "~1.23.1", - "@contentstack/cli-cm-export-to-csv": "~1.10.3", + "@contentstack/cli-cm-export-to-csv": "~1.11.0", "@contentstack/cli-cm-import": "~1.31.2", "@contentstack/cli-cm-import-setup": "~1.7.3", "@contentstack/cli-cm-migrate-rte": "~1.6.4", @@ -27811,7 +27784,7 @@ }, "packages/contentstack-export-to-csv": { "name": "@contentstack/cli-cm-export-to-csv", - "version": "1.10.3", + "version": "1.11.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.7.2", @@ -27826,74 +27799,36 @@ "devDependencies": { "@oclif/test": "^4.1.13", "@types/chai": "^4.3.20", + "@types/inquirer": "^9.0.8", + "@types/mkdirp": "^1.0.2", "@types/mocha": "^10.0.10", + "@types/node": "^20.17.50", "chai": "^4.5.0", - "debug": "^4.4.1", - "eslint": "^7.32.0", - "eslint-config-oclif": "^6.0.15", + "eslint": "^8.57.1", + "eslint-config-oclif": "^6.0.62", + "eslint-config-oclif-typescript": "^3.1.14", "mocha": "^10.8.2", + "nock": "^13.5.6", "nyc": "^15.1.0", - "oclif": "^4.17.46" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "packages/contentstack-export-to-csv/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "packages/contentstack-export-to-csv/node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "oclif": "^4.17.46", + "sinon": "^19.0.5", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=18.0.0" } }, - "packages/contentstack-export-to-csv/node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "deprecated": "Use @eslint/config-array instead", + "packages/contentstack-export-to-csv/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-3-Clause", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" + "@sinonjs/commons": "^3.0.1" } }, - "packages/contentstack-export-to-csv/node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, "packages/contentstack-export-to-csv/node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -27901,247 +27836,85 @@ "dev": true, "license": "MIT" }, - "packages/contentstack-export-to-csv/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/contentstack-export-to-csv/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "packages/contentstack-export-to-csv/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "packages/contentstack-export-to-csv/node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "dev": true, "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "undici-types": "~6.21.0" } }, - "packages/contentstack-export-to-csv/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "packages/contentstack-export-to-csv/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "packages/contentstack-export-to-csv/node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, + "packages/contentstack-export-to-csv/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", "license": "MIT", - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, "bin": { - "eslint": "bin/eslint.js" + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "packages/contentstack-export-to-csv/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "packages/contentstack-export-to-csv/node_modules/sinon": { + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", + "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "packages/contentstack-export-to-csv/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "packages/contentstack-export-to-csv/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "packages/contentstack-export-to-csv/node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "packages/contentstack-export-to-csv/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=8" } }, - "packages/contentstack-export-to-csv/node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "packages/contentstack-export-to-csv/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "packages/contentstack-export-to-csv/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "packages/contentstack-export-to-csv/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "packages/contentstack-export-to-csv/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "packages/contentstack-export-to-csv/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "packages/contentstack-export-to-csv/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "packages/contentstack-export-to-csv/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.17" } }, "packages/contentstack-export/node_modules/@sinonjs/fake-timers": { @@ -28171,6 +27944,27 @@ "@types/sinonjs__fake-timers": "*" } }, + "packages/contentstack-export/node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "packages/contentstack-export/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "packages/contentstack-export/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", diff --git a/packages/contentstack-audit/README.md b/packages/contentstack-audit/README.md index 430ac78416..cf0fa3427b 100644 --- a/packages/contentstack-audit/README.md +++ b/packages/contentstack-audit/README.md @@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-audit $ csdx COMMAND running command... $ csdx (--version|-v) -@contentstack/cli-audit/1.17.0 darwin-arm64 node-v22.14.0 +@contentstack/cli-audit/1.17.1 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND @@ -286,7 +286,7 @@ DESCRIPTION Display help for csdx. ``` -_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.36/src/commands/help.ts)_ +_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.37/src/commands/help.ts)_ ## `csdx plugins` @@ -309,7 +309,7 @@ EXAMPLES $ csdx plugins ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/index.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/index.ts)_ ## `csdx plugins:add PLUGIN` @@ -383,7 +383,7 @@ EXAMPLES $ csdx plugins:inspect myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/inspect.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/inspect.ts)_ ## `csdx plugins:install PLUGIN` @@ -432,7 +432,7 @@ EXAMPLES $ csdx plugins:install someuser/someplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/install.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/install.ts)_ ## `csdx plugins:link PATH` @@ -463,7 +463,7 @@ EXAMPLES $ csdx plugins:link myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/link.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/link.ts)_ ## `csdx plugins:remove [PLUGIN]` @@ -504,7 +504,7 @@ FLAGS --reinstall Reinstall all plugins after uninstalling. ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/reset.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/reset.ts)_ ## `csdx plugins:uninstall [PLUGIN]` @@ -532,7 +532,7 @@ EXAMPLES $ csdx plugins:uninstall myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/uninstall.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/uninstall.ts)_ ## `csdx plugins:unlink [PLUGIN]` @@ -576,5 +576,5 @@ DESCRIPTION Update installed plugins. ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/update.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/update.ts)_ diff --git a/packages/contentstack-auth/README.md b/packages/contentstack-auth/README.md index bf8e714ba6..e19bed162f 100644 --- a/packages/contentstack-auth/README.md +++ b/packages/contentstack-auth/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-auth/1.6.3 darwin-arm64 node-v22.13.1 +@contentstack/cli-auth/1.7.2 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-bootstrap/README.md b/packages/contentstack-bootstrap/README.md index c5c3bdecf6..37b8076db2 100644 --- a/packages/contentstack-bootstrap/README.md +++ b/packages/contentstack-bootstrap/README.md @@ -15,7 +15,7 @@ $ npm install -g @contentstack/cli-cm-bootstrap $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-bootstrap/1.17.2 darwin-arm64 node-v22.14.0 +@contentstack/cli-cm-bootstrap/1.18.2 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-branches/README.md b/packages/contentstack-branches/README.md index f6e0f05ea9..ff11cd472b 100755 --- a/packages/contentstack-branches/README.md +++ b/packages/contentstack-branches/README.md @@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-branches/1.6.2 darwin-arm64 node-v22.13.1 +@contentstack/cli-cm-branches/1.6.3 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-bulk-publish/README.md b/packages/contentstack-bulk-publish/README.md index 491ac0cd23..bd8a811391 100644 --- a/packages/contentstack-bulk-publish/README.md +++ b/packages/contentstack-bulk-publish/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-cm-bulk-publish $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-bulk-publish/1.10.4 darwin-arm64 node-v22.14.0 +@contentstack/cli-cm-bulk-publish/1.10.6 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-clone/README.md b/packages/contentstack-clone/README.md index 570ce72851..ec5f1bc21c 100644 --- a/packages/contentstack-clone/README.md +++ b/packages/contentstack-clone/README.md @@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-clone $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-clone/1.18.1 darwin-arm64 node-v22.13.1 +@contentstack/cli-cm-clone/1.20.0 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND @@ -150,5 +150,5 @@ EXAMPLES $ csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias --destination-management-token-alias --type ``` -_See code: [src/commands/cm/stacks/clone.js](https://github.com/contentstack/cli/blob/main/packages/contentstack-clone/src/commands/cm/stacks/clone.js)_ +_See code: [src/commands/cm/stacks/clone.ts](https://github.com/contentstack/cli/blob/main/packages/contentstack-clone/src/commands/cm/stacks/clone.ts)_ diff --git a/packages/contentstack-config/README.md b/packages/contentstack-config/README.md index 2208e7dfb8..4b851d3978 100644 --- a/packages/contentstack-config/README.md +++ b/packages/contentstack-config/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-config/1.17.0 darwin-arm64 node-v22.14.0 +@contentstack/cli-config/1.18.0 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-export-to-csv/.editorconfig b/packages/contentstack-export-to-csv/.editorconfig deleted file mode 100644 index beffa3084e..0000000000 --- a/packages/contentstack-export-to-csv/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/packages/contentstack-export-to-csv/.eslintrc b/packages/contentstack-export-to-csv/.eslintrc index e56091ba65..7b846193cc 100644 --- a/packages/contentstack-export-to-csv/.eslintrc +++ b/packages/contentstack-export-to-csv/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "oclif" + "extends": [ + "oclif", + "oclif-typescript" + ] } diff --git a/packages/contentstack-export-to-csv/.eslintrc.json b/packages/contentstack-export-to-csv/.eslintrc.json deleted file mode 100644 index 9a81a27548..0000000000 --- a/packages/contentstack-export-to-csv/.eslintrc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "env": { - "browser": true, - "commonjs": true, - "es6": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - } -} \ No newline at end of file diff --git a/packages/contentstack-export-to-csv/.gitignore b/packages/contentstack-export-to-csv/.gitignore index 5e74e314c4..55bf59effd 100644 --- a/packages/contentstack-export-to-csv/.gitignore +++ b/packages/contentstack-export-to-csv/.gitignore @@ -1,10 +1,31 @@ -*-debug.log -*-error.log -/.nyc_output +# Build output /dist -/tmp -/yarn.lock -coverage -node_modules -data -logs +/lib + +# Dependencies +/node_modules + +# TypeScript +*.tsbuildinfo +tsconfig.tsbuildinfo + +# oclif +/oclif.manifest.json + +# Coverage +/.nyc_output +/coverage + +# OS files +.DS_Store + +# IDE +.idea/ +.vscode/ + +# Logs +*.log +npm-debug.log* + +# Test output +/test-results diff --git a/packages/contentstack-export-to-csv/.mocharc.json b/packages/contentstack-export-to-csv/.mocharc.json new file mode 100644 index 0000000000..2febd2c36b --- /dev/null +++ b/packages/contentstack-export-to-csv/.mocharc.json @@ -0,0 +1,11 @@ +{ + "require": [ + "ts-node/register" + ], + "watch-extensions": [ + "ts" + ], + "recursive": true, + "reporter": "spec", + "timeout": 60000 +} diff --git a/packages/contentstack-export-to-csv/.snyk b/packages/contentstack-export-to-csv/.snyk deleted file mode 100644 index 8c40bf8ed4..0000000000 --- a/packages/contentstack-export-to-csv/.snyk +++ /dev/null @@ -1,10 +0,0 @@ -# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.25.1 -# ignores vulnerabilities until expiry date; change duration by modifying expiry date -ignore: - SNYK-JS-TMP-11501554: - - '*': - reason: 'https://contentstack.atlassian.net/browse/IS-5312' - expires: 2025-11-06T14:03:53.4141Z - created: 2025-08-08T14:03:53.4141Z -patch: {} \ No newline at end of file diff --git a/packages/contentstack-export-to-csv/LICENSE b/packages/contentstack-export-to-csv/LICENSE index aff1142eed..9d3fe576c8 100644 --- a/packages/contentstack-export-to-csv/LICENSE +++ b/packages/contentstack-export-to-csv/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Contentstack +Copyright (c) Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/contentstack-export-to-csv/bin/dev.cmd b/packages/contentstack-export-to-csv/bin/dev.cmd new file mode 100644 index 0000000000..0606143325 --- /dev/null +++ b/packages/contentstack-export-to-csv/bin/dev.cmd @@ -0,0 +1,2 @@ +@echo off +node "%~dp0\dev.js" %* diff --git a/packages/contentstack-export-to-csv/bin/dev.js b/packages/contentstack-export-to-csv/bin/dev.js new file mode 100644 index 0000000000..dd5484f712 --- /dev/null +++ b/packages/contentstack-export-to-csv/bin/dev.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core'); + await oclif.execute({ development: true, dir: __dirname }); +})(); diff --git a/packages/contentstack-export-to-csv/bin/run.cmd b/packages/contentstack-export-to-csv/bin/run.cmd index 968fc30758..42d2441c6e 100644 --- a/packages/contentstack-export-to-csv/bin/run.cmd +++ b/packages/contentstack-export-to-csv/bin/run.cmd @@ -1,3 +1,2 @@ @echo off - -node "%~dp0\run" %* +node "%~dp0\run.js" %* diff --git a/packages/contentstack-export-to-csv/bin/run.js b/packages/contentstack-export-to-csv/bin/run.js old mode 100755 new mode 100644 diff --git a/packages/contentstack-export-to-csv/env.example b/packages/contentstack-export-to-csv/env.example deleted file mode 100644 index 6c00c1d525..0000000000 --- a/packages/contentstack-export-to-csv/env.example +++ /dev/null @@ -1,7 +0,0 @@ -ORG= -ORG_WITH_NO_PERMISSION= -STACK= -ORG_WITH_NO_BRANCHES= -STACK_WITH_ORG_WITH_NO_BRANCHES= -BRANCH= -INVALID_BRANCH= diff --git a/packages/contentstack-export-to-csv/package.json b/packages/contentstack-export-to-csv/package.json index 47502bdf6f..2becc9cf4e 100644 --- a/packages/contentstack-export-to-csv/package.json +++ b/packages/contentstack-export-to-csv/package.json @@ -1,8 +1,8 @@ { "name": "@contentstack/cli-cm-export-to-csv", - "description": "Export entities to csv", - "version": "1.10.3", - "author": "Abhinav Gupta @abhinav-from-contentstack", + "description": "Export entries, taxonomies, terms, or organization users to CSV", + "version": "1.11.0", + "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { "@contentstack/cli-command": "~1.7.2", @@ -17,44 +17,44 @@ "devDependencies": { "@oclif/test": "^4.1.13", "@types/chai": "^4.3.20", + "@types/inquirer": "^9.0.8", "@types/mocha": "^10.0.10", + "@types/mkdirp": "^1.0.2", + "@types/node": "^20.17.50", "chai": "^4.5.0", - "debug": "^4.4.1", - "eslint": "^7.32.0", - "eslint-config-oclif": "^6.0.15", + "eslint": "^8.57.1", + "eslint-config-oclif": "^6.0.62", + "eslint-config-oclif-typescript": "^3.1.14", "mocha": "^10.8.2", + "nock": "^13.5.6", "nyc": "^15.1.0", - "oclif": "^4.17.46" + "oclif": "^4.17.46", + "sinon": "^19.0.5", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "files": [ + "/bin", + "/lib", "/npm-shrinkwrap.json", - "/oclif.manifest.json", - "/src", - "/yarn.lock" + "/oclif.manifest.json" ], "homepage": "https://github.com/contentstack/cli", "keywords": [ "contentstack", "cli", - "plugin" + "plugin", + "export", + "csv" ], - "scripts": { - "pack": "npm pack && mv *.tgz ../../build", - "postpack": "rm -f oclif.manifest.json", - "prepack": "oclif manifest && oclif readme", - "test": "nyc mocha --forbid-only \"test/**/*.test.js\"", - "test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.js\" \"test/util/*.test.js\"", - "test:unit:report": "nyc --extension .js mocha --forbid-only \"test/unit/**/*.test.js\" \"test/util/*.test.js\"", - "version": "oclif readme && git add README.md", - "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo" - }, - "main": "./src/commands/cm/export-to-csv.js", "license": "MIT", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", "oclif": { - "commands": "./src/commands", + "commands": "./lib/commands", "bin": "csdx", "repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-export-to-csv/<%- commandPath %>" }, @@ -63,5 +63,18 @@ "cm:export-to-csv": "EXPRTCSV" } }, - "repository": "https://github.com/contentstack/cli" + "repository": "https://github.com/contentstack/cli", + "scripts": { + "build": "npm run clean && npm run compile", + "clean": "rm -rf ./lib ./node_modules tsconfig.tsbuildinfo oclif.manifest.json", + "compile": "tsc -b tsconfig.json", + "lint": "eslint src/**/*.ts", + "lint:fix": "eslint src/**/*.ts --fix", + "postpack": "rm -f oclif.manifest.json", + "prepack": "pnpm compile && oclif manifest && oclif readme", + "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"", + "test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.ts\"", + "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"", + "version": "oclif readme && git add README.md" + } } diff --git a/packages/contentstack-export-to-csv/src/base-command.ts b/packages/contentstack-export-to-csv/src/base-command.ts new file mode 100644 index 0000000000..f30be43f6a --- /dev/null +++ b/packages/contentstack-export-to-csv/src/base-command.ts @@ -0,0 +1,66 @@ +/** + * Base command for export-to-csv package. + * Provides common functionality and context for all commands. + */ + +import { Command } from '@contentstack/cli-command'; +import { configHandler, log } from '@contentstack/cli-utilities'; + +/** + * Context for logging and error handling. + * Uses index signature for compatibility with ErrorContext. + */ +export interface CommandContext { + [key: string]: unknown; + command: string; + module: string; + userId: string; + email: string; + sessionId?: string; + apiKey?: string; + orgId: string; +} + +export abstract class BaseCommand extends Command { + public commandContext!: CommandContext; + + /** + * Initialize the command with context and logging. + */ + public async init(): Promise { + await super.init(); + this.commandContext = this.createCommandContext(); + log.debug('Command initialized', this.commandContext); + } + + /** + * Handle errors from the command. + */ + protected async catch(err: Error & { exitCode?: number }): Promise { + log.debug('Command error caught', { ...this.commandContext, error: err.message }); + return super.catch(err); + } + + /** + * Cleanup after command execution. + */ + protected async finally(_: Error | undefined): Promise { + log.debug('Command finished', this.commandContext); + return super.finally(_); + } + + /** + * Create context object for logging and error handling. + */ + protected createCommandContext(apiKey?: string): CommandContext { + return { + command: this.id || 'cm:export-to-csv', + module: 'export-to-csv', + userId: (configHandler.get('userUid') as string) || '', + email: (configHandler.get('email') as string) || '', + sessionId: undefined, + apiKey: apiKey || '', + orgId: (configHandler.get('oauthOrgUid') as string) || '', + }; + } +} diff --git a/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js b/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js deleted file mode 100644 index 96461c8320..0000000000 --- a/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js +++ /dev/null @@ -1,523 +0,0 @@ -const { Command } = require('@contentstack/cli-command'); -const { - configHandler, - managementSDKClient, - flags, - isAuthenticated, - cliux, - doesBranchExist, - isManagementTokenValid, -} = require('@contentstack/cli-utilities'); -const util = require('../../util'); -const config = require('../../util/config'); - -class ExportToCsvCommand extends Command { - static flags = { - action: flags.string({ - required: false, - multiple: false, - options: ['entries', 'users', 'teams', 'taxonomies'], - description: - 'Option to export data (entries, users, teams, taxonomies). ', - }), - alias: flags.string({ - char: 'a', - description: 'Alias of the management token.', - }), - org: flags.string({ - multiple: false, - required: false, - description: 'Provide organization UID to clone org users.', - }), - 'stack-name': flags.string({ - char: 'n', - multiple: false, - required: false, - description: 'Name of the stack that needs to be created as CSV filename.', - }), - 'stack-api-key': flags.string({ - char: 'k', - multiple: false, - required: false, - description: 'API Key of the source stack.', - }), - 'org-name': flags.string({ - multiple: false, - required: false, - description: 'Name of the organization that needs to be created as CSV filename.', - }), - locale: flags.string({ - required: false, - multiple: false, - description: 'Locale of entries that will be exported.', - }), - 'content-type': flags.string({ - description: 'Content type of entries that will be exported.', - required: false, - multiple: false, - }), - branch: flags.string({ - description: 'Branch from which entries will be exported.', - multiple: false, - required: false, - }), - 'team-uid': flags.string({ - description: 'Provide the UID of a specific team in an organization.', - }), - 'taxonomy-uid': flags.string({ - description: 'Provide the taxonomy UID of the related terms you want to export.', - }), - 'include-fallback': flags.boolean({ - description: - "[Optional] Include fallback locale data when exporting taxonomies. When enabled, if a taxonomy term doesn't exist in the specified locale, it will fallback to the hierarchy defined in the branch settings.", - default: false, - }), - 'fallback-locale': flags.string({ - description: - "[Optional] Specify a specific fallback locale for taxonomy export. This locale will be used when a taxonomy term doesn't exist in the primary locale. Takes priority over branch fallback hierarchy when both are specified.", - required: false, - }), - delimiter: flags.string({ - description: - "[optional] Provide a delimiter to separate individual data fields within the CSV file. For example: cm:export-to-csv --delimiter '|'", - default: ',', - }), - }; - async run() { - try { - let action, managementAPIClient; - const { - flags: { - org, - action: actionFlag, - 'org-name': orgName, - 'stack-name': stackName, - 'stack-api-key': stackAPIKey, - locale: locale, - 'content-type': contentTypesFlag, - alias: managementTokenAlias, - branch: branchUid, - 'team-uid': teamUid, - 'taxonomy-uid': taxonomyUID, - 'include-fallback': includeFallback, - 'fallback-locale': fallbackLocale, - delimiter, - }, - } = await this.parse(ExportToCsvCommand); - - if (!managementTokenAlias) { - managementAPIClient = await managementSDKClient({ host: this.cmaHost }); - if (!isAuthenticated()) { - this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, { - exit: 2, - suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], - }); - } - } - - if (actionFlag) { - action = actionFlag; - } else { - action = await util.startupQuestions(); - } - - switch (action) { - case config.exportEntries: - case 'entries': { - try { - let stack; - let stackAPIClient; - let language; - let contentTypes = []; - - if (managementTokenAlias) { - const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName); - managementAPIClient = apiClient; - stack = stackDetails; - } else { - stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); - } - - stackAPIClient = this.getStackClient(managementAPIClient, stack); - stackAPIClient = await this.checkAndUpdateBranchDetail( - branchUid, - stack, - stackAPIClient, - managementAPIClient, - ); - - const contentTypeCount = await util.getContentTypeCount(stackAPIClient); - - const environments = await util.getEnvironments(stackAPIClient); // fetch environments, because in publish details only env uid are available and we need env names - - if (contentTypesFlag) { - contentTypes = contentTypesFlag.split(',').map(this.snakeCase); - const contentTypesArray = await stackAPIClient - .contentType() - .query() - .find() - .then((res) => res.items.map((contentType) => contentType.uid)); - - const doesContentTypeExist = contentTypesArray.includes(contentTypesFlag); - - if (!doesContentTypeExist) { - throw new Error( - `The Content Type ${contentTypesFlag} was not found. Please try again. Content Type is not valid.`, - ); - } - } else { - for (let index = 0; index <= contentTypeCount / 100; index++) { - const contentTypesMap = await util.getContentTypes(stackAPIClient, index); - contentTypes = contentTypes.concat(Object.values(contentTypesMap)); // prompt for content Type - } - } - - if (contentTypes.length <= 0) { - this.log('No content types found for the given stack'); - this.exit(); - } - - if (!contentTypesFlag) { - contentTypes = await util.chooseInMemContentTypes(contentTypes); - } - - if (locale) { - language = { code: locale }; - } else { - language = await util.chooseLanguage(stackAPIClient); // prompt for language - } - - while (contentTypes.length > 0) { - let contentType = contentTypes.pop(); - - const entriesCount = await util.getEntriesCount(stackAPIClient, contentType, language.code); - let flatEntries = []; - for (let index = 0; index < entriesCount / 100; index++) { - const entriesResult = await util.getEntries(stackAPIClient, contentType, language.code, index, 100); - const flatEntriesResult = util.cleanEntries( - entriesResult.items, - language.code, - environments, - contentType, - ); - flatEntries = flatEntries.concat(flatEntriesResult); - } - let fileName = `${stackName ? stackName : stack.name}_${contentType}_${language.code}_entries_export.csv`; - util.write(this, flatEntries, fileName, 'entries', delimiter); // write to file - } - } catch (error) { - cliux.error(util.formatError(error)); - } - break; - } - case config.exportUsers: - case 'users': { - try { - let organization; - - if (org) { - organization = { uid: org, name: orgName || org }; - } else { - organization = await util.chooseOrganization(managementAPIClient, action); // prompt for organization - } - - const orgUsers = await util.getOrgUsers(managementAPIClient, organization.uid, this); - const orgRoles = await util.getOrgRoles(managementAPIClient, organization.uid, this); - const mappedUsers = util.getMappedUsers(orgUsers); - const mappedRoles = util.getMappedRoles(orgRoles); - const listOfUsers = util.cleanOrgUsers(orgUsers, mappedUsers, mappedRoles); - const fileName = `${util.kebabize( - (orgName ? orgName : organization.name).replace(config.organizationNameRegex, ''), - )}_users_export.csv`; - - util.write(this, listOfUsers, fileName, 'organization details', delimiter); - } catch (error) { - if (error.message || error.errorMessage) { - cliux.error(util.formatError(error)); - } - } - break; - } - case config.exportTeams: - case 'teams': { - try { - let organization; - if (org) { - organization = { uid: org, name: orgName || org }; - } else { - organization = await util.chooseOrganization(managementAPIClient, action); // prompt for organization - } - - await util.exportTeams(managementAPIClient, organization, teamUid, delimiter); - } catch (error) { - if (error.message || error.errorMessage) { - cliux.error(util.formatError(error)); - } - } - break; - } - case config.exportTaxonomies: - case 'taxonomies': { - let stack; - let language; - let stackAPIClient; - let finalIncludeFallback = includeFallback; - let finalFallbackLocale = fallbackLocale; - - if (managementTokenAlias) { - const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName); - managementAPIClient = apiClient; - stack = stackDetails; - } else { - stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); - } - - stackAPIClient = this.getStackClient(managementAPIClient, stack); - if (locale) { - language = { code: locale }; - } else { - language = await util.chooseLanguage(stackAPIClient); - } - - // if (includeFallback === undefined || fallbackLocale === undefined) { - // const fallbackOptions = await util.chooseFallbackOptions(stackAPIClient); - // } - - - if (fallbackLocale !== undefined) { - finalFallbackLocale = fallbackLocale; - } - - await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter, { - locale: language.code, - branch: branchUid, - include_fallback: finalIncludeFallback, - fallback_locale: finalFallbackLocale, - }); - break; - } - } - } catch (error) { - if (error.message || error.errorMessage) { - cliux.error(util.formatError(error)); - } - } - } - - snakeCase(string) { - return (string || '').split(' ').join('_').toLowerCase(); - } - - getStackClient(managementAPIClient, stack) { - const stackInit = { - api_key: stack.apiKey, - }; - if (stack?.branch_uid) stackInit['branch_uid'] = stack.branch_uid; - if (stack.token) { - return managementAPIClient.stack({ - ...stackInit, - management_token: stack.token, - }); - } - return managementAPIClient.stack(stackInit); - } - - getStackBranches(stackAPIClient) { - return stackAPIClient - .branch() - .query() - .find() - .then(({ items }) => (items !== undefined ? items : [])) - .catch(() => {}); - } - - /** - * check whether branch enabled org or not and update branch details - * @param {string} branchUid - * @param {object} stack - * @param {*} stackAPIClient - * @param {*} managementAPIClient - */ - async checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient) { - if (branchUid) { - try { - const branchExists = await doesBranchExist(stackAPIClient, branchUid); - if (branchExists?.errorCode) { - throw new Error(branchExists.errorMessage); - } - stack.branch_uid = branchUid; - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } catch (error) { - if (error?.message || error?.errorMessage) { - cliux.error(util.formatError(error)); - this.exit(); - } - } - } else { - const stackBranches = await this.getStackBranches(stackAPIClient); - if (stackBranches === undefined) { - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } else { - const { branch } = await util.chooseBranch(stackBranches); - stack.branch_uid = branch; - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } - } - return stackAPIClient; - } - - /** - * fetch stack details from alias token - * @param {string} managementTokenAlias - * @param {string} stackName - * @returns - */ - async getAliasDetails(managementTokenAlias, stackName) { - let apiClient, stackDetails; - const listOfTokens = configHandler.get('tokens'); - if (managementTokenAlias && listOfTokens[managementTokenAlias]) { - const checkManagementTokenValidity = await isManagementTokenValid( - listOfTokens[managementTokenAlias].apiKey, - listOfTokens[managementTokenAlias].token, - ); - if (Object.prototype.hasOwnProperty.call(checkManagementTokenValidity, 'message')) { - throw checkManagementTokenValidity.valid === 'failedToCheck' - ? checkManagementTokenValidity.message - : `error: Management token or stack API key is invalid. ${checkManagementTokenValidity.message}`; - } - apiClient = await managementSDKClient({ - host: this.cmaHost, - management_token: listOfTokens[managementTokenAlias].token, - }); - stackDetails = { - name: stackName || managementTokenAlias, - apiKey: listOfTokens[managementTokenAlias].apiKey, - token: listOfTokens[managementTokenAlias].token, - }; - } else if (managementTokenAlias) { - this.error('The provided management token alias was not found in your config.'); - } - return { - apiClient, - stackDetails, - }; - } - - /** - * fetch stack details on basis of the selected org and stack - * @param {*} managementAPIClient - * @param {string} stackAPIKey - * @param {string} org - * @returns - */ - async getStackDetails(managementAPIClient, stackAPIKey, org) { - let organization, stackDetails; - - if (!isAuthenticated()) { - this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, { - exit: 2, - suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], - }); - } - - if (org) { - organization = { uid: org }; - } else { - organization = await util.chooseOrganization(managementAPIClient); // prompt for organization - } - if (!stackAPIKey) { - stackDetails = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack - } else { - stackDetails = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey); - } - return stackDetails; - } - - /** - * Create a taxonomies csv file for stack and a terms csv file for associated taxonomies - * @param {string} stackName - * @param {object} stack - * @param {string} taxUID - */ - async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID, delimiter, localeOptions = {}) { - const payload = { - stackAPIClient, - type: '', - limit: config.limit || 100, - ...localeOptions, // Spread locale, branch, include_fallback, fallback_locale - }; - //check whether the taxonomy is valid or not - let taxonomies = []; - if (taxUID) { - payload['taxonomyUID'] = taxUID; - const taxonomy = await util.getTaxonomy(payload); - taxonomies.push(taxonomy); - } else { - taxonomies = await util.getAllTaxonomies(payload); - } - - if (!taxonomies?.length) { - cliux.print('info: No taxonomies found!', { color: 'blue' }); - } else { - const fileName = `${stackName ?? stack.name}_taxonomies.csv`; - const { taxonomiesData, headers } = await util.createImportableCSV(payload, taxonomies); - if (taxonomiesData?.length) { - util.write(this, taxonomiesData, fileName, 'taxonomies', delimiter, headers); - } - } - } -} - -ExportToCsvCommand.description = `Export entries, taxonomies, terms or organization users to csv using this command`; - -ExportToCsvCommand.examples = [ - 'csdx cm:export-to-csv', - '', - 'Exporting entries to CSV', - 'csdx cm:export-to-csv --action --locale --alias --content-type ', - '', - 'Exporting entries to CSV with stack name provided and branch name provided', - 'csdx cm:export-to-csv --action --locale --alias --content-type --stack-name --branch ', - '', - 'Exporting organization users to CSV', - 'csdx cm:export-to-csv --action --org ', - '', - 'Exporting organization users to CSV with organization name provided', - 'csdx cm:export-to-csv --action --org --org-name ', - '', - 'Exporting organization teams to CSV', - 'csdx cm:export-to-csv --action ', - '', - 'Exporting organization teams to CSV with org UID', - 'csdx cm:export-to-csv --action --org ', - '', - 'Exporting organization teams to CSV with team UID', - 'csdx cm:export-to-csv --action --team-uid ', - '', - 'Exporting organization teams to CSV with org UID and team UID', - 'csdx cm:export-to-csv --action --org --team-uid ', - '', - 'Exporting organization teams to CSV with org UID and team UID', - 'csdx cm:export-to-csv --action --org --team-uid --org-name ', - '', - 'Exporting taxonomies and related terms to a .CSV file with the provided taxonomy UID', - 'csdx cm:export-to-csv --action --alias --taxonomy-uid ', - '', - 'Exporting taxonomies and respective terms to a .CSV file', - 'csdx cm:export-to-csv --action --alias ', - '', - 'Exporting taxonomies and respective terms to a .CSV file with a delimiter', - 'csdx cm:export-to-csv --action --alias --delimiter ', - '', - 'Exporting taxonomies with specific locale', - 'csdx cm:export-to-csv --action --alias --locale ', - '', - 'Exporting taxonomies with fallback locale support', - 'csdx cm:export-to-csv --action --alias --locale --include-fallback', - '', - 'Exporting taxonomies with custom fallback locale', - 'csdx cm:export-to-csv --action --alias --locale --include-fallback --fallback-locale ', - '', -]; - -module.exports = ExportToCsvCommand; diff --git a/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.ts b/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.ts new file mode 100644 index 0000000000..bcda11584b --- /dev/null +++ b/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.ts @@ -0,0 +1,797 @@ +/** + * Export to CSV command. + * Migrated from: packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js + */ + +import { + configHandler, + managementSDKClient, + flags, + isAuthenticated, + cliux, + doesBranchExist, + isManagementTokenValid, + FlagInput, + log, + handleAndLogError, + ux, +} from '@contentstack/cli-utilities'; + +import config from '../../config'; +import { messages } from '../../messages'; +import { BaseCommand } from '../../base-command'; +import { + startupQuestions, + chooseOrganization, + chooseStack, + chooseBranch, + chooseLanguage, + chooseInMemContentTypes, + getContentTypeCount, + getContentTypes, + getEnvironments, + getEntriesCount, + getEntries, + cleanEntries, + write, + getOrgUsers, + getOrgRoles, + getMappedUsers, + getMappedRoles, + cleanOrgUsers, + kebabize, + exportTeams, + getAllTaxonomies, + getTaxonomy, + createImportableCSV, +} from '../../utils'; +import type { + ManagementClient, + StackClient, + StackDetails, + StackInitOptions, + OrganizationChoice, + StackChoice, + LanguageChoice, + Branch, + FlattenedEntryRow, + ExportToCsvFlags, + Taxonomy, + TaxonomyPayload, + TaxonomyLocaleOptions, + EnvironmentMap, + TokensMap, + BranchExistsResult, + ContentTypeItem, +} from '../../types'; + +export default class ExportToCsvCommand extends BaseCommand { + static readonly description = 'Export entries, taxonomies, terms or organization users to csv using this command'; + + static readonly aliases = ['cm:export-to-csv']; + + static readonly examples = [ + '$ <%= config.bin %> <%= command.id %>', + '', + 'Exporting entries to CSV', + '$ <%= config.bin %> <%= command.id %> --action entries --locale --alias --content-type ', + '', + 'Exporting entries to CSV with stack name and branch', + '$ <%= config.bin %> <%= command.id %> --action entries --locale --alias --content-type --stack-name --branch ', + '', + 'Exporting organization users to CSV', + '$ <%= config.bin %> <%= command.id %> --action users --org ', + '', + 'Exporting organization teams to CSV', + '$ <%= config.bin %> <%= command.id %> --action teams --org ', + '', + 'Exporting teams with specific team UID', + '$ <%= config.bin %> <%= command.id %> --action teams --org --team-uid ', + '', + 'Exporting taxonomies to CSV', + '$ <%= config.bin %> <%= command.id %> --action taxonomies --alias ', + '', + 'Exporting specific taxonomy with locale', + '$ <%= config.bin %> <%= command.id %> --action taxonomies --alias --taxonomy-uid --locale ', + '', + 'Exporting taxonomies with fallback locale', + '$ <%= config.bin %> <%= command.id %> --action taxonomies --alias --locale --include-fallback --fallback-locale ', + ]; + + static readonly flags: FlagInput = { + action: flags.string({ + required: false, + multiple: false, + options: ['entries', 'users', 'teams', 'taxonomies'], + description: + 'Option to export data (entries, users, teams, taxonomies). ', + }), + alias: flags.string({ + char: 'a', + description: 'Alias of the management token.', + }), + org: flags.string({ + multiple: false, + required: false, + description: 'Provide organization UID to clone org users.', + }), + 'stack-name': flags.string({ + char: 'n', + multiple: false, + required: false, + description: 'Name of the stack that needs to be created as CSV filename.', + }), + 'stack-api-key': flags.string({ + char: 'k', + multiple: false, + required: false, + description: 'API Key of the source stack.', + }), + 'org-name': flags.string({ + multiple: false, + required: false, + description: 'Name of the organization that needs to be created as CSV filename.', + }), + locale: flags.string({ + required: false, + multiple: false, + description: 'Locale of entries that will be exported.', + }), + 'content-type': flags.string({ + description: 'Content type of entries that will be exported.', + required: false, + multiple: false, + }), + branch: flags.string({ + description: 'Branch from which entries will be exported.', + multiple: false, + required: false, + }), + 'team-uid': flags.string({ + description: 'Provide the UID of a specific team in an organization.', + }), + 'taxonomy-uid': flags.string({ + description: 'Provide the taxonomy UID of the related terms you want to export.', + }), + 'include-fallback': flags.boolean({ + description: + "[Optional] Include fallback locale data when exporting taxonomies. When enabled, if a taxonomy term doesn't exist in the specified locale, it will fallback to the hierarchy defined in the branch settings.", + default: false, + }), + 'fallback-locale': flags.string({ + description: + "[Optional] Specify a specific fallback locale for taxonomy export. This locale will be used when a taxonomy term doesn't exist in the primary locale. Takes priority over branch fallback hierarchy when both are specified.", + required: false, + }), + delimiter: flags.string({ + description: + "[optional] Provide a delimiter to separate individual data fields within the CSV file. For example: cm:export-to-csv --delimiter '|'", + default: ',', + }), + }; + + async run(): Promise { + log.debug('ExportToCsvCommand run started', this.commandContext); + + try { + let action: string; + let managementAPIClient: ManagementClient; + + const { flags: parsedFlags } = await this.parse(ExportToCsvCommand); + log.debug('Flags parsed', { ...this.commandContext, flags: parsedFlags }); + + const flagsData = parsedFlags as ExportToCsvFlags; + const org = flagsData.org; + const actionFlag = flagsData.action; + const orgName = flagsData['org-name']; + const stackName = flagsData['stack-name']; + const stackAPIKey = flagsData['stack-api-key']; + const locale = flagsData.locale; + const contentTypesFlag = flagsData['content-type']; + const managementTokenAlias = flagsData.alias; + const branchUid = flagsData.branch; + const teamUid = flagsData['team-uid']; + const taxonomyUID = flagsData['taxonomy-uid']; + const includeFallback = flagsData['include-fallback'] ?? false; + const fallbackLocale = flagsData['fallback-locale']; + const delimiter = flagsData.delimiter ?? ','; + + if (!managementTokenAlias) { + log.debug('Initializing Management API client', this.commandContext); + managementAPIClient = await managementSDKClient({ host: this.cmaHost }) as ManagementClient; + log.debug('Management API client initialized', this.commandContext); + + if (!isAuthenticated()) { + log.debug('User not authenticated', this.commandContext); + this.error(messages.ERROR_NOT_LOGGED_IN, { + exit: 2, + suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], + }); + } + } + + if (actionFlag) { + action = actionFlag; + log.debug(`Action provided via flag: ${action}`, this.commandContext); + } else { + action = await startupQuestions(); + log.debug(`Action selected interactively: ${action}`, this.commandContext); + } + + switch (action) { + case messages.ACTION_EXPORT_ENTRIES: + case 'entries': { + await this.exportEntries({ + managementAPIClient: managementAPIClient!, + managementTokenAlias, + stackName, + stackAPIKey, + org, + branchUid, + locale, + contentTypesFlag, + delimiter, + }); + break; + } + + case messages.ACTION_EXPORT_USERS: + case 'users': { + await this.exportUsers({ + managementAPIClient: managementAPIClient!, + org, + orgName, + action, + delimiter, + }); + break; + } + + case messages.ACTION_EXPORT_TEAMS: + case 'teams': { + await this.exportTeamsData({ + managementAPIClient: managementAPIClient!, + org, + orgName, + action, + teamUid, + delimiter, + }); + break; + } + + case messages.ACTION_EXPORT_TAXONOMIES: + case 'taxonomies': { + await this.exportTaxonomiesData({ + managementAPIClient: managementAPIClient!, + managementTokenAlias, + stackName, + stackAPIKey, + org, + locale, + branchUid, + taxonomyUID, + includeFallback, + fallbackLocale, + delimiter, + }); + break; + } + } + + log.success('Export completed successfully', this.commandContext); + } catch (error) { + log.debug('Export failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext); + ux.action.stop('Export failed'); + this.exit(1); + } + } + + /** + * Export entries to CSV. + */ + private async exportEntries(options: { + managementAPIClient: ManagementClient; + managementTokenAlias?: string; + stackName?: string; + stackAPIKey?: string; + org?: string; + branchUid?: string; + locale?: string; + contentTypesFlag?: string; + delimiter: string; + }): Promise { + const { + managementAPIClient, + managementTokenAlias, + stackName, + stackAPIKey, + org, + branchUid, + locale, + contentTypesFlag, + delimiter, + } = options; + + log.debug('Starting entries export', this.commandContext); + + try { + let stack: StackDetails; + let stackAPIClient: StackClient; + let language: LanguageChoice; + let contentTypes: string[] = []; + let apiClient = managementAPIClient; + + if (managementTokenAlias) { + log.debug(`Using management token alias: ${managementTokenAlias}`, this.commandContext); + const { stackDetails, apiClient: client } = await this.getAliasDetails(managementTokenAlias, stackName); + apiClient = client; + stack = stackDetails; + } else { + stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); + } + + // Update context with stack API key + this.commandContext.apiKey = stack.apiKey; + log.debug(`Stack selected: ${stack.name}`, this.commandContext); + + stackAPIClient = this.getStackClient(apiClient, stack); + stackAPIClient = await this.checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, apiClient); + + cliux.loader('Fetching content types...'); + const contentTypeCount = await getContentTypeCount(stackAPIClient); + const environments: EnvironmentMap = await getEnvironments(stackAPIClient); + cliux.loader('Content types fetched'); + + log.debug(`Found ${contentTypeCount} content types`, this.commandContext); + + if (contentTypesFlag) { + contentTypes = contentTypesFlag.split(',').map(this.snakeCase); + log.debug(`Content types from flag: ${contentTypes.join(', ')}`, this.commandContext); + + const contentTypesResponse = await stackAPIClient + .contentType() + .query() + .find() as { items: ContentTypeItem[] }; + const contentTypesArray = contentTypesResponse.items.map((ct) => ct.uid); + + const doesContentTypeExist = contentTypesArray.includes(contentTypesFlag); + if (!doesContentTypeExist) { + throw new Error( + `The Content Type ${contentTypesFlag} was not found. Please try again. Content Type is not valid.`, + ); + } + } else { + for (let index = 0; index <= contentTypeCount / 100; index++) { + const contentTypesMap = await getContentTypes(stackAPIClient, index); + contentTypes = contentTypes.concat(Object.values(contentTypesMap)); + } + } + + if (contentTypes.length <= 0) { + cliux.print('No content types found for the given stack', { color: 'yellow' }); + log.info('No content types found', this.commandContext); + return; + } + + if (!contentTypesFlag) { + contentTypes = await chooseInMemContentTypes(contentTypes); + log.debug(`Content types selected: ${contentTypes.join(', ')}`, this.commandContext); + } + + if (locale) { + language = { code: locale }; + } else { + language = await chooseLanguage(stackAPIClient); + } + log.debug(`Locale: ${language.code}`, this.commandContext); + + while (contentTypes.length > 0) { + const contentType = contentTypes.pop()!; + log.debug(`Processing content type: ${contentType}`, this.commandContext); + + cliux.loader(`Fetching entries for ${contentType}...`); + const entriesCount = await getEntriesCount(stackAPIClient, contentType, language.code); + let flatEntries: FlattenedEntryRow[] = []; + + for (let index = 0; index < entriesCount / 100; index++) { + const entriesResult = await getEntries(stackAPIClient, contentType, language.code, index, 100); + const flatEntriesResult = cleanEntries(entriesResult.items, language.code, environments, contentType); + flatEntries = flatEntries.concat(flatEntriesResult); + } + cliux.loader(); + + log.info(`Exporting ${flatEntries.length} entries for ${contentType}`, this.commandContext); + const fileName = `${stackName || stack.name}_${contentType}_${language.code}_entries_export.csv`; + write(this, flatEntries, fileName, 'entries', delimiter); + } + + log.success('Entries exported successfully', this.commandContext); + } catch (error) { + log.debug('Entries export failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext, 'Failed to export entries'); + throw error; + } + } + + /** + * Export organization users to CSV. + */ + private async exportUsers(options: { + managementAPIClient: ManagementClient; + org?: string; + orgName?: string; + action: string; + delimiter: string; + }): Promise { + const { managementAPIClient, org, orgName, action, delimiter } = options; + + log.debug('Starting users export', this.commandContext); + + try { + let organization: OrganizationChoice; + + if (org) { + organization = { uid: org, name: orgName || org }; + log.debug(`Organization from flag: ${organization.name}`, this.commandContext); + } else { + organization = await chooseOrganization(managementAPIClient, action); + log.debug(`Organization selected: ${organization.name}`, this.commandContext); + } + + // Update context with org ID + this.commandContext.orgId = organization.uid; + + cliux.loader('Fetching organization users...'); + const orgUsers = await getOrgUsers(managementAPIClient, organization.uid); + const orgRoles = await getOrgRoles(managementAPIClient, organization.uid); + cliux.loader(); + + const mappedUsers = getMappedUsers(orgUsers); + const mappedRoles = getMappedRoles(orgRoles); + const listOfUsers = cleanOrgUsers(orgUsers, mappedUsers, mappedRoles); + + log.info(`Exporting ${listOfUsers.length} users`, this.commandContext); + + const fileName = `${kebabize( + (orgName || organization.name).replace(config.organizationNameRegex, ''), + )}_users_export.csv`; + + write(this, listOfUsers, fileName, 'organization details', delimiter); + log.success('Users exported successfully', this.commandContext); + } catch (error) { + log.debug('Users export failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext, 'Failed to export users'); + throw error; + } + } + + /** + * Export organization teams to CSV. + */ + private async exportTeamsData(options: { + managementAPIClient: ManagementClient; + org?: string; + orgName?: string; + action: string; + teamUid?: string; + delimiter: string; + }): Promise { + const { managementAPIClient, org, orgName, action, teamUid, delimiter } = options; + + log.debug('Starting teams export', this.commandContext); + + try { + let organization: OrganizationChoice; + + if (org) { + organization = { uid: org, name: orgName || org }; + log.debug(`Organization from flag: ${organization.name}`, this.commandContext); + } else { + organization = await chooseOrganization(managementAPIClient, action); + log.debug(`Organization selected: ${organization.name}`, this.commandContext); + } + + // Update context with org ID + this.commandContext.orgId = organization.uid; + + await exportTeams(managementAPIClient, organization, teamUid, delimiter); + log.success('Teams exported successfully', this.commandContext); + } catch (error) { + log.debug('Teams export failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext, 'Failed to export teams'); + throw error; + } + } + + /** + * Export taxonomies to CSV. + */ + private async exportTaxonomiesData(options: { + managementAPIClient: ManagementClient; + managementTokenAlias?: string; + stackName?: string; + stackAPIKey?: string; + org?: string; + locale?: string; + branchUid?: string; + taxonomyUID?: string; + includeFallback: boolean; + fallbackLocale?: string; + delimiter: string; + }): Promise { + const { + managementAPIClient, + managementTokenAlias, + stackName, + stackAPIKey, + org, + locale, + branchUid, + taxonomyUID, + includeFallback, + fallbackLocale, + delimiter, + } = options; + + log.debug('Starting taxonomies export', this.commandContext); + + try { + let stack: StackDetails; + let language: LanguageChoice; + let stackAPIClient: StackClient; + let apiClient = managementAPIClient; + + if (managementTokenAlias) { + log.debug(`Using management token alias: ${managementTokenAlias}`, this.commandContext); + const { stackDetails, apiClient: client } = await this.getAliasDetails(managementTokenAlias, stackName); + apiClient = client; + stack = stackDetails; + } else { + stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); + } + + // Update context with stack API key + this.commandContext.apiKey = stack.apiKey; + log.debug(`Stack selected: ${stack.name}`, this.commandContext); + + stackAPIClient = this.getStackClient(apiClient, stack); + + if (locale) { + language = { code: locale }; + } else { + language = await chooseLanguage(stackAPIClient); + } + log.debug(`Locale: ${language.code}`, this.commandContext); + + await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter, { + locale: language.code, + branch: branchUid, + include_fallback: includeFallback, + fallback_locale: fallbackLocale, + }); + + log.success('Taxonomies exported successfully', this.commandContext); + } catch (error) { + log.debug('Taxonomies export failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext, 'Failed to export taxonomies'); + throw error; + } + } + + snakeCase(string: string): string { + return (string || '').split(' ').join('_').toLowerCase(); + } + + getStackClient(managementAPIClient: ManagementClient, stack: StackDetails): StackClient { + const stackInit: StackInitOptions = { + api_key: stack.apiKey, + }; + + if (stack?.branch_uid) { + stackInit.branch_uid = stack.branch_uid; + } + + if (stack.token) { + return managementAPIClient.stack({ + ...stackInit, + management_token: stack.token, + }); + } + + return managementAPIClient.stack(stackInit); + } + + getStackBranches(stackAPIClient: StackClient): Promise { + return stackAPIClient + .branch() + .query() + .find() + .then(({ items }: { items?: Branch[] }) => (items !== undefined ? items : [])) + .catch(() => []); + } + + /** + * Check whether branch enabled org or not and update branch details. + */ + async checkAndUpdateBranchDetail( + branchUid: string | undefined, + stack: StackDetails, + stackAPIClient: StackClient, + managementAPIClient: ManagementClient, + ): Promise { + if (branchUid) { + log.debug(`Checking branch: ${branchUid}`, this.commandContext); + try { + const branchExists = await doesBranchExist(stackAPIClient, branchUid) as BranchExistsResult; + if (branchExists?.errorCode) { + throw new Error(branchExists.errorMessage); + } + stack.branch_uid = branchUid; + stackAPIClient = this.getStackClient(managementAPIClient, stack); + log.debug(`Branch validated: ${branchUid}`, this.commandContext); + } catch (error) { + log.debug('Branch validation failed', { ...this.commandContext, error }); + handleAndLogError(error, this.commandContext, 'Invalid branch'); + this.exit(1); + } + } else { + const stackBranches = await this.getStackBranches(stackAPIClient); + if (stackBranches === undefined || stackBranches.length === 0) { + stackAPIClient = this.getStackClient(managementAPIClient, stack); + } else { + const { branch } = await chooseBranch(stackBranches); + stack.branch_uid = branch; + stackAPIClient = this.getStackClient(managementAPIClient, stack); + log.debug(`Branch selected: ${branch}`, this.commandContext); + } + } + return stackAPIClient; + } + + /** + * Fetch stack details from alias token. + */ + async getAliasDetails( + managementTokenAlias: string, + stackName: string | undefined, + ): Promise<{ apiClient: ManagementClient; stackDetails: StackDetails }> { + log.debug(`Resolving alias: ${managementTokenAlias}`, this.commandContext); + + const listOfTokens = configHandler.get('tokens') as TokensMap | undefined; + + if (managementTokenAlias && listOfTokens?.[managementTokenAlias]) { + const tokenConfig = listOfTokens[managementTokenAlias]; + + cliux.loader('Validating management token...'); + const checkManagementTokenValidity = await isManagementTokenValid( + tokenConfig.apiKey, + tokenConfig.token, + ) as { valid?: string; message?: string }; + cliux.loader(); + + if (Object.prototype.hasOwnProperty.call(checkManagementTokenValidity, 'message')) { + const errorMsg = checkManagementTokenValidity.valid === 'failedToCheck' + ? checkManagementTokenValidity.message + : `Management token or stack API key is invalid. ${checkManagementTokenValidity.message}`; + log.debug('Token validation failed', { ...this.commandContext, error: errorMsg }); + throw new Error(errorMsg); + } + + log.debug('Token validated successfully', this.commandContext); + + const apiClient = await managementSDKClient({ + host: this.cmaHost, + management_token: tokenConfig.token, + }) as ManagementClient; + + const stackDetails: StackDetails = { + name: stackName || managementTokenAlias, + apiKey: tokenConfig.apiKey, + token: tokenConfig.token, + }; + + return { apiClient, stackDetails }; + } else if (managementTokenAlias) { + log.debug('Alias not found in config', this.commandContext); + this.error('The provided management token alias was not found in your config.', { + exit: 1, + suggestions: ['Use "csdx auth:tokens:add" to add a new token'], + }); + } + + throw new Error('Management token alias is required.'); + } + + /** + * Fetch stack details on basis of the selected org and stack. + */ + async getStackDetails( + managementAPIClient: ManagementClient, + stackAPIKey: string | undefined, + org: string | undefined, + ): Promise { + log.debug('Getting stack details', this.commandContext); + + if (!isAuthenticated()) { + log.debug('User not authenticated', this.commandContext); + this.error(messages.ERROR_NOT_LOGGED_IN, { + exit: 2, + suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], + }); + } + + let organization: OrganizationChoice; + let stackDetails: StackChoice; + + if (org) { + organization = { uid: org, name: org }; + } else { + organization = await chooseOrganization(managementAPIClient); + } + log.debug(`Organization: ${organization.name}`, this.commandContext); + + if (!stackAPIKey) { + stackDetails = await chooseStack(managementAPIClient, organization.uid); + } else { + stackDetails = await chooseStack(managementAPIClient, organization.uid, stackAPIKey); + } + log.debug(`Stack: ${stackDetails.name}`, this.commandContext); + + return { + name: stackDetails.name, + apiKey: stackDetails.apiKey, + }; + } + + /** + * Create a taxonomies csv file for stack and a terms csv file for associated taxonomies. + */ + async createTaxonomyAndTermCsvFile( + stackAPIClient: StackClient, + stackName: string | undefined, + stack: StackDetails, + taxUID: string | undefined, + delimiter: string, + localeOptions: TaxonomyLocaleOptions = {}, + ): Promise { + log.debug('Creating taxonomy CSV', this.commandContext); + + const payload: TaxonomyPayload = { + stackAPIClient, + limit: config.limit || 100, + ...localeOptions, + }; + + let taxonomies: Taxonomy[] = []; + + cliux.loader('Fetching taxonomies...'); + if (taxUID) { + payload.taxonomyUID = taxUID; + const taxonomy = await getTaxonomy(payload); + taxonomies.push(taxonomy); + log.debug(`Single taxonomy fetched: ${taxUID}`, this.commandContext); + } else { + taxonomies = await getAllTaxonomies(payload); + log.debug(`Fetched ${taxonomies.length} taxonomies`, this.commandContext); + } + cliux.loader(); + + if (!taxonomies?.length) { + cliux.print('No taxonomies found!', { color: 'yellow' }); + log.info('No taxonomies found', this.commandContext); + return; + } + + cliux.loader('Generating CSV...'); + const fileName = `${stackName ?? stack.name}_taxonomies.csv`; + const { taxonomiesData, headers } = await createImportableCSV(payload, taxonomies); + cliux.loader(); + + if (taxonomiesData?.length) { + log.info(`Exporting ${taxonomiesData.length} taxonomy records`, this.commandContext); + write(this, taxonomiesData, fileName, 'taxonomies', delimiter, headers); + } + } +} diff --git a/packages/contentstack-export-to-csv/src/config/index.ts b/packages/contentstack-export-to-csv/src/config/index.ts new file mode 100644 index 0000000000..64f844a997 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/config/index.ts @@ -0,0 +1,11 @@ +/** + * Configuration constants for export-to-csv command. + * Migrated from: packages/contentstack-export-to-csv/src/util/config.js + */ + +const config = { + limit: 100, + organizationNameRegex: /'/g, +}; + +export default config; diff --git a/packages/contentstack-export-to-csv/src/index.ts b/packages/contentstack-export-to-csv/src/index.ts new file mode 100644 index 0000000000..3301273a46 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/index.ts @@ -0,0 +1,17 @@ +/** + * @contentstack/cli-cm-export-to-csv + * + * Export entries, taxonomies, terms, or organization users to CSV files. + * Migrated from: packages/contentstack-export-to-csv + */ + +import ExportToCsv from './commands/cm/export-to-csv'; + +export { ExportToCsv }; +export { BaseCommand } from './base-command'; +export type { CommandContext } from './base-command'; + +// Re-export utilities for external use +export * from './utils'; +export * from './types'; +export { default as config } from './config'; diff --git a/packages/contentstack-export-to-csv/src/messages/index.ts b/packages/contentstack-export-to-csv/src/messages/index.ts new file mode 100644 index 0000000000..417e10d8d4 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/messages/index.ts @@ -0,0 +1,83 @@ +/** + * Messages and strings used in the export-to-csv command. + */ + +export const messages = { + // Command description + COMMAND_DESCRIPTION: 'Export entries, taxonomies, terms, or organization users to CSV using this command', + + // Flag descriptions + FLAG_ACTION: 'Option to export data (entries, users, teams, taxonomies). ', + FLAG_ALIAS: 'Alias of the management token', + FLAG_ORG: 'Provide organization UID to clone org users', + FLAG_STACK_NAME: 'Name of the stack that needs to be created as CSV filename', + FLAG_STACK_API_KEY: 'Provide the stack API key of the source stack', + FLAG_ORG_NAME: 'Name of the organization that needs to be created as CSV filename', + FLAG_LOCALE: 'Locale of entries that will be exported', + FLAG_CONTENT_TYPE: 'Content type of entries that will be exported', + FLAG_BRANCH: 'Branch from which entries will be exported', + FLAG_TEAM_UID: 'Provide the UID of a specific team in an organization', + FLAG_TAXONOMY_UID: 'Provide the taxonomy UID of the related terms you want to export', + FLAG_INCLUDE_FALLBACK: + "[Optional] Include fallback locale data when exporting taxonomies. When enabled, if a taxonomy term doesn't exist in the specified locale, it will fallback to the hierarchy defined in the branch settings.", + FLAG_FALLBACK_LOCALE: + "[Optional] Specify a specific fallback locale for taxonomy export. This locale will be used when a taxonomy term doesn't exist in the primary locale. Takes priority over branch fallback hierarchy when both are specified.", + FLAG_DELIMITER: + "[Optional] Provide a delimiter to separate individual data fields within the CSV file. For example: cm:export-to-csv --delimiter '|'", + + // Action choices for interactive prompts + ACTION_EXPORT_ENTRIES: 'Export entries to a .CSV file', + ACTION_EXPORT_USERS: "Export organization users' data to a .CSV file", + ACTION_EXPORT_TEAMS: "Export organization teams' data to a .CSV file", + ACTION_EXPORT_TAXONOMIES: 'Export taxonomies to a .CSV file', + ACTION_CANCEL: 'Cancel and Exit', + + // Error messages + ERROR_NOT_LOGGED_IN: 'You need to either login or provide a management token to execute this command', + ERROR_LOGIN_REQUIRED: 'You need to login to execute this command. See: auth:login --help', + ERROR_API_FAILED: 'Something went wrong! Please try again', + ERROR_CONTENT_TYPE_NOT_FOUND: 'The Content Type {contentType} was not found. Please try again.', + ERROR_NO_CONTENT_TYPES: 'No content types found for the given stack', + ERROR_SELECT_CONTENT_TYPE: 'Please select at least one content type', + ERROR_ORG_NOT_FOUND: 'Org UID not found', + ERROR_MANAGEMENT_TOKEN_NOT_FOUND: 'The provided management token alias was not found in your config', + ERROR_MANAGEMENT_TOKEN_INVALID: 'Management token or stack API key is invalid', + ERROR_ADMIN_ACCESS_DENIED: "Unable to export data. Make sure you're an admin or owner of this organization", + + // Info messages + INFO_NO_TAXONOMIES: 'No taxonomies found!', + INFO_WRITING_FILE: 'Writing {type} to file: "{path}"', + INFO_EXPORTING_TEAMS: 'Exporting the teams of Organisation {orgName}', + INFO_EXPORTING_TEAM: 'Exporting the team with uid {teamUid} in Organisation {orgName}', + INFO_EXPORTING_TEAM_USERS: 'Exporting the teams user data for {target}', + INFO_EXPORTING_STACK_ROLES: 'Exporting the stack role details for {target}', + INFO_NO_TEAMS: + 'The organization {orgName} does not have any teams associated with it. Please verify and provide the correct organization name.', + + // Warning messages + WARNING_STACK_ACCESS_DENIED: + 'Admin access denied to the following stacks using the provided API keys. Please get in touch with the stack owner to request access.', + + // Prompt messages + PROMPT_CHOOSE_ACTION: 'Choose Action', + PROMPT_CHOOSE_ORG: 'Choose an Organization', + PROMPT_CHOOSE_STACK: 'Choose a Stack', + PROMPT_CHOOSE_BRANCH: 'Choose a Branch', + PROMPT_CHOOSE_CONTENT_TYPE: 'Choose Content Type (Press Space to select the content types)', + PROMPT_CHOOSE_LANGUAGE: 'Choose Language', + PROMPT_CONTINUE_EXPORT: + 'Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.', +} as const; + +/** + * Template string replacer utility. + * @param message - Message with placeholders like {key} + * @param params - Object with key-value pairs to replace + */ +export function formatMessage(message: string, params: Record): string { + let result = message; + for (const [key, value] of Object.entries(params)) { + result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value); + } + return result; +} diff --git a/packages/contentstack-export-to-csv/src/types/index.ts b/packages/contentstack-export-to-csv/src/types/index.ts new file mode 100644 index 0000000000..6981dd7bdf --- /dev/null +++ b/packages/contentstack-export-to-csv/src/types/index.ts @@ -0,0 +1,727 @@ +/** + * Type definitions for export-to-csv package. + * + * Uses types from @contentstack/cli-utilities where available. + * Defines package-specific types for API responses, CSV structures, and CLI flags. + */ + +// Re-export shared types from cli-utilities +export type { + Organization, + ContentType, + Environment, + Locale, + PrintOptions, + InquirePayload, + ContentstackClient as ManagementClient, +} from '@contentstack/cli-utilities'; + +// Import ContentstackClient from cli-utilities (which re-exports from @contentstack/management) +import { ContentstackClient } from '@contentstack/cli-utilities'; + +// ============================================================================ +// Management SDK Types +// ============================================================================ + +/** + * Stack API Client - returned by managementClient.stack() + */ +export type StackClient = ReturnType; + +// ============================================================================ +// CLI Flag Types +// ============================================================================ + +/** + * Available export actions. + */ +export type ExportAction = 'entries' | 'users' | 'teams' | 'taxonomies'; + +/** + * Parsed CLI flags for the export-to-csv command. + */ +export interface ExportToCsvFlags { + [key: string]: unknown; + action?: ExportAction; + alias?: string; + org?: string; + 'stack-name'?: string; + 'stack-api-key'?: string; + 'org-name'?: string; + locale?: string; + 'content-type'?: string; + branch?: string; + 'team-uid'?: string; + 'taxonomy-uid'?: string; + 'include-fallback'?: boolean; + 'fallback-locale'?: string; + delimiter?: string; +} + +/** + * Token configuration from config handler. + */ +export interface TokenConfig { + apiKey: string; + token: string; +} + +/** + * Tokens map from config handler. + */ +export interface TokensMap { + [alias: string]: TokenConfig; +} + +// ============================================================================ +// Stack & Branch Types +// ============================================================================ + +/** + * Stack details used throughout the command. + */ +export interface StackDetails { + name: string; + apiKey: string; + token?: string; + branch_uid?: string; +} + +/** + * Branch data structure. + */ +export interface Branch { + uid: string; + source?: string; + alias?: string[]; +} + +/** + * Stack initialization options for SDK. + */ +export interface StackInitOptions { + api_key: string; + branch_uid?: string; + management_token?: string; +} + +/** + * Branch existence check result. + */ +export interface BranchExistsResult { + errorCode?: string; + errorMessage?: string; +} + +/** + * Stack item structure from API. + */ +export interface StackItem { + name: string; + api_key: string; +} + +// ============================================================================ +// Organization Types +// ============================================================================ + +/** + * Organization item structure from API. + */ +export interface OrganizationItem { + uid: string; + name: string; + items?: OrganizationItem[]; +} + +/** + * Organization with roles structure from getUser API. + */ +export interface OrgWithRoles { + uid: string; + name: string; + is_owner?: boolean; + org_roles?: Array<{ admin?: boolean }>; +} + +/** + * Organization role map structure. + */ +export interface OrgRoleMap { + member?: string; + admin?: string; + [key: string]: string | undefined; +} + +// ============================================================================ +// Organization User Types +// ============================================================================ + +/** + * Organization user from getInvitations API. + */ +export interface OrgUser { + uid?: string; + email: string; + user_uid: string; + org_uid?: string; + invited_by: string; + status: string; + created_at: string; + updated_at: string; + is_owner?: boolean; + org_roles?: string[]; +} + +/** + * Organization role data. + */ +export interface OrgRole { + uid: string; + name: string; + description?: string; + admin?: boolean; +} + +/** + * Paginated response for organization users. + */ +export interface OrgUsersResponse { + items: OrgUser[]; +} + +/** + * Paginated response for organization roles. + */ +export interface OrgRolesResponse { + items: OrgRole[]; +} + +/** + * User response from getUser API. + */ +export interface UserResponse { + organizations: Array<{ + uid: string; + name: string; + is_owner?: boolean; + org_roles?: Array<{ admin?: boolean }>; + getInvitations?: () => Promise; + roles?: () => Promise; + }>; +} + +// ============================================================================ +// Content Type & Entry Types +// ============================================================================ + +/** + * Content type count response. + */ +export interface ContentTypeCountResponse { + content_types: number; +} + +/** + * Content type item from API. + */ +export interface ContentTypeItem { + uid: string; + title?: string; +} + +/** + * Language/locale item structure from API. + */ +export interface LanguageItem { + name: string; + code: string; +} + +/** + * Environment item structure from API. + */ +export interface EnvironmentItem { + uid: string; + name: string; +} + +/** + * Entry publish details. + */ +export interface PublishDetails { + environment: string; + locale: string; + time?: string; +} + +/** + * Workflow details for an entry. + */ +export interface WorkflowDetails { + name?: string; + uid?: string; +} + +/** + * Raw entry from API (before transformation). + */ +export interface RawEntry { + uid: string; + title: string; + locale: string; + content_type_uid?: string; + publish_details?: PublishDetails[]; + _workflow?: WorkflowDetails; + setWorkflowStage?: unknown; + stackHeaders?: unknown; + update?: unknown; + delete?: unknown; + fetch?: unknown; + publish?: unknown; + unpublish?: unknown; + import?: unknown; + publishRequest?: unknown; + [key: string]: unknown; +} + +/** + * Entries query response from SDK. + */ +export interface EntriesResponse { + items: RawEntry[]; + count?: number; +} + +/** + * Entry count response from SDK. + */ +export interface EntriesCountResponse { + entries: number; +} + +/** + * Language/locale data with code and name. + */ +export interface LanguageChoice { + code: string; + name?: string; +} + +// ============================================================================ +// Team Types +// ============================================================================ + +/** + * Stack role map with stack details. + */ +export interface StackRoleMap { + [key: string]: string | { name: string; uid: string }; +} + +/** + * Team user data. + */ +export interface TeamUser { + [key: string]: unknown; + userId: string; + email: string; + firstName?: string; + lastName?: string; + active?: boolean; + orgInvitationStatus?: string; + 'team-name'?: string; + 'team-uid'?: string; +} + +/** + * Stack role mapping for teams. + */ +export interface StackRoleMapping { + stackApiKey: string; + roles: string[]; +} + +/** + * Raw team data from API (before transformation). + */ +export interface RawTeam { + uid: string; + name: string; + description?: string; + organizationRole: string; + users?: TeamUser[]; + stackRoleMapping?: StackRoleMapping[]; + _id?: string; + createdAt?: string; + createdBy?: string; + updatedAt?: string; + updatedBy?: string; + __v?: number; + createdByUserName?: string; + updatedByUserName?: string; + organizationUid?: string; + urlPath?: string; + update?: unknown; + delete?: unknown; + fetch?: unknown; + stackRoleMappings?: unknown; + teamUsers?: unknown; +} + +/** + * Cleaned team data for CSV export. + */ +export interface CleanedTeam { + uid: string; + name: string; + description: string; + organizationRole: string; + users?: TeamUser[]; + stackRoleMapping?: StackRoleMapping[]; + Total_Members: number; +} + +/** + * Teams fetch response. + */ +export interface TeamsResponse { + items: RawTeam[]; + count?: number; +} + +/** + * Stack role data from SDK. + */ +export interface StackRole { + uid: string; + name: string; + stack?: { + api_key: string; + name: string; + uid: string; + }; +} + +/** + * Stack roles response. + */ +export interface StackRolesResponse { + items: StackRole[]; +} + +// ============================================================================ +// Taxonomy Types +// ============================================================================ + +/** + * Taxonomy data structure. + */ +export interface Taxonomy { + uid: string; + name: string; + description?: string; +} + +/** + * Taxonomy term data structure. + */ +export interface Term { + uid: string; + name: string; + parent_uid: string | null; + depth: number; +} + +/** + * Taxonomies query response. + */ +export interface TaxonomiesResponse { + items: Taxonomy[]; + count: number; +} + +/** + * Terms query response. + */ +export interface TermsResponse { + items: Term[]; + count: number; +} + +/** + * Taxonomy SDK handler payload. + */ +export interface TaxonomyPayload { + stackAPIClient: StackClient; + type?: 'taxonomies' | 'taxonomy' | 'terms' | 'export-taxonomies'; + taxonomyUID?: string; + format?: string; + locale?: string; + branch?: string; + include_fallback?: boolean; + fallback_locale?: string; + limit: number; +} + +/** + * Locale options for taxonomy export. + */ +export interface TaxonomyLocaleOptions { + locale?: string; + branch?: string; + include_fallback?: boolean; + fallback_locale?: string; +} + +// ============================================================================ +// CSV Row Types +// ============================================================================ + +/** + * Flattened entry row for CSV export. + */ +export interface FlattenedEntryRow { + uid: string; + title: string; + locale: string; + content_type_uid: string; + publish_details: string[]; + _workflow: string; + ACL: string; + [key: string]: unknown; +} + +/** + * Organization user row for CSV export. + */ +export interface OrgUserCsvRow { + [key: string]: unknown; + Email: string; + 'User UID': string; + 'Organization Role': string; + Status: string; + 'Invited By': string; + 'Created Time': string; + 'Updated Time': string; +} + +/** + * Team CSV row for export. + */ +export interface TeamCsvRow { + [key: string]: unknown; + uid: string; + name: string; + description: string; + organizationRole: string; + Total_Members: number; +} + +/** + * Team user CSV row for export. + */ +export interface TeamUserCsvRow { + [key: string]: unknown; + userId: string; + email: string; + firstName?: string; + lastName?: string; + 'team-name': string; + 'team-uid': string; +} + +/** + * Stack role mapping CSV row for export. + */ +export interface StackRoleMappingCsvRow { + [key: string]: unknown; + 'Team Name': string; + 'Team Uid': string; + 'Stack Name': string; + 'Stack Uid': string; + 'Role Name': string; + 'Role Uid': string; +} + +/** + * Taxonomy CSV row for export. + */ +export interface TaxonomyCsvRow { + [key: string]: unknown; + 'Taxonomy UID': string; + Name: string; + Description: string; +} + +/** + * Term CSV row for export. + */ +export interface TermCsvRow { + [key: string]: unknown; + 'Taxonomy UID': string; + UID: string; + Name: string; + 'Parent UID': string | null; + Depth: number; +} + +// ============================================================================ +// Pagination Types +// ============================================================================ + +/** + * Pagination parameters for API calls. + */ +export interface PaginationParams { + skip: number; + page: number; + limit: number; +} + +/** + * Query parameters for taxonomy/term API calls. + */ +export interface TaxonomyQueryParams { + include_count: boolean; + limit: number; + skip?: number; + locale?: string; + branch?: string; + include_fallback?: boolean; + fallback_locale?: string; + depth?: number; +} + +// ============================================================================ +// Error Types +// ============================================================================ + +/** + * API error structure. + */ +export interface ApiError { + errorMessage?: string; + error_message?: string; + message?: string; + errors?: Record; +} + +// ============================================================================ +// Mapping Types +// ============================================================================ + +/** + * Map of organization names to UIDs. + */ +export type OrgMap = Record; + +/** + * Map of stack names to API keys. + */ +export type StackMap = Record; + +/** + * Map of content type titles to UIDs. + */ +export type ContentTypeMap = Record; + +/** + * Map of language names to codes. + */ +export type LanguageMap = Record; + +/** + * Map of environment UIDs to names. + */ +export type EnvironmentMap = Record; + +/** + * Map of user UIDs to emails. + */ +export type UserMap = Record; + +/** + * Map of role UIDs to names. + */ +export type RoleMap = Record; + +// ============================================================================ +// Inquirer Prompt Types +// ============================================================================ + +/** + * Organization choice result from prompt. + */ +export interface OrganizationChoice { + name: string; + uid: string; +} + +/** + * Stack choice result from prompt. + */ +export interface StackChoice { + name: string; + apiKey: string; +} + +/** + * Branch choice result from prompt. + */ +export interface BranchChoice { + branch: string; +} + +/** + * Fallback options result from prompt. + */ +export interface FallbackOptions { + includeFallback: boolean; + fallbackLocale: string | null; +} + +// ============================================================================ +// Error Types +// ============================================================================ + +/** + * API error with optional errorMessage and message properties. + */ +export interface ErrorWithMessage { + errorMessage?: string; + message?: string; +} + +/** + * Taxonomy-specific error with errors object. + */ +export interface TaxonomyError { + errorMessage?: string; + message?: string; + errors?: { + taxonomy?: string; + term?: string; + }; +} + +// ============================================================================ +// CSV Types +// ============================================================================ + +/** + * CSV row data type - can be any record with string keys or string array. + */ +export type CsvRow = Record | string[]; + +// ============================================================================ +// CSV Import Result Types +// ============================================================================ + +/** + * Result from createImportableCSV function. + */ +export interface ImportableCsvResult { + taxonomiesData: string[][]; + headers: string[]; +} diff --git a/packages/contentstack-export-to-csv/src/util/client.js b/packages/contentstack-export-to-csv/src/util/client.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/contentstack-export-to-csv/src/util/config.js b/packages/contentstack-export-to-csv/src/util/config.js deleted file mode 100644 index e0bd76a34a..0000000000 --- a/packages/contentstack-export-to-csv/src/util/config.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - limit:100, - cancelString: 'Cancel and Exit', - exportEntries: 'Export entries to a .CSV file', - exportUsers: "Export organization users' data to a .CSV file", - exportTeams: "Export organization teams' data to a .CSV file", - exportTaxonomies: 'Export taxonomies to a .CSV file', - adminError: "Unable to export data. Make sure you're an admin or owner of this organization", - organizationNameRegex: /\'/, - CLI_EXPORT_CSV_LOGIN_FAILED: "You need to login to execute this command. See: auth:login --help", - CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command", - CLI_EXPORT_CSV_API_FAILED: 'Something went wrong! Please try again' -}; diff --git a/packages/contentstack-export-to-csv/src/util/index.js b/packages/contentstack-export-to-csv/src/util/index.js deleted file mode 100644 index e2b8923605..0000000000 --- a/packages/contentstack-export-to-csv/src/util/index.js +++ /dev/null @@ -1,1313 +0,0 @@ -const os = require('os'); -const fs = require('fs'); -const mkdirp = require('mkdirp'); -const find = require('lodash/find'); -const cloneDeep = require('lodash/cloneDeep'); -const omit = require('lodash/omit'); -const flat = require('lodash/flatten'); -const fastcsv = require('fast-csv'); -const inquirer = require('inquirer'); -const debug = require('debug')('export-to-csv'); -const checkboxPlus = require('inquirer-checkbox-plus-prompt'); -const config = require('./config.js'); -const { - cliux, - configHandler, - HttpClient, - messageHandler, - managementSDKClient, - ContentstackClient, -} = require('@contentstack/cli-utilities'); - -const directory = './data'; -const delimeter = os.platform() === 'win32' ? '\\' : '/'; - -// Register checkbox-plus here. -inquirer.registerPrompt('checkbox-plus', checkboxPlus); - -function chooseOrganization(managementAPIClient, action) { - return new Promise(async (resolve, reject) => { - try { - let organizations; - if (action === config.exportUsers || action === config.exportTeams || action === 'teams') { - organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient); - } else { - organizations = await getOrganizations(managementAPIClient); - } - let orgList = Object.keys(organizations); - orgList.push(config.cancelString); - let _chooseOrganization = [ - { - type: 'list', - name: 'chosenOrg', - message: 'Choose an Organization', - choices: orgList, - loop: false, - }, - ]; - inquirer - .prompt(_chooseOrganization) - .then(({ chosenOrg }) => { - if (chosenOrg === config.cancelString) exitProgram(); - resolve({ name: chosenOrg, uid: organizations[chosenOrg] }); - }) - .catch(reject); - } catch (error) { - reject(error); - } - }); -} - -async function getOrganizations(managementAPIClient) { - try { - return await getOrganizationList(managementAPIClient, { skip: 0, page: 1, limit: 100 }, []); - } catch (error) { - throw error; - } -} - -async function getOrganizationList(managementAPIClient, params, result = []) { - let organizations; - const configOrgUid = configHandler.get('oauthOrgUid'); - - if (configOrgUid) { - organizations = await managementAPIClient.organization(configOrgUid).fetch(); - result = result.concat([organizations]); - } else { - organizations = await managementAPIClient.organization().fetchAll({ limit: 100 }); - result = result.concat(organizations.items); - } - - if (!organizations.items || (organizations.items && organizations.items.length < params.limit)) { - const orgMap = {}; - for (const org of result) { - orgMap[org.name] = org.uid; - } - return orgMap; - } else { - params.skip = params.page * params.limit; - params.page++; - await wait(200); - return getOrganizationList(managementAPIClient, params, result); - } -} - -async function getOrganizationsWhereUserIsAdmin(managementAPIClient) { - try { - let result = {}; - const configOrgUid = configHandler.get('oauthOrgUid'); - - if (configOrgUid) { - const response = await managementAPIClient.organization(configOrgUid).fetch(); - result[response.name] = response.uid; - } else { - const response = await managementAPIClient.getUser({ include_orgs_roles: true }); - const organizations = response.organizations.filter((org) => { - if (org.org_roles) { - const org_role = org.org_roles.shift(); - return org_role.admin; - } - return org.is_owner === true; - }); - - organizations.forEach((org) => { - result[org.name] = org.uid; - }); - } - - return result; - } catch (error) { - throw error; - } -} - -function chooseStack(managementAPIClient, orgUid, stackApiKey) { - return new Promise(async (resolve, reject) => { - try { - let stacks = await getStacks(managementAPIClient, orgUid); - - if (stackApiKey) { - const stackName = Object.keys(stacks).find((key) => stacks[key] === stackApiKey); - - if (stackName) { - resolve({ name: stackName, apiKey: stackApiKey }); - } else { - throw new Error('Could not find stack'); - } - return; - } - - let stackList = Object.keys(stacks); - stackList.push(config.cancelString); - let _chooseStack = [ - { - type: 'list', - name: 'chosenStack', - message: 'Choose a Stack', - choices: stackList, - }, - ]; - - inquirer - .prompt(_chooseStack) - .then(({ chosenStack }) => { - if (chosenStack === config.cancelString) exitProgram(); - resolve({ name: chosenStack, apiKey: stacks[chosenStack] }); - }) - .catch(reject); - } catch (error) { - reject(error); - } - }); -} - -async function chooseBranch(branchList) { - try { - const branchesArray = branchList.map((branch) => branch.uid); - - let _chooseBranch = [ - { - type: 'list', - name: 'branch', - message: 'Choose a Branch', - choices: branchesArray, - }, - ]; - return await inquirer.prompt(_chooseBranch); - } catch (err) { - cliux.error(err); - } -} - -function getStacks(managementAPIClient, orgUid) { - return new Promise((resolve, reject) => { - let result = {}; - managementAPIClient - .stack({ organization_uid: orgUid }) - .query({ query: {} }) - .find() - .then((stacks) => { - stacks.items.forEach((stack) => { - result[stack.name] = stack.api_key; - }); - resolve(result); - }) - .catch((error) => { - reject(error); - }); - }); -} - -function chooseContentType(stackAPIClient, skip) { - return new Promise(async (resolve, reject) => { - let contentTypes = await getContentTypes(stackAPIClient, skip); - let contentTypesList = Object.values(contentTypes); - let _chooseContentType = [ - { - type: 'checkbox', - message: 'Choose Content Type (Press Space to select the content types) ', - choices: contentTypesList, - name: 'chosenContentTypes', - loop: false, - }, - ]; - - inquirer - .prompt(_chooseContentType) - .then(({ chosenContentTypes }) => resolve(chosenContentTypes)) - .catch(reject); - }); -} - -function chooseInMemContentTypes(contentTypesList) { - return new Promise((resolve, reject) => { - let _chooseContentType = [ - { - type: 'checkbox-plus', - message: 'Choose Content Type (Press Space to select the content types)', - choices: contentTypesList, - name: 'chosenContentTypes', - loop: false, - highlight: true, - searchable: true, - source: (_, input) => { - input = input || ''; - const inputArray = input.split(' '); - return new Promise((resolveSource) => { - const contentTypes = contentTypesList.filter((contentType) => { - let shouldInclude = true; - inputArray.forEach((inputChunk) => { - // if any term to filter by doesn't exist, exclude - if (!contentType.toLowerCase().includes(inputChunk.toLowerCase())) { - shouldInclude = false; - } - }); - return shouldInclude; - }); - resolveSource(contentTypes); - }); - }, - }, - ]; - inquirer - .prompt(_chooseContentType) - .then(({ chosenContentTypes }) => { - if (chosenContentTypes.length === 0) { - reject('Please select atleast one content type.'); - } - resolve(chosenContentTypes); - }) - .catch(reject); - }); -} - -function getContentTypes(stackAPIClient, skip) { - return new Promise((resolve, reject) => { - let result = {}; - stackAPIClient - .contentType() - .query({ skip: skip * 100, include_branch: true }) - .find() - .then((contentTypes) => { - contentTypes.items.forEach((contentType) => { - result[contentType.title] = contentType.uid; - }); - resolve(result); - }) - .catch((error) => { - reject(error); - }); - }); -} - -function chooseLanguage(stackAPIClient) { - return new Promise(async (resolve, reject) => { - let languages = await getLanguages(stackAPIClient); - let languagesList = Object.keys(languages); - languagesList.push(config.cancelString); - - let _chooseLanguage = [ - { - type: 'list', - message: 'Choose Language', - choices: languagesList, - name: 'chosenLanguage', - }, - ]; - - inquirer - .prompt(_chooseLanguage) - .then(({ chosenLanguage }) => { - if (chosenLanguage === config.cancelString) exitProgram(); - resolve({ name: chosenLanguage, code: languages[chosenLanguage] }); - }) - .catch(reject); - }); -} - -function getLanguages(stackAPIClient) { - return new Promise((resolve, reject) => { - let result = {}; - stackAPIClient - .locale() - .query() - .find() - .then((languages) => { - languages.items.forEach((language) => { - result[language.name] = language.code; - }); - resolve(result); - }) - .catch((error) => reject(error)); - }); -} - -function getEntries(stackAPIClient, contentType, language, skip, limit) { - return new Promise((resolve, reject) => { - stackAPIClient - .contentType(contentType) - .entry() - .query({ - include_publish_details: true, - locale: language, - skip: skip * 100, - limit: limit, - include_workflow: true, - }) - .find() - .then((entries) => resolve(entries)) - .catch((error) => reject(error)); - }); -} - -function getEntriesCount(stackAPIClient, contentType, language) { - return new Promise((resolve, reject) => { - stackAPIClient - .contentType(contentType) - .entry() - .query({ include_publish_details: true, locale: language }) - .count() - .then((entriesData) => resolve(entriesData.entries)) - .catch((error) => reject(formatError(error))); - }); -} - -function getEnvironments(stackAPIClient) { - let result = {}; - return stackAPIClient - .environment() - .query() - .find() - .then((environments) => { - environments.items.forEach((env) => { - result[env['uid']] = env['name']; - }); - return result; - }); -} - -function getContentTypeCount(stackAPIClient) { - return new Promise((resolve, reject) => { - stackAPIClient - .contentType() - .query() - .count() - .then((contentTypes) => resolve(contentTypes.content_types)) - .catch((error) => reject(error)); - }); -} - -function exitProgram() { - debug('Exiting...'); - // eslint-disable-next-line no-undef - process.exit(); -} - -function sanitizeData(flatData) { - // sanitize against CSV Injections - const CSVRegex = /^[\\+\\=@\\-]/; - for (key in flatData) { - if (typeof flatData[key] === 'string' && flatData[key].match(CSVRegex)) { - flatData[key] = flatData[key].replace(/\"/g, "\"\""); - flatData[key] = `"'${flatData[key]}"`; - } else if (typeof flatData[key] === 'object') { - // convert any objects or arrays to string - // to store this data correctly in csv - flatData[key] = JSON.stringify(flatData[key]); - } - } - return flatData; -} - -function cleanEntries(entries, language, environments, contentTypeUid) { - const filteredEntries = entries.filter((entry) => { - return entry['locale'] === language; - }); - return filteredEntries.map((entry) => { - let workflow = ''; - const envArr = []; - if (entry?.publish_details?.length) { - entry.publish_details.forEach((env) => { - envArr.push(JSON.stringify([environments[env['environment']], env['locale'], env['time']])); - }); - } - - delete entry.publish_details; - delete entry.setWorkflowStage; - if ('_workflow' in entry) { - if (entry._workflow?.name) { - workflow = entry['_workflow']['name']; - delete entry['_workflow']; - } - } - entry = flatten(entry); - entry = sanitizeData(entry); - entry['publish_details'] = envArr; - entry['_workflow'] = workflow; - entry['ACL'] = JSON.stringify({}); // setting ACL to empty obj - entry['content_type_uid'] = contentTypeUid; // content_type_uid is being returned as 'uid' from the sdk for some reason - - // entry['url'] might also be wrong - delete entry.stackHeaders; - delete entry.update; - delete entry.delete; - delete entry.fetch; - delete entry.publish; - delete entry.unpublish; - delete entry.import; - delete entry.publishRequest; - return entry; - }); -} - -function getDateTime() { - let date = new Date(); - let dateTime = date.toLocaleString().split(','); - dateTime[0] = dateTime[0].split('/').join('-'); - dateTime[1] = dateTime[1].trim(); // trim the space before time - dateTime[1] = dateTime[1].split(' ').join(''); - return dateTime.join('_'); -} - -function write(command, entries, fileName, message, delimiter, headers) { - // eslint-disable-next-line no-undef - if (process.cwd().split(delimeter).pop() !== 'data' && !fs.existsSync(directory)) { - mkdirp.sync(directory); - } - // eslint-disable-next-line no-undef - if (process.cwd().split(delimeter).pop() !== 'data') { - // eslint-disable-next-line no-undef - process.chdir(directory); - } - // eslint-disable-next-line no-undef - cliux.print(`Writing ${message} to file: "${process.cwd()}${delimeter}${fileName}"`); - if (headers?.length) fastcsv.writeToPath(fileName, entries, { headers, delimiter }); - else fastcsv.writeToPath(fileName, entries, { headers: true, delimiter }); -} - -function startupQuestions() { - return new Promise((resolve, reject) => { - let actions = [ - { - type: 'list', - name: 'action', - message: 'Choose Action', - choices: [config.exportEntries, config.exportUsers, config.exportTeams, config.exportTaxonomies, 'Exit'], - }, - ]; - inquirer - .prompt(actions) - .then((answers) => { - if (answers.action === 'Exit') exitProgram(); - resolve(answers.action); - }) - .catch(reject); - }); -} - -function chooseFallbackOptions(stackAPIClient) { - return new Promise(async (resolve, reject) => { - try { - const questions = [ - { - type: 'confirm', - name: 'includeFallback', - message: 'Include fallback locale data when exporting taxonomies?', - default: false, - }, - ]; - - const { includeFallback } = await inquirer.prompt(questions); - - let fallbackLocale = null; - - if (includeFallback) { - // Get available languages for fallback locale selection - const languages = await getLanguages(stackAPIClient); - const languagesList = Object.keys(languages); - - const fallbackQuestion = [ - { - type: 'list', - name: 'selectedFallbackLocale', - message: 'Choose fallback locale', - choices: languagesList, - }, - ]; - - const { selectedFallbackLocale } = await inquirer.prompt(fallbackQuestion); - fallbackLocale = languages[selectedFallbackLocale]; - } - - resolve({ - includeFallback, - fallbackLocale, - }); - } catch (error) { - reject(error); - } - }); -} - -function getOrgUsers(managementAPIClient, orgUid) { - return new Promise((resolve, reject) => { - managementAPIClient - .getUser({ include_orgs_roles: true }) - .then(async (response) => { - let organization = response.organizations.filter((org) => org.uid === orgUid).pop(); - if (!organization) return reject(new Error('Org UID not found.')); - if (organization.is_owner === true) { - return managementAPIClient - .organization(organization.uid) - .getInvitations() - .then((data) => { - resolve({ items: data.items }); - }) - .catch(reject); - } - if (!organization.getInvitations && !find(organization.org_roles, 'admin')) { - return reject(new Error(config.adminError)); - } - try { - const users = await getUsers(managementAPIClient, organization, { skip: 0, page: 1, limit: 100 }); - return resolve({ items: users }); - } catch (error) { - return reject(error); - } - }) - .catch((error) => reject(error)); - }); -} - -async function getUsers(managementAPIClient, organization, params, result = []) { - try { - const users = await managementAPIClient.organization(organization.uid).getInvitations(params); - if (!users.items || (users.items && !users.items.length)) { - return result; - } else { - result = result.concat(users.items); - params.skip = params.page * params.limit; - params.page++; - await wait(200); - return getUsers(managementAPIClient, organization, params, result); - } - } catch (error) {} -} - -function getMappedUsers(users) { - let mappedUsers = {}; - users.items.forEach((user) => { - mappedUsers[user.user_uid] = user.email; - }); - mappedUsers['System'] = 'System'; - return mappedUsers; -} - -function getMappedRoles(roles) { - let mappedRoles = {}; - roles.items.forEach((role) => { - mappedRoles[role.uid] = role.name; - }); - return mappedRoles; -} - -function getOrgRoles(managementAPIClient, orgUid, ecsv) { - return new Promise((resolve, reject) => { - managementAPIClient - .getUser({ include_orgs_roles: true }) - .then((response) => { - let organization = response.organizations.filter((org) => org.uid === orgUid).pop(); - if (organization.is_owner === true) { - return managementAPIClient - .organization(organization.uid) - .roles() - .then((roles) => { - resolve({ items: roles.items }); - }) - .catch(reject); - } - if (!organization.roles && !find(organization.org_roles, 'admin')) { - return reject(new Error(config.adminError)); - } - - managementAPIClient - .organization(organization.uid) - .roles() - .then((roles) => { - resolve({ items: roles.items }); - }) - .catch(reject); - }) - .catch((error) => reject(error)); - }); -} - -function determineUserOrgRole(user, roles) { - let roleName = 'No Role'; - let roleUid = user.org_roles || []; - if (roleUid.length > 0) { - roleUid = roleUid.shift(); - roleName = roles[roleUid]; - } - if (user.is_owner) { - roleName = 'Owner'; - } - return roleName; -} - -function cleanOrgUsers(orgUsers, mappedUsers, mappedRoles) { - const userList = []; - orgUsers.items.forEach((user) => { - let invitedBy; - let formattedUser = {}; - try { - invitedBy = mappedUsers[user['invited_by']]; - } catch (error) { - invitedBy = 'System'; - } - formattedUser['Email'] = user['email']; - formattedUser['User UID'] = user['user_uid']; - formattedUser['Organization Role'] = determineUserOrgRole(user, mappedRoles); - formattedUser['Status'] = user['status']; - formattedUser['Invited By'] = invitedBy; - formattedUser['Created Time'] = getFormattedDate(user['created_at']); - formattedUser['Updated Time'] = getFormattedDate(user['updated_at']); - userList.push(formattedUser); - }); - return userList; -} - -function kebabize(str) { - return str - .split(' ') - .map((word) => word.toLowerCase()) - .join('-'); -} - -function getFormattedDate(date) { - if (!(date instanceof Date)) { - date = new Date(date); - } - const year = date.getFullYear(); - const month = (1 + date.getMonth()).toString().padStart(2, '0'); - const day = date.getDate().toString().padStart(2, '0'); - return month + '/' + day + '/' + year; -} - -// https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects -function flatten(data) { - let result = {}; - function recurse(cur, prop) { - if (Object(cur) !== cur) { - result[prop] = cur; - } else if (Array.isArray(cur)) { - let i, l; - for (i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']'); - if (l == 0) result[prop] = []; - } else { - let isEmpty = true; - for (let p in cur) { - isEmpty = false; - recurse(cur[p], prop ? prop + '.' + p : p); - } - if (isEmpty && prop) result[prop] = {}; - } - } - recurse(data, ''); - return result; -} - -function formatError(error) { - try { - if (typeof error === 'string') { - error = JSON.parse(error); - } else { - error = JSON.parse(error.message); - } - } catch (e) {} - let message = error.errorMessage || error.error_message || error.message || error; - if (error.errors && Object.keys(error.errors).length > 0) { - Object.keys(error.errors).forEach((e) => { - let entity = e; - switch (e) { - case 'authorization': - entity = 'Management Token'; - break; - case 'api_key': - entity = 'Stack API key'; - break; - case 'uid': - entity = 'Content Type'; - break; - case 'access_token': - entity = 'Delivery Token'; - break; - } - message += ' ' + [entity, error.errors[e]].join(' '); - }); - } - return message; -} - -function wait(time) { - return new Promise((res) => { - setTimeout(res, time); - }); -} - -function handleErrorMsg(err) { - cliux.print(`Error: ${(err?.errorMessage || err?.message) ?? messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { - color: 'red', - }); - process.exit(1); -} - -/** - * This function does the sdk calls to get all the teams in org - * @param {object} managementAPIClient - * @param {object} org - * @param {object} queryParam - * @returns - */ -async function getAllTeams(managementAPIClient, org, queryParam = {}) { - try { - return await managementAPIClient.organization(org.uid).teams().fetchAll(queryParam); - } catch (error) { - handleErrorMsg(error); - } -} - -/** - * This function is used to handle the pagination and call the sdk - * @param {object} managementAPIClient - * @param {object} org - */ -async function exportOrgTeams(managementAPIClient, org) { - let allTeamsInOrg = []; - let skip = 0; - let limit = config?.limit || 100; - do { - const data = await getAllTeams(managementAPIClient, org, { - skip: skip, - limit: limit, - includeUserDetails: true, - }); - skip += limit; - allTeamsInOrg.push(...data.items); - if (skip >= data?.count) break; - } while (1); - allTeamsInOrg = await cleanTeamsData(allTeamsInOrg, managementAPIClient, org); - return allTeamsInOrg; -} - -/** - * This function will get all the org level roles - * @param {object} managementAPIClient - * @param {object} org - */ -async function getOrgRolesForTeams(managementAPIClient, org) { - let roleMap = {}; // for org level there are two roles only admin and member - - // SDK call to get the role UIDs - await managementAPIClient - .organization(org.uid) - .roles() - .then((roles) => { - roles.items.forEach((item) => { - if (item.name === 'member' || item.name === 'admin') { - roleMap[item.name] = item.uid; - } - }); - }) - .catch((err) => { - handleErrorMsg(err); - }); - return roleMap; -} - -/** - * Removes the unnecessary fields from the objects in the data and assign org level roles to the team based on role uid - * @param {array} data - * @param {object} managementAPIClient - * @param {object} org - */ -async function cleanTeamsData(data, managementAPIClient, org) { - const roleMap = await getOrgRolesForTeams(managementAPIClient, org); - const fieldToBeDeleted = [ - '_id', - 'createdAt', - 'createdBy', - 'updatedAt', - 'updatedBy', - '__v', - 'createdByUserName', - 'updatedByUserName', - 'organizationUid', - 'urlPath', - 'update', - 'delete', - 'fetch', - 'stackRoleMappings', - 'teamUsers', - ]; - if (data?.length) { - return data.map((team) => { - team = omit(team, fieldToBeDeleted); - - team.organizationRole = team.organizationRole === roleMap['member'] ? 'member' : 'admin'; - - if (!team.hasOwnProperty('description')) { - team.description = ''; - } - team.Total_Members = team?.users?.length || 0; - - return team; - }); - } else { - return []; - } -} - -/** - * This function is used to call all the other teams function to export the required files - * @param {object} managementAPIClient - * @param {object} organization - * @param {string} teamUid - * @param {character} delimiter - */ -async function exportTeams(managementAPIClient, organization, teamUid, delimiter) { - cliux.print( - `info: Exporting the ${ - teamUid && organization?.name - ? `team with uid ${teamUid} in Organisation ${organization?.name} ` - : `teams of Organisation ` + organization?.name - }`, - { color: 'blue' }, - ); - const allTeamsData = await exportOrgTeams(managementAPIClient, organization); - if (!allTeamsData?.length) { - cliux.print( - `info: The organization ${organization?.name} does not have any teams associated with it. Please verify and provide the correct organization name.`, - ); - } else { - const modifiedTeam = cloneDeep(allTeamsData); - modifiedTeam.forEach((team) => { - delete team['users']; - delete team['stackRoleMapping']; - }); - const fileName = `${kebabize(organization.name.replace(config.organizationNameRegex, ''))}_teams_export.csv`; - write(this, modifiedTeam, fileName, ' organization Team details', delimiter); - // exporting teams user data or a single team user data - cliux.print( - `info: Exporting the teams user data for ${teamUid ? `team ` + teamUid : `organisation ` + organization?.name}`, - { color: 'blue' }, - ); - await getTeamsDetail(allTeamsData, organization, teamUid, delimiter); - cliux.print( - `info: Exporting the stack role details for ${ - teamUid ? `team ` + teamUid : `organisation ` + organization?.name - }`, - { color: 'blue' }, - ); - // Exporting the stack Role data for all the teams or exporting stack role data for a single team - await exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter); - } -} - -/** - * This function is used to get individual team user details and write to file - * @param {array} allTeamsData - * @param {object} organization - * @param {string} teamUid optional - * @param {character} delimiter - */ -async function getTeamsDetail(allTeamsData, organization, teamUid, delimiter) { - if (!teamUid) { - const userData = await getTeamsUserDetails(allTeamsData); - const fileName = `${kebabize( - organization.name.replace(config.organizationNameRegex, ''), - )}_team_User_Details_export.csv`; - - write(this, userData, fileName, 'Team User details', delimiter); - } else { - const team = allTeamsData.filter((team) => team.uid === teamUid)[0]; - team.users.forEach((user) => { - user['team-name'] = team.name; - user['team-uid'] = team.uid; - delete user['active']; - delete user['orgInvitationStatus']; - }); - const fileName = `${kebabize( - organization.name.replace(config.organizationNameRegex, ''), - )}_team_${teamUid}_User_Details_export.csv`; - - write(this, team.users, fileName, 'Team User details', delimiter); - } -} - -/** - * This will export the role mappings of the team, for which stack the team has which role - * @param {object} managementAPIClient - * @param {array} allTeamsData Data for all the teams in the stack - * @param {string} teamUid for a particular team who's data we want - * @param {character} delimiter - */ -async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter) { - let stackRoleWithTeamData = []; - let flag = false; - const stackNotAdmin = []; - if (teamUid) { - const team = find(allTeamsData, function (teamObject) { - return teamObject?.uid === teamUid; - }); - for (const stack of team?.stackRoleMapping) { - const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid); - stackRoleWithTeamData.push(...roleData); - if (roleData[0]['Stack Name'] === '') { - flag = true; - stackNotAdmin.push(stack.stackApiKey); - } - } - } else { - for (const team of allTeamsData ?? []) { - for (const stack of team?.stackRoleMapping ?? []) { - const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid); - stackRoleWithTeamData.push(...roleData); - if (roleData[0]['Stack Name'] === '') { - flag = true; - stackNotAdmin.push(stack.stackApiKey); - } - } - } - } - if (stackNotAdmin?.length) { - cliux.print( - `warning: Admin access denied to the following stacks using the provided API keys. Please get in touch with the stack owner to request access.`, - { color: 'yellow' }, - ); - cliux.print(`${stackNotAdmin.join(' , ')}`, { color: 'yellow' }); - } - if (flag) { - let export_stack_role = [ - { - type: 'list', - name: 'chooseExport', - message: `Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.`, - choices: ['yes', 'no'], - loop: false, - }, - ]; - try { - const exportStackRole = await inquirer.prompt(export_stack_role); - if (exportStackRole.chooseExport === 'no') { - process.exit(1); - } - } catch (error) { - cliux.print(error, { color: 'red' }); - process.exit(1); - } - } - - const fileName = `${kebabize('Stack_Role_Mapping'.replace(config.organizationNameRegex, ''))}${ - teamUid ? `_${teamUid}` : '' - }.csv`; - - write(this, stackRoleWithTeamData, fileName, 'Team Stack Role details', delimiter); -} - -/** - * Mapping the team stacks with the stack role and returning and array of object - * @param {object} managementAPIClient - * @param {array} stackRoleMapping - * @param {string} teamName - * @param {string} teamUid - */ -async function mapRoleWithTeams(managementAPIClient, stackRoleMapping, teamName, teamUid) { - const roles = await getRoleData(managementAPIClient, stackRoleMapping.stackApiKey); - const stackRole = {}; - roles?.items?.forEach((role) => { - if (!stackRole.hasOwnProperty(role?.uid)) { - stackRole[role?.uid] = role?.name; - stackRole[role?.stack?.api_key] = { name: role?.stack?.name, uid: role?.stack?.uid }; - } - }); - const stackRoleMapOfTeam = stackRoleMapping?.roles.map((role) => { - return { - 'Team Name': teamName, - 'Team Uid': teamUid, - 'Stack Name': stackRole[stackRoleMapping?.stackApiKey]?.name || '', - 'Stack Uid': stackRole[stackRoleMapping?.stackApiKey]?.uid || '', - 'Role Name': stackRole[role] || '', - 'Role Uid': role || '', - }; - }); - return stackRoleMapOfTeam; -} - -/** - * Making sdk call to get all the roles in the given stack - * @param {object} managementAPIClient - * @param {string} stackApiKey - */ -async function getRoleData(managementAPIClient, stackApiKey) { - try { - return await managementAPIClient.stack({ api_key: stackApiKey }).role().fetchAll(); - } catch (error) { - return {}; - } -} - -/** - * Here in the users array we are adding the team-name and team-uid to individual users and returning an array of object of user details only - * @param {array} teams - */ -async function getTeamsUserDetails(teams) { - const allTeamUsers = []; - teams.forEach((team) => { - if (team?.users?.length) { - team.users.forEach((user) => { - user['team-name'] = team.name; - user['team-uid'] = team.uid; - delete user['active']; - delete user['orgInvitationStatus']; - allTeamUsers.push(user); - }); - } - }); - return allTeamUsers; -} - -/** - * fetch all taxonomies in the provided stack - * @param {object} payload - * @param {number} skip - * @param {array} taxonomies - * @returns - */ -async function getAllTaxonomies(payload, skip = 0, taxonomies = []) { - payload['type'] = 'taxonomies'; - const { items, count } = await taxonomySDKHandler(payload, skip); - if (items) { - skip += payload.limit; - taxonomies.push(...items); - if (skip >= count) { - return taxonomies; - } else { - return getAllTaxonomies(payload, skip, taxonomies); - } - } - return taxonomies; -} - -/** - * fetch taxonomy related terms - * @param {object} payload - * @param {number} skip - * @param {number} limit - * @param {array} terms - * @returns - */ -async function getAllTermsOfTaxonomy(payload, skip = 0, terms = []) { - payload['type'] = 'terms'; - const { items, count } = await taxonomySDKHandler(payload, skip); - if (items) { - skip += payload.limit; - terms.push(...items); - if (skip >= count) { - return terms; - } else { - return getAllTermsOfTaxonomy(payload, skip, terms); - } - } - return terms; -} - -/** - * Verify the existence of a taxonomy. Obtain its details if it exists and return - * @param {object} payload - * @param {string} taxonomyUID - * @returns - */ -async function getTaxonomy(payload) { - payload['type'] = 'taxonomy'; - const resp = await taxonomySDKHandler(payload); - return resp; -} - -/** - * taxonomy & term sdk handler - * @async - * @method - * @param payload - * @param skip - * @param limit - * @returns {*} Promise - */ -async function taxonomySDKHandler(payload, skip) { - const { stackAPIClient, taxonomyUID, type, format, locale, branch, include_fallback, fallback_locale } = payload; - - const queryParams = { include_count: true, limit: payload.limit }; - if (skip >= 0) queryParams['skip'] = skip || 0; - - // Add locale and branch parameters if provided - if (locale) queryParams['locale'] = locale; - if (branch) queryParams['branch'] = branch; - if (include_fallback !== undefined) queryParams['include_fallback'] = include_fallback; - if (fallback_locale) queryParams['fallback_locale'] = fallback_locale; - - switch (type) { - case 'taxonomies': - return await stackAPIClient - .taxonomy() - .query(queryParams) - .find() - .then((data) => data) - .catch((err) => handleTaxonomyErrorMsg(err)); - case 'taxonomy': - return await stackAPIClient - .taxonomy(taxonomyUID) - .fetch() - .then((data) => data) - .catch((err) => handleTaxonomyErrorMsg(err)); - case 'terms': - queryParams['depth'] = 0; - return await stackAPIClient - .taxonomy(taxonomyUID) - .terms() - .query(queryParams) - .find() - .then((data) => data) - .catch((err) => handleTaxonomyErrorMsg(err)); - case 'export-taxonomies': - const exportParams = { format }; - if (locale) exportParams.locale = locale; - if (branch) exportParams.branch = branch; - if (include_fallback !== undefined) exportParams.include_fallback = include_fallback; - if (fallback_locale) exportParams.fallback_locale = fallback_locale; - - return await stackAPIClient - .taxonomy(taxonomyUID) - .export(exportParams) - .then((data) => data) - .catch((err) => handleTaxonomyErrorMsg(err)); - default: - handleTaxonomyErrorMsg({ errorMessage: 'Invalid module!' }); - } -} - -/** - * Change taxonomies data in required CSV headers format - * @param {array} taxonomies - * @returns - */ -function formatTaxonomiesData(taxonomies) { - if (taxonomies?.length) { - const formattedTaxonomies = taxonomies.map((taxonomy) => { - return sanitizeData({ - 'Taxonomy UID': taxonomy.uid, - Name: taxonomy.name, - Description: taxonomy.description, - }); - }); - return formattedTaxonomies; - } -} - -/** - * Modify the linked taxonomy data's terms in required CSV headers format - * @param {array} terms - * @param {string} taxonomyUID - * @returns - */ -function formatTermsOfTaxonomyData(terms, taxonomyUID) { - if (terms?.length) { - const formattedTerms = terms.map((term) => { - return sanitizeData({ - 'Taxonomy UID': taxonomyUID, - UID: term.uid, - Name: term.name, - 'Parent UID': term.parent_uid, - Depth: term.depth, - }); - }); - return formattedTerms; - } -} - -function handleTaxonomyErrorMsg(err) { - if (err?.errorMessage || err?.message) { - const errorMsg = err?.errorMessage || err?.errors?.taxonomy || err?.errors?.term || err?.message; - cliux.print(`Error: ${errorMsg}`, { color: 'red' }); - } else { - console.log(err); - cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' }); - } - process.exit(1); -} - -/** - * Generate a CSV file that can be imported for use with the migration script. - * @param {*} payload api request payload - * @param {*} taxonomies taxonomies data - * @returns - */ -async function createImportableCSV(payload, taxonomies) { - let taxonomiesData = []; - let headers = []; - payload['type'] = 'export-taxonomies'; - payload['format'] = 'csv'; - for (const taxonomy of taxonomies) { - if (taxonomy?.uid) { - payload['taxonomyUID'] = taxonomy?.uid; - const data = await taxonomySDKHandler(payload); - const taxonomies = await csvParse(data, headers); - taxonomiesData.push(...taxonomies); - } - } - - return { taxonomiesData, headers }; -} - -/** - * Parse the CSV data and segregate the headers from the actual data. - * @param {*} data taxonomy csv data with headers - * @param {*} headers list of csv headers - * @returns taxonomy data without headers - */ -const csvParse = (data, headers) => { - return new Promise((resolve, reject) => { - const taxonomies = []; - const stream = fastcsv.parseStream(fastcsv.parse()); - stream.write(data); - stream.end(); - stream - .on('data', (data) => { - taxonomies.push(data); - }) - .on('error', (err) => reject(err)) - .on('end', () => { - taxonomies[0]?.forEach((header) => { - if (!headers.includes(header)) headers.push(header); - }); - resolve(taxonomies.splice(1)); - }); - }); -}; - -module.exports = { - chooseOrganization: chooseOrganization, - chooseStack: chooseStack, - chooseBranch: chooseBranch, - chooseContentType: chooseContentType, - chooseLanguage: chooseLanguage, - chooseFallbackOptions: chooseFallbackOptions, - getEntries: getEntries, - getEnvironments: getEnvironments, - cleanEntries: cleanEntries, - write: write, - startupQuestions: startupQuestions, - getDateTime: getDateTime, - getOrgUsers: getOrgUsers, - getOrgRoles: getOrgRoles, - getMappedUsers: getMappedUsers, - getMappedRoles: getMappedRoles, - cleanOrgUsers: cleanOrgUsers, - determineUserOrgRole: determineUserOrgRole, - getOrganizationsWhereUserIsAdmin: getOrganizationsWhereUserIsAdmin, - kebabize: kebabize, - flatten: flatten, - getContentTypeCount: getContentTypeCount, - getContentTypes: getContentTypes, - chooseInMemContentTypes: chooseInMemContentTypes, - getEntriesCount: getEntriesCount, - formatError: formatError, - exportOrgTeams: exportOrgTeams, - exportTeams: exportTeams, - getAllTaxonomies, - getAllTermsOfTaxonomy, - formatTaxonomiesData, - formatTermsOfTaxonomyData, - getTaxonomy, - getStacks, - createImportableCSV, -}; diff --git a/packages/contentstack-export-to-csv/src/utils/api-client.ts b/packages/contentstack-export-to-csv/src/utils/api-client.ts new file mode 100644 index 0000000000..cec010028c --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/api-client.ts @@ -0,0 +1,606 @@ +/** + * API client utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +import find from 'lodash/find'; +import { configHandler } from '@contentstack/cli-utilities'; + +import config from '../config'; +import { messages } from '../messages'; +import { wait, handleErrorMsg, handleTaxonomyErrorMsg } from './error-handler'; +import type { + ManagementClient, + StackClient, + OrgMap, + StackMap, + ContentTypeMap, + LanguageMap, + EnvironmentMap, + OrgUsersResponse, + OrgRolesResponse, + PaginationParams, + OrganizationChoice, + RawTeam, + CleanedTeam, + TeamsResponse, + StackRolesResponse, + TaxonomyPayload, + TaxonomiesResponse, + TermsResponse, + Taxonomy, + Term, + TaxonomyQueryParams, + EntriesResponse, + UserResponse, + ImportableCsvResult, + OrganizationItem, + OrgWithRoles, + StackItem, + ContentTypeItem, + LanguageItem, + EnvironmentItem, +} from '../types'; + +// ============================================================================ +// Organization APIs +// ============================================================================ + +/** + * Get all organizations the user has access to. + */ +export async function getOrganizations(managementAPIClient: ManagementClient): Promise { + try { + return await getOrganizationList(managementAPIClient, { skip: 0, page: 1, limit: 100 }, []); + } catch (error) { + throw error; + } +} + +/** + * Get organization list with pagination. + */ +async function getOrganizationList( + managementAPIClient: ManagementClient, + params: PaginationParams, + result: OrganizationItem[] = [], +): Promise { + let organizations: OrganizationItem & { items?: OrganizationItem[] }; + const configOrgUid = configHandler.get('oauthOrgUid') as string | undefined; + + if (configOrgUid) { + organizations = await managementAPIClient.organization(configOrgUid).fetch() as unknown as OrganizationItem; + result = result.concat([organizations]); + } else { + const response = await managementAPIClient.organization().fetchAll({ limit: 100 }) as unknown as { items: OrganizationItem[] }; + organizations = response as unknown as OrganizationItem & { items?: OrganizationItem[] }; + result = result.concat(response.items); + } + + if (!organizations.items || (organizations.items && organizations.items.length < params.limit)) { + const orgMap: OrgMap = {}; + for (const org of result) { + orgMap[org.name] = org.uid; + } + return orgMap; + } else { + params.skip = params.page * params.limit; + params.page++; + await wait(200); + return getOrganizationList(managementAPIClient, params, result); + } +} + +/** + * Get organizations where user is admin. + */ +export async function getOrganizationsWhereUserIsAdmin(managementAPIClient: ManagementClient): Promise { + try { + const result: OrgMap = {}; + const configOrgUid = configHandler.get('oauthOrgUid') as string | undefined; + + if (configOrgUid) { + const response = await managementAPIClient.organization(configOrgUid).fetch() as unknown as OrganizationItem; + result[response.name] = response.uid; + } else { + const response = await managementAPIClient.getUser({ include_orgs_roles: true }) as unknown as { organizations: OrgWithRoles[] }; + const organizations = response.organizations.filter((org) => { + if (org.org_roles) { + const org_role = org.org_roles.shift(); + return org_role?.admin; + } + return org.is_owner === true; + }); + + organizations.forEach((org) => { + result[org.name] = org.uid; + }); + } + + return result; + } catch (error) { + throw error; + } +} + +/** + * Get organization users. + */ +export function getOrgUsers(managementAPIClient: ManagementClient, orgUid: string): Promise { + return new Promise((resolve, reject) => { + managementAPIClient + .getUser({ include_orgs_roles: true }) + .then(async (response: unknown) => { + const userResponse = response as UserResponse; + const organization = userResponse.organizations.filter((org) => org.uid === orgUid).pop(); + + if (!organization) { + return reject(new Error('Org UID not found.')); + } + + if (organization.is_owner === true) { + return managementAPIClient + .organization(organization.uid) + .getInvitations() + .then((data: unknown) => { + resolve(data as OrgUsersResponse); + }) + .catch(reject); + } + + if (!organization.getInvitations && !find(organization.org_roles, 'admin')) { + return reject(new Error(messages.ERROR_ADMIN_ACCESS_DENIED)); + } + + try { + const users = await getUsers(managementAPIClient, { uid: organization.uid }, { skip: 0, page: 1, limit: 100 }); + return resolve({ items: users || [] }); + } catch (error) { + return reject(error); + } + }) + .catch((error: unknown) => reject(error)); + }); +} + +/** + * Get users with pagination. + */ +async function getUsers( + managementAPIClient: ManagementClient, + organization: { uid: string }, + params: PaginationParams, + result: OrgUsersResponse['items'] = [], +): Promise { + try { + const users = await managementAPIClient.organization(organization.uid).getInvitations(params) as unknown as OrgUsersResponse; + + if (!users.items || (users.items && !users.items.length)) { + return result; + } else { + result = result.concat(users.items); + params.skip = params.page * params.limit; + params.page++; + await wait(200); + return getUsers(managementAPIClient, organization, params, result); + } + } catch { + return result; + } +} + +/** + * Get organization roles. + */ +export function getOrgRoles(managementAPIClient: ManagementClient, orgUid: string): Promise { + return new Promise((resolve, reject) => { + managementAPIClient + .getUser({ include_orgs_roles: true }) + .then((response: unknown) => { + const userResponse = response as UserResponse; + const organization = userResponse.organizations.filter((org) => org.uid === orgUid).pop(); + + if (!organization) { + return reject(new Error('Org UID not found.')); + } + + if (organization.is_owner === true) { + return managementAPIClient + .organization(organization.uid) + .roles() + .then((roles: unknown) => { + resolve(roles as OrgRolesResponse); + }) + .catch(reject); + } + + if (!organization.roles && !find(organization.org_roles, 'admin')) { + return reject(new Error(messages.ERROR_ADMIN_ACCESS_DENIED)); + } + + managementAPIClient + .organization(organization.uid) + .roles() + .then((roles: unknown) => { + resolve(roles as OrgRolesResponse); + }) + .catch(reject); + }) + .catch((error: unknown) => reject(error)); + }); +} + +// ============================================================================ +// Stack APIs +// ============================================================================ + +/** + * Get all stacks in an organization. + */ +export function getStacks(managementAPIClient: ManagementClient, orgUid: string): Promise { + return new Promise((resolve, reject) => { + const result: StackMap = {}; + + managementAPIClient + .stack({ organization_uid: orgUid }) + .query({ query: {} }) + .find() + .then((stacks: unknown) => { + const stacksResponse = stacks as { items: StackItem[] }; + stacksResponse.items.forEach((stack) => { + result[stack.name] = stack.api_key; + }); + resolve(result); + }) + .catch((error: unknown) => { + reject(error); + }); + }); +} + +// ============================================================================ +// Content Type APIs +// ============================================================================ + +/** + * Get content type count. + */ +export function getContentTypeCount(stackAPIClient: StackClient): Promise { + return new Promise((resolve, reject) => { + stackAPIClient + .contentType() + .query() + .count() + .then((contentTypes: unknown) => { + const response = contentTypes as { content_types: number }; + resolve(response.content_types); + }) + .catch((error: unknown) => reject(error)); + }); +} + +/** + * Get content types with pagination. + */ +export function getContentTypes(stackAPIClient: StackClient, skip: number): Promise { + return new Promise((resolve, reject) => { + const result: ContentTypeMap = {}; + + stackAPIClient + .contentType() + .query({ skip: skip * 100, include_branch: true }) + .find() + .then((contentTypes: unknown) => { + const response = contentTypes as { items: ContentTypeItem[] }; + response.items.forEach((contentType) => { + if (contentType.title) { + result[contentType.title] = contentType.uid; + } + }); + resolve(result); + }) + .catch((error: unknown) => { + reject(error); + }); + }); +} + +// ============================================================================ +// Language/Locale APIs +// ============================================================================ + +/** + * Get all languages/locales for a stack. + */ +export function getLanguages(stackAPIClient: StackClient): Promise { + return new Promise((resolve, reject) => { + const result: LanguageMap = {}; + + stackAPIClient + .locale() + .query() + .find() + .then((languages: unknown) => { + const response = languages as { items: LanguageItem[] }; + response.items.forEach((language) => { + result[language.name] = language.code; + }); + resolve(result); + }) + .catch((error: unknown) => reject(error)); + }); +} + +// ============================================================================ +// Entry APIs +// ============================================================================ + +/** + * Get entry count for a content type. + */ +export function getEntriesCount(stackAPIClient: StackClient, contentType: string, language: string): Promise { + return new Promise((resolve, reject) => { + stackAPIClient + .contentType(contentType) + .entry() + .query({ include_publish_details: true, locale: language }) + .count() + .then((entriesData: unknown) => { + const response = entriesData as { entries: number }; + resolve(response.entries); + }) + .catch((error: unknown) => { + const { formatError } = require('./error-handler'); + reject(formatError(error)); + }); + }); +} + +/** + * Get entries with pagination. + */ +export function getEntries( + stackAPIClient: StackClient, + contentType: string, + language: string, + skip: number, + limit: number, +): Promise { + return new Promise((resolve, reject) => { + stackAPIClient + .contentType(contentType) + .entry() + .query({ + include_publish_details: true, + locale: language, + skip: skip * 100, + limit: limit, + include_workflow: true, + }) + .find() + .then((entries: unknown) => resolve(entries as EntriesResponse)) + .catch((error: unknown) => reject(error)); + }); +} + +// ============================================================================ +// Environment APIs +// ============================================================================ + +/** + * Get all environments for a stack. + */ +export function getEnvironments(stackAPIClient: StackClient): Promise { + const result: EnvironmentMap = {}; + + return stackAPIClient + .environment() + .query() + .find() + .then((environments: unknown) => { + const response = environments as { items: EnvironmentItem[] }; + response.items.forEach((env) => { + result[env.uid] = env.name; + }); + return result; + }); +} + +// ============================================================================ +// Team APIs +// ============================================================================ + +/** + * Get all teams in an organization. + */ +export async function getAllTeams( + managementAPIClient: ManagementClient, + org: OrganizationChoice, + queryParam: Record = {}, +): Promise { + try { + return await managementAPIClient.organization(org.uid).teams().fetchAll(queryParam) as unknown as TeamsResponse; + } catch (error) { + handleErrorMsg(error); + } +} + +/** + * Export all organization teams with pagination. + */ +export async function exportOrgTeams( + managementAPIClient: ManagementClient, + org: OrganizationChoice, +): Promise { + const { cleanTeamsData } = await import('./data-transform'); + + let allTeamsInOrg: RawTeam[] = []; + let skip = 0; + const limit = config?.limit || 100; + + do { + const data = await getAllTeams(managementAPIClient, org, { + skip: skip, + limit: limit, + includeUserDetails: true, + }); + skip += limit; + allTeamsInOrg.push(...data.items); + if (skip >= (data.count || 0)) break; + } while (true); + + const cleanedTeams = await cleanTeamsData(allTeamsInOrg, managementAPIClient, org); + return cleanedTeams; +} + +/** + * Get role data for a stack. + */ +export async function getRoleData(managementAPIClient: ManagementClient, stackApiKey: string): Promise { + try { + return await managementAPIClient.stack({ api_key: stackApiKey }).role().fetchAll() as unknown as StackRolesResponse; + } catch { + return { items: [] }; + } +} + +// ============================================================================ +// Taxonomy APIs +// ============================================================================ + +/** + * Taxonomy & term SDK handler. + */ +export async function taxonomySDKHandler(payload: TaxonomyPayload, skip?: number): Promise { + const { stackAPIClient, taxonomyUID, type, format, locale, branch, include_fallback, fallback_locale } = payload; + + const queryParams: TaxonomyQueryParams = { include_count: true, limit: payload.limit }; + if (skip !== undefined && skip >= 0) queryParams.skip = skip || 0; + + // Add locale and branch parameters if provided + if (locale) queryParams.locale = locale; + if (branch) queryParams.branch = branch; + if (include_fallback !== undefined) queryParams.include_fallback = include_fallback; + if (fallback_locale) queryParams.fallback_locale = fallback_locale; + + switch (type) { + case 'taxonomies': + return await stackAPIClient + .taxonomy() + .query(queryParams) + .find() + .then((data: unknown) => data as TaxonomiesResponse) + .catch((err: unknown) => handleTaxonomyErrorMsg(err)); + + case 'taxonomy': + return await stackAPIClient + .taxonomy(taxonomyUID!) + .fetch() + .then((data: unknown) => data as Taxonomy) + .catch((err: unknown) => handleTaxonomyErrorMsg(err)); + + case 'terms': + queryParams.depth = 0; + return await stackAPIClient + .taxonomy(taxonomyUID!) + .terms() + .query(queryParams) + .find() + .then((data: unknown) => data as TermsResponse) + .catch((err: unknown) => handleTaxonomyErrorMsg(err)); + + case 'export-taxonomies': + const exportParams: Record = { format }; + if (locale) exportParams.locale = locale; + if (branch) exportParams.branch = branch; + if (include_fallback !== undefined) exportParams.include_fallback = include_fallback; + if (fallback_locale) exportParams.fallback_locale = fallback_locale; + + return await stackAPIClient + .taxonomy(taxonomyUID!) + .export(exportParams) + .then((data: unknown) => data as string) + .catch((err: unknown) => handleTaxonomyErrorMsg(err)); + + default: + handleTaxonomyErrorMsg({ errorMessage: 'Invalid module!' }); + } +} + +/** + * Get all taxonomies in a stack. + */ +export async function getAllTaxonomies(payload: TaxonomyPayload, skip = 0, taxonomies: Taxonomy[] = []): Promise { + payload.type = 'taxonomies'; + const response = await taxonomySDKHandler(payload, skip) as TaxonomiesResponse; + + if (response.items) { + skip += payload.limit; + taxonomies.push(...response.items); + + if (skip >= response.count) { + return taxonomies; + } else { + return getAllTaxonomies(payload, skip, taxonomies); + } + } + + return taxonomies; +} + +/** + * Get all terms for a taxonomy. + */ +export async function getAllTermsOfTaxonomy(payload: TaxonomyPayload, skip = 0, terms: Term[] = []): Promise { + payload.type = 'terms'; + const response = await taxonomySDKHandler(payload, skip) as TermsResponse; + + if (response.items) { + skip += payload.limit; + terms.push(...response.items); + + if (skip >= response.count) { + return terms; + } else { + return getAllTermsOfTaxonomy(payload, skip, terms); + } + } + + return terms; +} + +/** + * Get a single taxonomy by UID. + */ +export async function getTaxonomy(payload: TaxonomyPayload): Promise { + payload.type = 'taxonomy'; + const resp = await taxonomySDKHandler(payload); + return resp as Taxonomy; +} + +/** + * Generate importable CSV data for taxonomies. + */ +export async function createImportableCSV( + payload: TaxonomyPayload, + taxonomies: Taxonomy[], +): Promise { + const { csvParse } = await import('./csv-writer'); + + const taxonomiesData: string[][] = []; + const headers: string[] = []; + + payload.type = 'export-taxonomies'; + payload.format = 'csv'; + + for (const taxonomy of taxonomies) { + if (taxonomy?.uid) { + payload.taxonomyUID = taxonomy.uid; + const data = await taxonomySDKHandler(payload) as string; + const parsedTaxonomies = await csvParse(data, headers); + taxonomiesData.push(...parsedTaxonomies); + } + } + + return { taxonomiesData, headers }; +} diff --git a/packages/contentstack-export-to-csv/src/utils/csv-writer.ts b/packages/contentstack-export-to-csv/src/utils/csv-writer.ts new file mode 100644 index 0000000000..5d835a66b5 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/csv-writer.ts @@ -0,0 +1,82 @@ +/** + * CSV writing utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +import * as os from 'os'; +import * as fs from 'fs'; +import * as fastcsv from 'fast-csv'; +import { cliux } from '@contentstack/cli-utilities'; + +import type { CsvRow } from '../types'; + +const directory = './data'; +const delimiter = os.platform() === 'win32' ? '\\' : '/'; + +/** + * Write data to a CSV file. + * + * @param _command - Command instance (not used but kept for parity) + * @param entries - Array of objects or string arrays to write + * @param fileName - Name of the output file + * @param message - Message type for logging (e.g., 'entries', 'organization details') + * @param csvDelimiter - CSV delimiter character (default: ',') + * @param headers - Optional custom headers + */ +export function write( + _command: unknown, + entries: CsvRow[], + fileName: string, + message: string, + csvDelimiter?: string, + headers?: string[], +): void { + // Create data directory if it doesn't exist + if (process.cwd().split(delimiter).pop() !== 'data' && !fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + + // Change to data directory if not already there + if (process.cwd().split(delimiter).pop() !== 'data') { + process.chdir(directory); + } + + cliux.print(`Writing ${message} to file: "${process.cwd()}${delimiter}${fileName}"`); + + const writeOptions: fastcsv.FormatterOptionsArgs = { + headers: headers?.length ? headers : true, + delimiter: csvDelimiter || ',', + }; + + fastcsv.writeToPath(fileName, entries, writeOptions); +} + +/** + * Parse CSV data and extract headers. + * + * @param data - Raw CSV data string + * @param headers - Array to populate with headers (mutated) + * @returns Parsed data without header row + */ +export function csvParse(data: string, headers: string[]): Promise { + return new Promise((resolve, reject) => { + const taxonomies: string[][] = []; + + fastcsv + .parseString(data, { headers: false }) + .on('data', (rowData: string[]) => { + taxonomies.push(rowData); + }) + .on('error', (err: Error) => reject(err)) + .on('end', () => { + // Extract headers from first row + taxonomies[0]?.forEach((header: string) => { + if (!headers.includes(header)) { + headers.push(header); + } + }); + // Return data without header row + resolve(taxonomies.splice(1)); + }); + }); +} diff --git a/packages/contentstack-export-to-csv/src/utils/data-transform.ts b/packages/contentstack-export-to-csv/src/utils/data-transform.ts new file mode 100644 index 0000000000..e6ec2f62c2 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/data-transform.ts @@ -0,0 +1,415 @@ +/** + * Data transformation utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +import omit from 'lodash/omit'; +import type { + ManagementClient, + RawEntry, + FlattenedEntryRow, + OrgUser, + OrgUsersResponse, + OrgRolesResponse, + UserMap, + RoleMap, + OrgUserCsvRow, + RawTeam, + CleanedTeam, + TeamUser, + Taxonomy, + TaxonomyCsvRow, + Term, + TermCsvRow, + EnvironmentMap, + OrganizationChoice, + OrgRoleMap, +} from '../types'; + +// ============================================================================ +// Core Transformation Functions +// ============================================================================ + +/** + * Flatten a nested object into a single-level object. + * Arrays are flattened with bracket notation (e.g., "field[0]"). + * + * @see https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects + */ +export function flatten(data: Record): Record { + const result: Record = {}; + + function recurse(cur: unknown, prop: string): void { + if (Object(cur) !== cur) { + result[prop] = cur; + } else if (Array.isArray(cur)) { + const l = cur.length; + for (let i = 0; i < l; i++) { + recurse(cur[i], prop + '[' + i + ']'); + } + if (l === 0) { + result[prop] = []; + } + } else { + let isEmpty = true; + for (const p in cur as Record) { + isEmpty = false; + recurse((cur as Record)[p], prop ? prop + '.' + p : p); + } + if (isEmpty && prop) { + result[prop] = {}; + } + } + } + + recurse(data, ''); + return result; +} + +/** + * Sanitize data against CSV injection attacks. + * Prefixes potentially dangerous characters with a quote. + * Also converts objects/arrays to JSON strings. + */ +export function sanitizeData>(flatData: T): T { + // sanitize against CSV Injections + const CSVRegex = /^[\\+\\=@\\-]/; + + for (const key in flatData) { + const value = flatData[key]; + if (typeof value === 'string' && value.match(CSVRegex)) { + (flatData as Record)[key] = `"'${value.replace(/"/g, '""')}"`; + } else if (typeof value === 'object' && value !== null) { + // convert any objects or arrays to string + // to store this data correctly in csv + (flatData as Record)[key] = JSON.stringify(value); + } + } + return flatData; +} + +// ============================================================================ +// Entry Transformation Functions +// ============================================================================ + +/** + * Clean and format entries for CSV export. + */ +export function cleanEntries( + entries: RawEntry[], + language: string, + environments: EnvironmentMap, + contentTypeUid: string, +): FlattenedEntryRow[] { + const filteredEntries = entries.filter((entry) => { + return entry.locale === language; + }); + + return filteredEntries.map((entry) => { + let workflow = ''; + const envArr: string[] = []; + + if (entry.publish_details?.length) { + entry.publish_details.forEach((env) => { + envArr.push(JSON.stringify([environments[env.environment], env.locale, env.time])); + }); + } + + // Create a mutable copy for transformation + const mutableEntry: Record = { ...entry }; + + delete mutableEntry.publish_details; + delete mutableEntry.setWorkflowStage; + + if ('_workflow' in mutableEntry) { + const workflowData = mutableEntry._workflow as { name?: string } | undefined; + if (workflowData?.name) { + workflow = workflowData.name; + delete mutableEntry._workflow; + } + } + + let flatEntry = flatten(mutableEntry); + flatEntry = sanitizeData(flatEntry as Record); + flatEntry.publish_details = envArr; + flatEntry._workflow = workflow; + flatEntry.ACL = JSON.stringify({}); // setting ACL to empty obj + flatEntry.content_type_uid = contentTypeUid; // content_type_uid is being returned as 'uid' from the sdk for some reason + + // entry['url'] might also be wrong + delete flatEntry.stackHeaders; + delete flatEntry.update; + delete flatEntry.delete; + delete flatEntry.fetch; + delete flatEntry.publish; + delete flatEntry.unpublish; + delete flatEntry.import; + delete flatEntry.publishRequest; + + return flatEntry as unknown as FlattenedEntryRow; + }); +} + +// ============================================================================ +// Organization User Transformation Functions +// ============================================================================ + +/** + * Map user UIDs to emails. + */ +export function getMappedUsers(users: OrgUsersResponse): UserMap { + const mappedUsers: UserMap = {}; + users.items.forEach((user) => { + mappedUsers[user.user_uid] = user.email; + }); + mappedUsers['System'] = 'System'; + return mappedUsers; +} + +/** + * Map role UIDs to names. + */ +export function getMappedRoles(roles: OrgRolesResponse): RoleMap { + const mappedRoles: RoleMap = {}; + roles.items.forEach((role) => { + mappedRoles[role.uid] = role.name; + }); + return mappedRoles; +} + +/** + * Determine a user's organization role. + */ +export function determineUserOrgRole(user: OrgUser, roles: RoleMap): string { + let roleName = 'No Role'; + const roleUids = user.org_roles ? [...user.org_roles] : []; + + if (roleUids.length > 0) { + const roleUid = roleUids.shift()!; + roleName = roles[roleUid]; + } + + if (user.is_owner) { + roleName = 'Owner'; + } + + return roleName; +} + +/** + * Clean and format organization users for CSV export. + */ +export function cleanOrgUsers( + orgUsers: OrgUsersResponse, + mappedUsers: UserMap, + mappedRoles: RoleMap, +): OrgUserCsvRow[] { + const userList: OrgUserCsvRow[] = []; + + orgUsers.items.forEach((user) => { + let invitedBy: string; + + try { + invitedBy = mappedUsers[user.invited_by] || 'System'; + } catch { + invitedBy = 'System'; + } + + const formattedUser: OrgUserCsvRow = { + 'Email': user.email, + 'User UID': user.user_uid, + 'Organization Role': determineUserOrgRole(user, mappedRoles), + 'Status': user.status, + 'Invited By': invitedBy, + 'Created Time': getFormattedDate(user.created_at), + 'Updated Time': getFormattedDate(user.updated_at), + }; + + userList.push(formattedUser); + }); + + return userList; +} + +// ============================================================================ +// Team Transformation Functions +// ============================================================================ + +/** + * Removes unnecessary fields from team data and assigns org level roles. + */ +export async function cleanTeamsData( + data: RawTeam[], + managementAPIClient: ManagementClient, + org: OrganizationChoice, +): Promise { + const roleMap = await getOrgRolesForTeams(managementAPIClient, org); + + const fieldToBeDeleted: (keyof RawTeam)[] = [ + '_id', + 'createdAt', + 'createdBy', + 'updatedAt', + 'updatedBy', + '__v', + 'createdByUserName', + 'updatedByUserName', + 'organizationUid', + 'urlPath', + 'update', + 'delete', + 'fetch', + 'stackRoleMappings', + 'teamUsers', + ]; + + if (data?.length) { + return data.map((team) => { + const cleanedTeam = omit(team, fieldToBeDeleted) as Partial & { organizationRole: string }; + + cleanedTeam.organizationRole = team.organizationRole === roleMap.member ? 'member' : 'admin'; + + if (!Object.prototype.hasOwnProperty.call(cleanedTeam, 'description')) { + cleanedTeam.description = ''; + } + cleanedTeam.Total_Members = team.users?.length || 0; + + return cleanedTeam as CleanedTeam; + }); + } else { + return []; + } +} + +/** + * Get all org level roles for teams. + */ +async function getOrgRolesForTeams( + managementAPIClient: ManagementClient, + org: OrganizationChoice, +): Promise { + const roleMap: OrgRoleMap = {}; // for org level there are two roles only admin and member + + // SDK call to get the role UIDs + try { + const roles = await managementAPIClient.organization(org.uid).roles() as unknown as { items: Array<{ name: string; uid: string }> }; + roles.items.forEach((item) => { + if (item.name === 'member' || item.name === 'admin') { + roleMap[item.name] = item.uid; + } + }); + } catch (err) { + // Import handleErrorMsg here to avoid circular dependency + const { handleErrorMsg } = await import('./error-handler'); + handleErrorMsg(err); + } + + return roleMap; +} + +/** + * Get team user details from all teams. + */ +export function getTeamsUserDetails(teams: CleanedTeam[]): TeamUser[] { + const allTeamUsers: TeamUser[] = []; + + teams.forEach((team) => { + if (team.users?.length) { + team.users.forEach((user) => { + const userWithTeam: TeamUser = { + ...user, + 'team-name': team.name, + 'team-uid': team.uid, + }; + delete userWithTeam.active; + delete userWithTeam.orgInvitationStatus; + allTeamUsers.push(userWithTeam); + }); + } + }); + + return allTeamUsers; +} + +// ============================================================================ +// Taxonomy Transformation Functions +// ============================================================================ + +/** + * Change taxonomies data in required CSV headers format. + */ +export function formatTaxonomiesData(taxonomies: Taxonomy[]): TaxonomyCsvRow[] | undefined { + if (taxonomies?.length) { + const formattedTaxonomies = taxonomies.map((taxonomy) => { + return sanitizeData({ + 'Taxonomy UID': taxonomy.uid, + Name: taxonomy.name, + Description: taxonomy.description || '', + }); + }); + return formattedTaxonomies; + } +} + +/** + * Modify the linked taxonomy data's terms in required CSV headers format. + */ +export function formatTermsOfTaxonomyData(terms: Term[], taxonomyUID: string): TermCsvRow[] | undefined { + if (terms?.length) { + const formattedTerms = terms.map((term) => { + return sanitizeData({ + 'Taxonomy UID': taxonomyUID, + UID: term.uid, + Name: term.name, + 'Parent UID': term.parent_uid, + Depth: term.depth, + }) as TermCsvRow; + }); + return formattedTerms; + } +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * Convert string to kebab-case. + */ +export function kebabize(str: string): string { + return str + .split(' ') + .map((word) => word.toLowerCase()) + .join('-'); +} + +/** + * Get formatted date string (MM/DD/YYYY). + */ +export function getFormattedDate(date: Date | string): string { + let dateObj: Date; + + if (!(date instanceof Date)) { + dateObj = new Date(date); + } else { + dateObj = date; + } + + const year = dateObj.getFullYear(); + const month = (1 + dateObj.getMonth()).toString().padStart(2, '0'); + const day = dateObj.getDate().toString().padStart(2, '0'); + + return month + '/' + day + '/' + year; +} + +/** + * Get date-time string for file naming. + */ +export function getDateTime(): string { + const date = new Date(); + const dateTime = date.toLocaleString().split(','); + dateTime[0] = dateTime[0].split('/').join('-'); + dateTime[1] = dateTime[1].trim(); // trim the space before time + dateTime[1] = dateTime[1].split(' ').join(''); + return dateTime.join('_'); +} diff --git a/packages/contentstack-export-to-csv/src/utils/error-handler.ts b/packages/contentstack-export-to-csv/src/utils/error-handler.ts new file mode 100644 index 0000000000..2add97dd63 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/error-handler.ts @@ -0,0 +1,114 @@ +/** + * Error handling utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +import { cliux, messageHandler, log } from '@contentstack/cli-utilities'; +import type { ApiError, ErrorWithMessage, TaxonomyError } from '../types'; + +/** + * Format an error into a user-friendly message. + * + * Handles various error formats from the Contentstack API: + * - String errors + * - Error objects with `message` property + * - Error objects with `errorMessage` property + * - Error objects with `errors` object containing field-specific errors + */ +export function formatError(error: unknown): string { + let parsedError: ApiError | string = error as ApiError; + + try { + if (typeof error === 'string') { + parsedError = JSON.parse(error) as ApiError; + } else if (error && typeof error === 'object' && 'message' in error) { + parsedError = JSON.parse((error as Error).message) as ApiError; + } + } catch { + // If parsing fails, use the original error + } + + let message: string; + if (typeof parsedError === 'string') { + message = parsedError; + } else { + message = parsedError?.errorMessage || parsedError?.error_message || parsedError?.message || String(parsedError); + } + + if (typeof parsedError === 'object' && parsedError?.errors && Object.keys(parsedError.errors).length > 0) { + const errors = parsedError.errors; + Object.keys(errors).forEach((e) => { + let entity = e; + switch (e) { + case 'authorization': + entity = 'Management Token'; + break; + case 'api_key': + entity = 'Stack API key'; + break; + case 'uid': + entity = 'Content Type'; + break; + case 'access_token': + entity = 'Delivery Token'; + break; + } + message += ' ' + [entity, errors[e]].join(' '); + }); + } + + return message; +} + +/** + * Handle and print error messages. + * Uses the CLI utilities handleAndLogError for consistent error handling. + * Exits the process with code 1. + */ +export function handleErrorMsg(err: ErrorWithMessage | Error | unknown, context?: Record): never { + const errorObj = err as ErrorWithMessage; + const errorMessage = errorObj?.errorMessage || errorObj?.message || messageHandler.parse('CLI_EXPORT_CSV_API_FAILED'); + + log.debug('Error occurred', { ...context, error: errorMessage }); + cliux.print(`Error: ${errorMessage}`, { color: 'red' }); + + process.exit(1); +} + +/** + * Handle taxonomy-specific errors. + * Exits the process with code 1. + */ +export function handleTaxonomyErrorMsg(err: TaxonomyError | Error | unknown, context?: Record): never { + const errorObj = err as TaxonomyError; + + if (errorObj?.errorMessage || errorObj?.message) { + const errorMsg = errorObj?.errorMessage || errorObj?.errors?.taxonomy || errorObj?.errors?.term || errorObj?.message; + log.debug('Taxonomy error', { ...context, error: errorMsg }); + cliux.print(`Error: ${errorMsg}`, { color: 'red' }); + } else { + log.debug('Unknown taxonomy error', { ...context, error: err }); + console.log(err); + cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' }); + } + + process.exit(1); +} + +/** + * Utility function to wait for a specified time. + * Used for rate limiting API calls. + */ +export function wait(time: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, time); + }); +} + +/** + * Exit the program gracefully. + */ +export function exitProgram(): never { + log.debug('Exiting program'); + process.exit(0); +} diff --git a/packages/contentstack-export-to-csv/src/utils/index.ts b/packages/contentstack-export-to-csv/src/utils/index.ts new file mode 100644 index 0000000000..fcbd22ae36 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/index.ts @@ -0,0 +1,67 @@ +/** + * Utility module exports. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +// Error handling +export { formatError, handleErrorMsg, handleTaxonomyErrorMsg, wait, exitProgram } from './error-handler'; + +// Data transformation +export { + flatten, + sanitizeData, + cleanEntries, + getMappedUsers, + getMappedRoles, + determineUserOrgRole, + cleanOrgUsers, + cleanTeamsData, + getTeamsUserDetails, + formatTaxonomiesData, + formatTermsOfTaxonomyData, + kebabize, + getFormattedDate, + getDateTime, +} from './data-transform'; + +// CSV writing +export { write, csvParse } from './csv-writer'; + +// API client +export { + getOrganizations, + getOrganizationsWhereUserIsAdmin, + getOrgUsers, + getOrgRoles, + getStacks, + getContentTypeCount, + getContentTypes, + getLanguages, + getEntriesCount, + getEntries, + getEnvironments, + getAllTeams, + exportOrgTeams, + getRoleData, + taxonomySDKHandler, + getAllTaxonomies, + getAllTermsOfTaxonomy, + getTaxonomy, + createImportableCSV, +} from './api-client'; + +// Interactive prompts +export { + startupQuestions, + chooseOrganization, + chooseStack, + chooseBranch, + chooseContentType, + chooseInMemContentTypes, + chooseLanguage, + chooseFallbackOptions, + promptContinueExport, +} from './interactive'; + +// Team export functions (composite functions) +export { exportTeams, getTeamsDetail, exportRoleMappings, mapRoleWithTeams } from './teams-export'; diff --git a/packages/contentstack-export-to-csv/src/utils/interactive.ts b/packages/contentstack-export-to-csv/src/utils/interactive.ts new file mode 100644 index 0000000000..ef9bd99767 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/interactive.ts @@ -0,0 +1,387 @@ +/** + * Interactive prompt utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + */ + +import inquirer, { Answers } from 'inquirer'; +// @ts-ignore - no types available +import checkboxPlus from 'inquirer-checkbox-plus-prompt'; +import { cliux } from '@contentstack/cli-utilities'; + +import { messages } from '../messages'; +import { exitProgram } from './error-handler'; +import { getOrganizations, getOrganizationsWhereUserIsAdmin, getStacks, getLanguages } from './api-client'; +import type { + ManagementClient, + StackClient, + OrganizationChoice, + StackChoice, + BranchChoice, + LanguageChoice, + FallbackOptions, + Branch, + OrgMap, + LanguageMap, +} from '../types'; + +// Register checkbox-plus prompt type +inquirer.registerPrompt('checkbox-plus', checkboxPlus); + +// ============================================================================ +// Startup Questions +// ============================================================================ + +/** + * Display startup questions to choose an action. + */ +export function startupQuestions(): Promise { + return new Promise((resolve, reject) => { + const actions = [ + { + type: 'list', + name: 'action', + message: 'Choose Action', + choices: [messages.ACTION_EXPORT_ENTRIES, messages.ACTION_EXPORT_USERS, messages.ACTION_EXPORT_TEAMS, messages.ACTION_EXPORT_TAXONOMIES, 'Exit'], + }, + ]; + + inquirer + .prompt(actions) + .then((answers: Answers) => { + if (answers.action === 'Exit') exitProgram(); + resolve(answers.action as string); + }) + .catch(reject); + }); +} + +// ============================================================================ +// Organization Prompts +// ============================================================================ + +/** + * Prompt user to choose an organization. + */ +export function chooseOrganization( + managementAPIClient: ManagementClient, + action?: string, +): Promise { + return new Promise(async (resolve, reject) => { + try { + let organizations: OrgMap; + + if (action === messages.ACTION_EXPORT_USERS || action === messages.ACTION_EXPORT_TEAMS || action === 'teams') { + organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient); + } else { + organizations = await getOrganizations(managementAPIClient); + } + + const orgList = Object.keys(organizations); + orgList.push(messages.ACTION_CANCEL); + + const _chooseOrganization = [ + { + type: 'list', + name: 'chosenOrg', + message: 'Choose an Organization', + choices: orgList, + loop: false, + }, + ]; + + inquirer + .prompt(_chooseOrganization) + .then((answers: Answers) => { + const chosenOrg = answers.chosenOrg as string; + if (chosenOrg === messages.ACTION_CANCEL) exitProgram(); + resolve({ name: chosenOrg, uid: organizations[chosenOrg] }); + }) + .catch(reject); + } catch (error) { + reject(error); + } + }); +} + +// ============================================================================ +// Stack Prompts +// ============================================================================ + +/** + * Prompt user to choose a stack. + */ +export function chooseStack( + managementAPIClient: ManagementClient, + orgUid: string, + stackApiKey?: string, +): Promise { + return new Promise(async (resolve, reject) => { + try { + const stacks = await getStacks(managementAPIClient, orgUid); + + if (stackApiKey) { + const stackName = Object.keys(stacks).find((key) => stacks[key] === stackApiKey); + + if (stackName) { + resolve({ name: stackName, apiKey: stackApiKey }); + } else { + throw new Error('Could not find stack'); + } + return; + } + + const stackList = Object.keys(stacks); + stackList.push(messages.ACTION_CANCEL); + + const _chooseStack = [ + { + type: 'list', + name: 'chosenStack', + message: 'Choose a Stack', + choices: stackList, + }, + ]; + + inquirer + .prompt(_chooseStack) + .then((answers: Answers) => { + const chosenStack = answers.chosenStack as string; + if (chosenStack === messages.ACTION_CANCEL) exitProgram(); + resolve({ name: chosenStack, apiKey: stacks[chosenStack] }); + }) + .catch(reject); + } catch (error) { + reject(error); + } + }); +} + +// ============================================================================ +// Branch Prompts +// ============================================================================ + +/** + * Prompt user to choose a branch. + */ +export async function chooseBranch(branchList: Branch[]): Promise { + try { + const branchesArray = branchList.map((branch) => branch.uid); + + const _chooseBranch = [ + { + type: 'list', + name: 'branch', + message: 'Choose a Branch', + choices: branchesArray, + }, + ]; + + const answers = await inquirer.prompt(_chooseBranch); + return { branch: answers.branch as string }; + } catch (err) { + cliux.error(err as string); + throw err; + } +} + +// ============================================================================ +// Content Type Prompts +// ============================================================================ + +/** + * Prompt user to choose content types (basic checkbox). + */ +export function chooseContentType(stackAPIClient: StackClient, skip: number): Promise { + return new Promise(async (resolve, reject) => { + const { getContentTypes } = await import('./api-client'); + const contentTypes = await getContentTypes(stackAPIClient, skip); + const contentTypesList = Object.values(contentTypes); + + const _chooseContentType = [ + { + type: 'checkbox', + message: 'Choose Content Type (Press Space to select the content types) ', + choices: contentTypesList, + name: 'chosenContentTypes', + loop: false, + }, + ]; + + inquirer + .prompt(_chooseContentType) + .then((answers: Answers) => resolve(answers.chosenContentTypes as string[])) + .catch(reject); + }); +} + +/** + * Checkbox-plus source function type. + */ +type CheckboxPlusSource = ( + answersSoFar: Record, + input: string, +) => Promise; + +/** + * Prompt user to choose content types (searchable multi-select). + */ +export function chooseInMemContentTypes(contentTypesList: string[]): Promise { + return new Promise((resolve, reject) => { + const source: CheckboxPlusSource = (_answersSoFar, input) => { + input = input || ''; + const inputArray = input.split(' '); + + return new Promise((resolveSource) => { + const contentTypes = contentTypesList.filter((contentType) => { + let shouldInclude = true; + inputArray.forEach((inputChunk) => { + // if any term to filter by doesn't exist, exclude + if (!contentType.toLowerCase().includes(inputChunk.toLowerCase())) { + shouldInclude = false; + } + }); + return shouldInclude; + }); + resolveSource(contentTypes); + }); + }; + + const _chooseContentType = [ + { + type: 'checkbox-plus', + message: 'Choose Content Type (Press Space to select the content types)', + choices: contentTypesList, + name: 'chosenContentTypes', + loop: false, + highlight: true, + searchable: true, + source, + }, + ]; + + inquirer + .prompt(_chooseContentType as Parameters[0]) + .then((answers: Answers) => { + const chosenContentTypes = answers.chosenContentTypes as string[]; + if (chosenContentTypes.length === 0) { + reject('Please select atleast one content type.'); + } + resolve(chosenContentTypes); + }) + .catch(reject); + }); +} + +// ============================================================================ +// Language Prompts +// ============================================================================ + +/** + * Prompt user to choose a language/locale. + */ +export function chooseLanguage(stackAPIClient: StackClient): Promise { + return new Promise(async (resolve, reject) => { + const languages: LanguageMap = await getLanguages(stackAPIClient); + const languagesList = Object.keys(languages); + languagesList.push(messages.ACTION_CANCEL); + + const _chooseLanguage = [ + { + type: 'list', + message: 'Choose Language', + choices: languagesList, + name: 'chosenLanguage', + }, + ]; + + inquirer + .prompt(_chooseLanguage) + .then((answers: Answers) => { + const chosenLanguage = answers.chosenLanguage as string; + if (chosenLanguage === messages.ACTION_CANCEL) exitProgram(); + resolve({ name: chosenLanguage, code: languages[chosenLanguage] }); + }) + .catch(reject); + }); +} + +// ============================================================================ +// Fallback Options Prompts +// ============================================================================ + +/** + * Prompt user for fallback options. + */ +export function chooseFallbackOptions(stackAPIClient: StackClient): Promise { + return new Promise(async (resolve, reject) => { + try { + const questions = [ + { + type: 'confirm', + name: 'includeFallback', + message: 'Include fallback locale data when exporting taxonomies?', + default: false, + }, + ]; + + const firstAnswers = await inquirer.prompt(questions); + const includeFallback = firstAnswers.includeFallback as boolean; + + let fallbackLocale: string | null = null; + + if (includeFallback) { + // Get available languages for fallback locale selection + const languages: LanguageMap = await getLanguages(stackAPIClient); + const languagesList = Object.keys(languages); + + const fallbackQuestion = [ + { + type: 'list', + name: 'selectedFallbackLocale', + message: 'Choose fallback locale', + choices: languagesList, + }, + ]; + + const secondAnswers = await inquirer.prompt(fallbackQuestion); + const selectedFallbackLocale = secondAnswers.selectedFallbackLocale as string; + fallbackLocale = languages[selectedFallbackLocale]; + } + + resolve({ + includeFallback, + fallbackLocale, + }); + } catch (error) { + reject(error); + } + }); +} + +// ============================================================================ +// Team Export Prompts +// ============================================================================ + +/** + * Prompt to continue exporting without certain fields. + */ +export async function promptContinueExport(): Promise { + const export_stack_role = [ + { + type: 'list', + name: 'chooseExport', + message: + 'Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.', + choices: ['yes', 'no'], + loop: false, + }, + ]; + + try { + const answers = await inquirer.prompt(export_stack_role); + return answers.chooseExport === 'yes'; + } catch (error) { + cliux.print(error as string, { color: 'red' }); + process.exit(1); + } +} diff --git a/packages/contentstack-export-to-csv/src/utils/teams-export.ts b/packages/contentstack-export-to-csv/src/utils/teams-export.ts new file mode 100644 index 0000000000..4b78dc64f6 --- /dev/null +++ b/packages/contentstack-export-to-csv/src/utils/teams-export.ts @@ -0,0 +1,261 @@ +/** + * Team export utilities. + * Migrated from: packages/contentstack-export-to-csv/src/util/index.js + * + * These are composite functions that use multiple utilities together + * for team export functionality. + */ + +import find from 'lodash/find'; +import cloneDeep from 'lodash/cloneDeep'; +import { cliux, log } from '@contentstack/cli-utilities'; + +import config from '../config'; +import { write } from './csv-writer'; +import { kebabize, getTeamsUserDetails } from './data-transform'; +import { exportOrgTeams, getRoleData } from './api-client'; +import { promptContinueExport } from './interactive'; +import type { + ManagementClient, + OrganizationChoice, + CleanedTeam, + TeamUser, + TeamCsvRow, + StackRoleMapping, + StackRoleMappingCsvRow, + StackRole, + StackRoleMap, +} from '../types'; + +/** + * Export teams data for an organization. + */ +export async function exportTeams( + managementAPIClient: ManagementClient, + organization: OrganizationChoice, + teamUid: string | undefined, + delimiter: string, +): Promise { + const logContext = { module: 'teams-export', orgUid: organization.uid, teamUid }; + + log.debug('Starting teams export', logContext); + + cliux.print( + `info: Exporting the ${ + teamUid && organization?.name + ? `team with uid ${teamUid} in Organisation ${organization?.name} ` + : `teams of Organisation ` + organization?.name + }`, + { color: 'blue' }, + ); + + cliux.loader('Fetching teams...'); + const allTeamsData = await exportOrgTeams(managementAPIClient, organization); + cliux.loader(); + + if (!allTeamsData?.length) { + log.info('No teams found', logContext); + cliux.print( + `info: The organization ${organization?.name} does not have any teams associated with it. Please verify and provide the correct organization name.`, + ); + return; + } + + log.debug(`Found ${allTeamsData.length} teams`, logContext); + + const modifiedTeam: TeamCsvRow[] = cloneDeep(allTeamsData).map((team) => { + const csvRow: TeamCsvRow = { + uid: team.uid, + name: team.name, + description: team.description, + organizationRole: team.organizationRole, + Total_Members: team.Total_Members, + }; + return csvRow; + }); + + const fileName = `${kebabize(organization.name.replace(config.organizationNameRegex, ''))}_teams_export.csv`; + log.info(`Writing teams to ${fileName}`, logContext); + write(null, modifiedTeam, fileName, ' organization Team details', delimiter); + + // Exporting teams user data or a single team user data + cliux.print( + `info: Exporting the teams user data for ${teamUid ? `team ` + teamUid : `organisation ` + organization?.name}`, + { color: 'blue' }, + ); + await getTeamsDetail(allTeamsData, organization, teamUid, delimiter); + + cliux.print( + `info: Exporting the stack role details for ${ + teamUid ? `team ` + teamUid : `organisation ` + organization?.name + }`, + { color: 'blue' }, + ); + + // Exporting the stack Role data for all the teams or exporting stack role data for a single team + await exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter); + + log.success('Teams export completed', logContext); +} + +/** + * Get individual team user details and write to file. + */ +export async function getTeamsDetail( + allTeamsData: CleanedTeam[], + organization: OrganizationChoice, + teamUid: string | undefined, + delimiter: string, +): Promise { + const logContext = { module: 'teams-export', action: 'team-users', teamUid }; + + log.debug('Exporting team user details', logContext); + + if (!teamUid) { + const userData = getTeamsUserDetails(allTeamsData); + const fileName = `${kebabize( + organization.name.replace(config.organizationNameRegex, ''), + )}_team_User_Details_export.csv`; + + log.info(`Writing ${userData.length} team users to ${fileName}`, logContext); + write(null, userData, fileName, 'Team User details', delimiter); + } else { + const team = allTeamsData.filter((t) => t.uid === teamUid)[0]; + + if (!team) { + log.debug('Team not found', { ...logContext, teamUid }); + cliux.print(`Team with UID ${teamUid} not found.`, { color: 'red' }); + return; + } + + const teamUsers: TeamUser[] = (team.users || []).map((user) => ({ + ...user, + 'team-name': team.name, + 'team-uid': team.uid, + })); + + // Remove unwanted properties + teamUsers.forEach((user) => { + delete user.active; + delete user.orgInvitationStatus; + }); + + const fileName = `${kebabize( + organization.name.replace(config.organizationNameRegex, ''), + )}_team_${teamUid}_User_Details_export.csv`; + + log.info(`Writing ${teamUsers.length} users for team ${teamUid} to ${fileName}`, logContext); + write(null, teamUsers, fileName, 'Team User details', delimiter); + } +} + +/** + * Export role mappings of teams to CSV. + */ +export async function exportRoleMappings( + managementAPIClient: ManagementClient, + allTeamsData: CleanedTeam[], + teamUid: string | undefined, + delimiter: string, +): Promise { + const logContext = { module: 'teams-export', action: 'role-mappings', teamUid }; + + log.debug('Exporting role mappings', logContext); + + const stackRoleWithTeamData: StackRoleMappingCsvRow[] = []; + let flag = false; + const stackNotAdmin: string[] = []; + + cliux.loader('Fetching stack role mappings...'); + + if (teamUid) { + const team = find(allTeamsData, function (teamObject) { + return teamObject?.uid === teamUid; + }); + + for (const stack of team?.stackRoleMapping || []) { + const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name || '', team?.uid || ''); + stackRoleWithTeamData.push(...roleData); + + if (roleData[0]['Stack Name'] === '') { + flag = true; + stackNotAdmin.push(stack.stackApiKey); + } + } + } else { + for (const team of allTeamsData ?? []) { + for (const stack of team?.stackRoleMapping ?? []) { + const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid); + stackRoleWithTeamData.push(...roleData); + + if (roleData[0]['Stack Name'] === '') { + flag = true; + stackNotAdmin.push(stack.stackApiKey); + } + } + } + } + + cliux.loader(); + + if (stackNotAdmin?.length) { + log.debug('Admin access denied to some stacks', { ...logContext, stacks: stackNotAdmin }); + cliux.print( + `warning: Admin access denied to the following stacks using the provided API keys. Please get in touch with the stack owner to request access.`, + { color: 'yellow' }, + ); + cliux.print(`${stackNotAdmin.join(' , ')}`, { color: 'yellow' }); + } + + if (flag) { + const shouldContinue = await promptContinueExport(); + if (!shouldContinue) { + log.debug('User chose not to continue export', logContext); + process.exit(1); + } + } + + const fileName = `${kebabize('Stack_Role_Mapping'.replace(config.organizationNameRegex, ''))}${ + teamUid ? `_${teamUid}` : '' + }.csv`; + + log.info(`Writing ${stackRoleWithTeamData.length} role mappings to ${fileName}`, logContext); + write(null, stackRoleWithTeamData, fileName, 'Team Stack Role details', delimiter); +} + +/** + * Map team stacks with stack role and return array of objects. + */ +export async function mapRoleWithTeams( + managementAPIClient: ManagementClient, + stackRoleMapping: StackRoleMapping, + teamName: string, + teamUid: string, +): Promise { + const rolesResponse = await getRoleData(managementAPIClient, stackRoleMapping.stackApiKey); + const stackRole: StackRoleMap = {}; + + rolesResponse.items?.forEach((role: StackRole) => { + if (!Object.prototype.hasOwnProperty.call(stackRole, role?.uid)) { + stackRole[role?.uid] = role?.name; + if (role?.stack?.api_key) { + stackRole[role.stack.api_key] = { name: role.stack.name, uid: role.stack.uid }; + } + } + }); + + const stackInfo = stackRole[stackRoleMapping?.stackApiKey] as { name: string; uid: string } | undefined; + + const stackRoleMapOfTeam: StackRoleMappingCsvRow[] = stackRoleMapping?.roles.map((role) => { + return { + 'Team Name': teamName, + 'Team Uid': teamUid, + 'Stack Name': stackInfo?.name || '', + 'Stack Uid': stackInfo?.uid || '', + 'Role Name': (stackRole[role] as string) || '', + 'Role Uid': role || '', + }; + }); + + return stackRoleMapOfTeam; +} diff --git a/packages/contentstack-export-to-csv/test/commands/export-to-csv.test.js b/packages/contentstack-export-to-csv/test/commands/export-to-csv.test.js deleted file mode 100644 index 6e0fb6552f..0000000000 --- a/packages/contentstack-export-to-csv/test/commands/export-to-csv.test.js +++ /dev/null @@ -1,43 +0,0 @@ -const { test } = require('@oclif/test'); -const util = require('../../src/util'); -const config = require('../../src/util/config.js'); - -// mock data for Export Entries to CSV -const sampleEnvironments = { envUid1: 'envName1', envUid2: 'envName2' }; -const sampleEntries = require('../mock-data/entries.json'); -// mock data for Export Organization Users -// eslint-disable-next-line no-undef -describe('export to csv', () => { - test - .stdout() - .stub(util, 'startupQuestions', () => Promise.resolve(config.exportEntries)) - .stub(util, 'chooseOrganization', () => Promise.resolve({ uid: 'sampleOrgUid', name: 'sampleOrg' })) - .stub(util, 'chooseStack', () => Promise.resolve({ name: 'someStack', apiKey: 'sampleApiKey' })) - .stub(util, 'chooseContentType', () => Promise.resolve({ name: 'someContentType', uid: 'sampleCtUid' })) - .stub(util, 'chooseLanguage', () => Promise.resolve({ name: 'en-us', code: 'en-us' })) - .stub(util, 'getEntries', () => Promise.resolve(sampleEntries)) - .stub(util, 'getEnvironments', () => Promise.resolve(sampleEnvironments)) - .stub(util, 'write', () => {}) - .command(['cm:export-to-csv']) - // .it('runs hello', ctx => { - .it('executes export-to-csv command for entries', () => {}); - - test - .stdout() - .stub(util, 'startupQuestions', () => Promise.resolve(config.exportUsers)) - .stub(util, 'chooseOrganization', () => Promise.resolve({ uid: 'sampleOrgUid', name: 'sampleOrg' })) - .stub(util, 'getOrgUsers', () => Promise.resolve({})) - .stub(util, 'getOrgRoles', () => Promise.resolve({})) - .stub(util, 'getMappedUsers', () => { - return {}; - }) - .stub(util, 'getMappedRoles', () => { - return {}; - }) - - .stub(util, 'cleanOrgUsers', () => Promise.resolve({})) - .stub(util, 'write', () => {}) - .command(['cm:export-to-csv']) - // .it('runs hello', ctx => { - .it('executes export-to-csv command for exporting users', () => {}); -}); diff --git a/packages/contentstack-export-to-csv/test/helpers/init.js b/packages/contentstack-export-to-csv/test/helpers/init.js new file mode 100644 index 0000000000..9f5593da0e --- /dev/null +++ b/packages/contentstack-export-to-csv/test/helpers/init.js @@ -0,0 +1,8 @@ +// Test initialization helper +const path = require('path'); + +process.env.TS_NODE_PROJECT = path.resolve('test/tsconfig.json'); +process.env.NODE_ENV = 'test'; + +// Suppress debug output during tests +process.env.DEBUG = ''; diff --git a/packages/contentstack-export-to-csv/test/mocha.opts b/packages/contentstack-export-to-csv/test/mocha.opts deleted file mode 100644 index c6d1cb290c..0000000000 --- a/packages/contentstack-export-to-csv/test/mocha.opts +++ /dev/null @@ -1,3 +0,0 @@ ---recursive ---reporter spec ---timeout 5000 diff --git a/packages/contentstack-export-to-csv/test/mock-data/common.mock.json b/packages/contentstack-export-to-csv/test/mock-data/common.mock.json deleted file mode 100644 index 46f65c1e25..0000000000 --- a/packages/contentstack-export-to-csv/test/mock-data/common.mock.json +++ /dev/null @@ -1,420 +0,0 @@ -{ - "taxonomiesResp": { - "taxonomies": [ - { - "uid": "taxonomy_uid_1", - "name": "taxonomy uid 1", - "description": "", - "created_at": "2023-09-01T06:09:44.934Z", - "created_by": "user1", - "updated_at": "2023-09-01T06:44:16.604Z", - "updated_by": "user1" - }, - { - "uid": "taxonomy_uid_2", - "name": "taxonomy uid 2", - "description": "", - "created_at": "2023-09-01T06:09:44.934Z", - "created_by": "user1", - "updated_at": "2023-09-01T06:44:16.604Z", - "updated_by": "user1" - } - ], - "count": 2 - }, - "termsResp": { - "terms": [ - { - "uid": "wsq", - "name": "wsq", - "created_at": "2023-08-30T09:51:12.043Z", - "created_by": "user1", - "updated_at": "2023-08-30T09:51:12.043Z", - "updated_by": "user1", - "parent_uid": null, - "depth": 1 - }, - { - "uid": "term2", - "name": "term2", - "created_at": "2023-08-30T09:45:11.963Z", - "created_by": "user2", - "updated_at": "2023-08-30T09:45:11.963Z", - "updated_by": "user2", - "parent_uid": null, - "depth": 1 - } - ], - "count": 2 - }, - "organizations": [ - { - "uid": "test-uid-1", - "name": "test org 1" - }, - { - "uid": "test-uid-2", - "name": "test org 2" - }, - { - "uid": "org_uid_1_teams", - "name": "Teams Org" - } - ], - "stacks": [ - { - "name": "Stack 1", - "uid": "stack-uid-1", - "api_key": "stack_api_key_1" - }, - { - "name": "Stack 2", - "uid": "stack-uid-2", - "api_key": "stack_api_key_2" - } - ], - "users": [ - { - "uid": "uid1", - "email": "test@gmail.abc", - "user_uid": "user1", - "org_uid": "test-uid-1", - "invited_by": "user2", - "invited_at": "2023-08-21T11:08:41.038Z", - "status": "accepted", - "acceptance_token": "dfghdfgd", - "created_at": "2023-08-21T11:08:41.036Z", - "updated_at": "2023-08-21T11:09:11.342Z", - "urlPath": "/user", - "organizations": [ - { - "uid": "test-uid-1", - "name": "test org 1", - "org_roles": [ - { - "uid": "role1", - "name": "Admin", - "description": "Admin Role", - "org_uid": "test-uid-1", - "admin": true, - "default": true - } - ] - } - ] - }, - { - "uid": "test-uid-2", - "name": "test org 2" - }, - { - "organizations": [ - { - "uid": "org_uid_1_teams", - "name": "Teams Org", - "org_roles": [ - { - "uid": "role1", - "name": "Admin", - "description": "Admin Role", - "org_uid": "test-uid-1", - "admin": true, - "default": true - } - ] - } - ] - - } - ], - "roles": [ - { - "urlPath": "/roles/role1", - "uid": "role1", - "name": "admin", - "description": "The Admin role has rights to add/remove users from an organization, and has access to stacks created by self or shared by others.", - "org_uid": "test-uid-1", - "owner_uid": "user1", - "admin": true, - "default": true, - "users": ["user2", "user3"], - "created_at": "2023-08-08T10:09:43.445Z", - "updated_at": "2023-08-21T11:08:41.042Z" - } - ], - "contentTypes": [ - { - "stackHeaders": { - "api_key": "stack_api_key_1" - }, - "urlPath": "/content_types/ct1", - "created_at": "2023-08-08T13:52:31.980Z", - "updated_at": "2023-08-08T13:52:34.265Z", - "title": "CT 1", - "uid": "ct_1", - "_version": 2, - "inbuilt_class": false, - "schema": [ - { - "data_type": "text", - "display_name": "Title", - "field_metadata": { - "_default": true, - "version": 3 - }, - "mandatory": true, - "uid": "title", - "unique": true, - "multiple": false, - "non_localizable": false - } - ] - }, - { - "stackHeaders": { - "api_key": "stack_api_key_1" - }, - "urlPath": "/content_types/ct2", - "created_at": "2023-08-08T13:52:31.980Z", - "updated_at": "2023-08-08T13:52:34.265Z", - "title": "CT 2", - "uid": "ct_2", - "_version": 2, - "inbuilt_class": false, - "schema": [ - { - "data_type": "text", - "display_name": "Title", - "field_metadata": { - "_default": true, - "version": 3 - }, - "mandatory": true, - "uid": "title", - "unique": true, - "multiple": false, - "non_localizable": false - } - ] - } - ], - "branch": { - "stackHeaders": { - "api_key": "stack_api_key_1" - }, - "urlPath": "/stacks/branches/test_branch1", - "uid": "test_branch1", - "source": "", - "created_by": "user1", - "updated_by": "user1", - "created_at": "2023-08-08T13:51:43.217Z", - "updated_at": "2023-08-08T13:51:43.217Z", - "deleted_at": false - }, - "entry": [ - { - "stackHeaders": { - "api_key": "stack_api_key_1" - }, - "content_type_uid": "home", - "urlPath": "/content_types/ct1/entries/test_entry1", - "title": "Test Entry1", - "url": "/", - "tags": [], - "locale": "en1", - "uid": "test_entry1", - "created_by": "user1", - "updated_by": "user1", - "created_at": "2023-08-08T13:52:46.592Z", - "updated_at": "2023-08-08T13:52:46.592Z", - "_version": 1 - } - ], - "environments": [ - { - "stackHeaders": { - "api_key": "stack_api_key_1" - }, - "urlPath": "/environments/development", - "urls": [ - { - "url": "http://localhost:3000/", - "locale": "en1" - } - ], - "name": "development", - "_version": 3, - "uid": "env1", - "created_by": "user1", - "updated_by": "user1", - "created_at": "2023-06-12T18:59:56.853Z", - "updated_at": "2023-06-12T18:59:56.853Z" - } - ], - "locales": [ - { - "code": "en1", - "name": "English - En", - "fallback_locale": "en-us", - "uid": "gsfdasgdf", - "created_at": "2023-09-11T10:44:40.213Z", - "updated_at": "2023-09-11T10:44:40.213Z", - "ACL": [], - "_version": 1 - }, - { - "code": "en-us", - "name": "English - United States", - "locale": null, - "uid": "en-us-uid", - "created_at": "2023-09-11T10:44:40.213Z", - "updated_at": "2023-09-11T10:44:40.213Z", - "ACL": [], - "_version": 1 - }, - { - "code": "fr-fr", - "name": "French - France", - "fallback_locale": "en-us", - "uid": "fr-fr-uid", - "created_at": "2023-09-11T10:44:40.213Z", - "updated_at": "2023-09-11T10:44:40.213Z", - "ACL": [], - "_version": 1 - } - ], - "Teams": { - "emptyTeam": [], - "allTeams": { - "count": 1, - "teams": [ - { - "_id": "team_1_uid", - "name": "Test_Team_1", - - "organizationUid": "org_uid_1_teams", - "users": [], - "stackRoleMapping": [ - { - "stackApiKey": "stack_api_key", - "roles": ["role_uid_1"] - } - ], - "organizationRole": "org_role_uid_1", - - "uid": "team_1_uid", - "createdByUserName": "team_creator", - "updatedByUserName": "team_creator" - } - ] - } - }, - "roless": { - "roles": [ - { - "name": "Developer", - "description": "Developer can perform all Content Manager's actions, view audit logs, create roles, invite users, manage content types, languages, and environments.", - "uid": "stack_role_uid_3", - "users": [], - "owner": "ownerEmail@domain.com", - "stack": { - "uid": "stack_uid", - "name": "CLI Test", - "org_uid": "org_uid_1_teams", - "api_key": "stack_api_key", - "master_locale": "en-us", - - "owner_uid": "team_owner_uid", - "user_uids": ["team_owner_uid"] - }, - "SYS_ACL": {} - }, - { - "name": "Content Manager", - "description": "Content Managers can view all content types, manage entries and assets. They cannot edit content types or access stack settings.", - "uid": "stack_role_uid_1", - - "updated_by": "team_owner_uid", - - "owner": "ownerEmail@domain.com", - "stack": { - "uid": "stack_uid", - "name": "CLI Test", - "org_uid": "org_uid_1_teams", - "api_key": "stack_api_key", - "master_locale": "en-us", - - "owner_uid": "team_owner_uid", - "user_uids": ["team_owner_uid"] - }, - "SYS_ACL": {} - }, - { - "name": "Admin", - "description": "Admin can perform all actions and manage all settings of the stack, except the ability to delete or transfer ownership of the stack.", - "uid": "role_uid_1", - - "updated_at": "2023-10-26T04:44:51.529Z", - "users": [], - "owner": "ownerEmail@domain.com", - "stack": { - "uid": "stack_uid", - "name": "CLI Test", - "org_uid": "org_uid_1_teams", - "api_key": "stack_api_key", - "master_locale": "en-us", - - "owner_uid": "team_owner_uid", - "user_uids": ["team_owner_uid"] - }, - "SYS_ACL": {} - }, - { - "name": "Custom_Role_1", - "description": "", - "users": [], - "uid": "stack_role_uid_2", - - "updated_by": "team_owner_uid", - - "owner": "ownerEmail@domain.com", - "stack": { - "uid": "stack_uid", - "name": "CLI Test", - "org_uid": "org_uid_1_teams", - "api_key": "stack_api_key", - "master_locale": "en-us", - - "owner_uid": "team_owner_uid", - "user_uids": ["team_owner_uid"] - }, - "SYS_ACL": {} - } - ] - }, - "org_roles": { - "roles": [ - { - "uid": "org_role_uid_1", - "name": "admin", - "description": "The Admin role has rights to add/remove users from an organization, and has access to stacks created by self or shared by others.", - "org_uid": "org_uid_1_teams", - "owner_uid": "org_owner_uid", - "admin": true, - "default": true, - "users": ["user_1_uid", "org_owner_uid", "user_2_uid"] - }, - { - "uid": "org_role_uid_2", - "name": "member", - "description": "The Member role has access only to the stacks created by or shared with him/her, and does not have access to organization settings.", - "org_uid": "org_uid_1_teams", - "owner_uid": "org_owner_uid", - "admin": false, - "default": true, - "users": ["user_1_uid_member", "user_2_uid_member", "user_3_uid_member", "user_4_uid_member"] - } - ] - }, - "taxonomyCSVData": "`taxonomy1,taxonomy1,,,,,,,\n,,,term1,term1,,,,\n,,,,,term1_2,term1_2,,\n,,,term2,term2,,,,\n,,,,,term2_2,term2_2,,\n,,,,,,,term2_2_1,term2_2_1\n,,,,,term2_1,term2_1,,`" -} diff --git a/packages/contentstack-export-to-csv/test/mock-data/entries.json b/packages/contentstack-export-to-csv/test/mock-data/entries.json deleted file mode 100644 index 8d645117d3..0000000000 --- a/packages/contentstack-export-to-csv/test/mock-data/entries.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "items": [ - { - "stackHeaders": [ - null - ], - "content_type_uid": "uid", - "title": "C1", - "url": "/c1", - "json": {}, - "one": [ - null - ], - "tags": [], - "locale": "en-us", - "ACL": {}, - "_version": 4, - "_in_progress": false, - "update": [ - null - ], - "delete": [ - null - ], - "fetch": [ - null - ], - "publish": [ - null - ], - "unpublish": [ - null - ], - "import": [ - null - ], - "publish_details" : [{ - "environment": "envUid1", - "locale": "en-us" - }] - }, - { - "stackHeaders": [ - null - ], - "content_type_uid": "uid", - "title": "FLipkart9", - "url": "/flipkart", - "locale": "en-us", - "ACL": {}, - "_version": 2, - "tags": [], - "_in_progress": false, - "json": {}, - "one": [ - null - ], - "update": [ - null - ], - "delete": [ - null - ], - "fetch": [ - null - ], - "publish": [ - null - ], - "unpublish": [ - null - ], - "import": [ - null - ], - "publish_details" : [{ - "environment": "envUid1", - "locale": "en-us" - }] - }, - { - "stackHeaders": [ - null - ], - "content_type_uid": "uid", - "title": "Untitled-1", - "url": "/untitled", - "json": {}, - "tags": [], - "locale": "en-us", - "ACL": {}, - "_version": 3, - "_in_progress": false, - "one": [ - null - ], - "update": [ - null - ], - "delete": [ - null - ], - "fetch": [ - null - ], - "publish": [ - null - ], - "unpublish": [ - null - ], - "import": [ - null - ], - "publish_details" : [{ - "environment": "envUid1", - "locale": "en-us" - }] - } - ] -} \ No newline at end of file diff --git a/packages/contentstack-export-to-csv/test/tsconfig.json b/packages/contentstack-export-to-csv/test/tsconfig.json new file mode 100644 index 0000000000..cdd474c8db --- /dev/null +++ b/packages/contentstack-export-to-csv/test/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": false, + "noEmit": true, + "rootDir": ".." + }, + "include": [ + "./**/*.ts" + ] +} diff --git a/packages/contentstack-export-to-csv/test/unit/base-command.test.ts b/packages/contentstack-export-to-csv/test/unit/base-command.test.ts new file mode 100644 index 0000000000..fb2277a511 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/base-command.test.ts @@ -0,0 +1,17 @@ +import { expect } from 'chai'; +import { BaseCommand } from '../../src/base-command'; + +describe('BaseCommand', () => { + describe('class definition', () => { + it('should be an abstract class that extends Command', () => { + expect(BaseCommand).to.be.a('function'); + expect(BaseCommand.prototype).to.have.property('init'); + expect(BaseCommand.prototype).to.have.property('catch'); + expect(BaseCommand.prototype).to.have.property('finally'); + }); + + it('should have createCommandContext method', () => { + expect(BaseCommand.prototype).to.have.property('createCommandContext'); + }); + }); +}); diff --git a/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js b/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js deleted file mode 100644 index 08104a86a9..0000000000 --- a/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js +++ /dev/null @@ -1,359 +0,0 @@ -const { expect } = require('chai'); -const nock = require('nock'); -const fs = require('fs'); -const inquirer = require('inquirer'); -const { PassThrough } = require('stream'); -const mockData = require('../../mock-data/common.mock.json'); -const { configHandler } = require('@contentstack/cli-utilities'); -const { runCommand } = require('@oclif/test'); -const sinon = require('sinon'); - -const regionConfig = configHandler.get('region') || {}; -const cma = regionConfig.cma || 'https://api.contentstack.io/v3'; -let sandbox; - -describe('Export to CSV functionality', () => { - beforeEach(() => { - if (!configHandler.get('authorisationType')) { - configHandler.set('authorisationType', 'BASIC'); - configHandler.set('delete', true); - } - sandbox = sinon.createSandbox(); - sandbox.stub(fs, 'createWriteStream').returns(new PassThrough()); - nock(cma) - .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) - .reply(200, { stacks: mockData.stacks }); - }); - - afterEach(() => { - if (configHandler.get('delete')) { - configHandler.delete('delete'); - configHandler.delete('authorisationType'); - } - sandbox.restore(); - nock.cleanAll(); - }); - - describe('Export taxonomies', () => { - it('CSV file should be created with taxonomy uid and locale parameters', async () => { - nock(cma) - .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}`) - .reply(200, { taxonomy: mockData.taxonomiesResp.taxonomies[0] }) - .get( - `/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}/export?format=csv&locale=en-us&include_fallback=true&fallback_locale=en-us`, - ) - .reply(200, mockData.taxonomyCSVData); - - const { stdout } = await runCommand([ - 'cm:export-to-csv', - '--action', - 'taxonomies', - '--taxonomy-uid', - mockData.taxonomiesResp.taxonomies[0].uid, - '--stack-api-key', - mockData.stacks[0].api_key, - '--org', - mockData.organizations[0].uid, - '--locale', - 'en-us', - '--include-fallback', - '--fallback-locale', - 'en-us', - ]); - expect(stdout).to.include('Writing taxonomies to file:'); - }); - - it('CSV file should be created without taxonomy uid and with locale parameters', async () => { - nock(cma) - .get( - '/v3/taxonomies?include_count=true&limit=100&skip=0&locale=en-us&include_fallback=true&fallback_locale=en-us', - ) - .reply(200, mockData.taxonomiesResp) - .get( - `/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}/export?format=csv&locale=en-us&include_fallback=true&fallback_locale=en-us`, - ) - .reply(200, mockData.taxonomyCSVData) - .get( - `/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[1].uid}/export?format=csv&locale=en-us&include_fallback=true&fallback_locale=en-us`, - ) - .reply(200, mockData.taxonomyCSVData); - - const { stdout } = await runCommand([ - 'cm:export-to-csv', - '--action', - 'taxonomies', - '--stack-api-key', - mockData.stacks[0].api_key, - '--org', - mockData.organizations[0].uid, - '--locale', - 'en-us', - '--include-fallback', - '--fallback-locale', - 'en-us', - ]); - expect(stdout).to.include('Writing taxonomies to file:'); - }); - }); - - describe('Export entries', () => { - it('Entries CSV file should be created with flags', async () => { - nock(cma) - .get(`/v3/environments`) - .reply(200, { environments: mockData.environments }) - .get('/v3/content_types?count=true') - .reply(200, { content_types: 2 }) - .get('/v3/content_types') - .reply(200, { content_types: mockData.contentTypes }) - .get( - `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=en1&count=true`, - ) - .reply(200, { entries: 1 }) - .get( - `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=en1&skip=0&limit=100&include_workflow=true`, - ) - .reply(200, { entries: mockData.entry }); - - const result = await runCommand([ - 'cm:export-to-csv', - '--action', - 'entries', - '--stack-api-key', - mockData.stacks[0].api_key, - '--org', - mockData.organizations[0].uid, - '--branch', - mockData.branch.uid, - '--locale', - 'en1', - '--content-type', - mockData.contentTypes[0].uid, - ]); - expect(result.stdout).to.include('Writing entries to file:'); - }); - - it('Entries CSV file should be created with prompt', async () => { - sandbox.stub(inquirer, 'registerPrompt').returns(undefined); - sandbox.stub(inquirer, 'prompt').returns( - Promise.resolve({ - action: 'entries', - chosenOrg: mockData.organizations[0].name, - chosenLanguage: mockData.locales[0].name, - chosenStack: mockData.stacks[0].name, - chosenContentTypes: [mockData.contentTypes[0].uid], - branch: mockData.branch.uid, - }), - ); - nock(cma) - .get(`/v3/organizations?limit=100`) - .reply(200, { organizations: mockData.organizations }) - .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) - .reply(200, { stacks: mockData.stacks }) - .get('/v3/environments') - .reply(200, { environments: mockData.environments }) - .get('/v3/locales') - .reply(200, { locales: mockData.locales }) - .get('/v3/stacks/branches') - .reply(200, { branches: mockData.branch }) - .get('/v3/content_types?count=true') - .reply(200, { content_types: 2 }) - .get('/v3/content_types?skip=0&include_branch=true') - .reply(200, { content_types: mockData.contentTypes }) - .get( - `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=${mockData.locales[0].code}&count=true`, - ) - .reply(200, { entries: 1 }) - .get( - `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=${mockData.locales[0].code}&skip=0&limit=100&include_workflow=true`, - ) - .reply(200, { entries: mockData.entry }); - const { stdout } = await runCommand(['cm:export-to-csv']); - expect(stdout).to.include('Writing entries to file'); - sandbox.restore(); - }); - }); - - describe('export-to-csv with action users', () => { - describe('Export users CSV file with flags', () => { - beforeEach(() => { - nock(cma) - .get('/v3/user?include_orgs_roles=true') - .reply(200, { user: mockData.users[0] }) - .persist() - .get(`/v3/organizations/${mockData.organizations[0].uid}/roles`) - .reply(200, { roles: mockData.roles }) - .get(`/v3/organizations/${mockData.organizations[0].uid}/share?skip=0&page=1&limit=100`) - .reply(200, { users: mockData.users }); - }); - it('Users CSV file should be successfully created', async () => { - const { stdout } = await runCommand([ - 'cm:export-to-csv', - '--action', - 'users', - '--org', - mockData.organizations[0].uid, - ]); - expect(stdout).to.include('Writing organization details to file'); - }); - }); - - describe('Export users CSV file with prompt', () => { - it('Users CSV file should be successfully created', async () => { - sandbox.stub(process, 'chdir').returns(undefined); - sandbox.stub(inquirer, 'registerPrompt').returns(undefined); - sandbox.stub(inquirer, 'prompt').returns( - Promise.resolve({ - action: 'users', - chosenOrg: mockData.organizations[0].name, - }), - ); - nock(cma) - .get(`/v3/organizations?limit=100`) - .reply(200, { organizations: mockData.organizations }) - .get('/v3/user?include_orgs_roles=true') - .reply(200, { user: mockData.users[0] }) - .persist() - .get(`/v3/organizations/${mockData.organizations[0].uid}/roles`) - .reply(200, { roles: mockData.roles }) - .get(`/v3/organizations/${mockData.organizations[0].uid}/share?skip=0&page=1&limit=100`) - .reply(200, { users: mockData.users }); - const { stdout } = await runCommand(['cm:export-to-csv']); - expect(stdout).to.include('Writing organization details to file'); - sandbox.restore(); - }); - }); - }); -}); - -describe('Testing teams support in CLI export-to-csv', () => { - beforeEach(() => { - if (!configHandler.get('authorisationType')) { - configHandler.set('authorisationType', 'BASIC'); - configHandler.set('delete', true); - } - sandbox = sinon.createSandbox(); - }); - afterEach(() => { - if (configHandler.get('delete')) { - configHandler.delete('delete'); - configHandler.delete('authorisationType'); - } - sandbox.restore(); - nock.cleanAll(); - }); - - describe('Testing Teams Command with org and team flags', () => { - it('CSV file should be created', async () => { - nock(cma) - .get(`/v3/organizations/org_uid_1_teams/teams?skip=0&limit=100&includeUserDetails=true`) - .reply(200, mockData.Teams.allTeams) - .get(`/v3/organizations/org_uid_1_teams/roles`) - .reply(200, mockData.org_roles) - .get(`/v3/roles`) - .reply(200, { roles: mockData.roless.roles }); - - const { stdout } = await runCommand([ - 'cm:export-to-csv', - '--action', - 'teams', - '--org', - 'org_uid_1_teams', - '--team-uid', - 'team_1_uid', - ]); - expect(stdout).to.include('Exporting the team with uid team_1_uid in Organisation org_uid_1_teams'); - }); - }); - - describe('Testing Teams Command with no teams', () => { - it('CSV file should be created', async () => { - nock(cma) - .get(`/v3/organizations/org_uid_1_teams/teams?skip=0&limit=100&includeUserDetails=true`) - .reply(200, mockData.Teams.allTeams) - .get(`/v3/organizations/org_uid_1_teams/roles`) - .reply(200, mockData.org_roles) - .get(`/v3/roles`) - .reply(200, { roles: mockData.roless.roles }); - - const { stdout } = await runCommand([ - 'cm:export-to-csv', - '--action', - 'teams', - '--org', - 'org_uid_1_teams', - '--team-uid', - 'team_1_uid', - ]); - expect(stdout).to.include('Exporting the team with uid team_1_uid in Organisation org_uid_1_teams'); - }); - }); - - describe('Testing Teams Command with org flag', () => { - beforeEach(() => { - nock(cma) - .get(`/v3/organizations/org_uid_1_teams/teams?skip=0&limit=100&includeUserDetails=true`) - .reply(200, mockData.Teams.allTeams) - .get(`/v3/organizations/org_uid_1_teams/roles`) - .reply(200, mockData.org_roles) - .get(`/v3/roles`) - .reply(200, { roles: mockData.roless.roles }); - }); - it('CSV file should be created', async () => { - const { stdout } = await runCommand(['cm:export-to-csv', '--action', 'teams', '--org', 'org_uid_1_teams']); - expect(stdout).to.include('Exporting the teams of Organisation org_uid_1_teams'); - }); - }); - - describe('Testing Teams Command with prompt', () => { - it('CSV file should be created', async () => { - sandbox.stub(process, 'chdir').returns(undefined); - sandbox.stub(inquirer, 'registerPrompt').returns(undefined); - sandbox.stub(inquirer, 'prompt').returns( - Promise.resolve({ - action: 'teams', - chosenOrg: mockData.organizations[2].name, - }), - ); - nock(cma) - .get('/v3/user?include_orgs_roles=true') - .reply(200, { user: mockData.users[2] }) - .get(`/v3/organizations/org_uid_1_teams/teams?skip=0&limit=100&includeUserDetails=true`) - .reply(200, mockData.Teams.allTeams) - .get(`/v3/organizations/org_uid_1_teams/roles`) - .reply(200, mockData.org_roles) - .get(`/v3/roles`) - .reply(200, { roles: mockData.roless.roles }); - - const { stdout } = await runCommand(['cm:export-to-csv']); - expect(stdout).to.include('Exporting the teams of Organisation Teams Org'); - sandbox.restore(); - }); - }); - - describe('Testing Teams Command with prompt and no stack role data', () => { - it('CSV file should be created', async () => { - sandbox.stub(process, 'chdir').returns(undefined); - sandbox.stub(inquirer, 'registerPrompt').returns(undefined); - sandbox.stub(inquirer, 'prompt').returns( - Promise.resolve({ - action: 'teams', - chosenOrg: mockData.organizations[2].name, - chooseExport: 'yes', - }), - ); - nock(cma) - .get('/v3/user?include_orgs_roles=true') - .reply(200, { user: mockData.users[2] }) - .get(`/v3/organizations/org_uid_1_teams/teams?skip=0&limit=100&includeUserDetails=true`) - .reply(200, mockData.Teams.allTeams) - .get(`/v3/organizations/org_uid_1_teams/roles`) - .reply(200, mockData.org_roles) - .get(`/v3/roles`) - .reply(200, { roles: {} }); - - const { stdout } = await runCommand(['cm:export-to-csv']); - expect(stdout).to.include('Exporting the teams of Organisation Teams Org'); - sandbox.restore(); - }); - }); -}); diff --git a/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.ts b/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.ts new file mode 100644 index 0000000000..8363b77b20 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.ts @@ -0,0 +1,55 @@ +import { expect } from 'chai'; +import ExportToCsv from '../../../src/commands/cm/export-to-csv'; + +describe('cm:export-to-csv', () => { + describe('command scaffolding', () => { + it('should have the command file in place', () => { + expect(ExportToCsv).to.exist; + expect(ExportToCsv.description).to.be.a('string'); + }); + + it('should have all expected flags defined', () => { + const flagNames = Object.keys(ExportToCsv.flags); + + expect(flagNames).to.include('action'); + expect(flagNames).to.include('alias'); + expect(flagNames).to.include('org'); + expect(flagNames).to.include('stack-name'); + expect(flagNames).to.include('stack-api-key'); + expect(flagNames).to.include('org-name'); + expect(flagNames).to.include('locale'); + expect(flagNames).to.include('content-type'); + expect(flagNames).to.include('branch'); + expect(flagNames).to.include('team-uid'); + expect(flagNames).to.include('taxonomy-uid'); + expect(flagNames).to.include('include-fallback'); + expect(flagNames).to.include('fallback-locale'); + expect(flagNames).to.include('delimiter'); + }); + + it('should have correct command description', () => { + expect(ExportToCsv.description).to.include('Export'); + expect(ExportToCsv.description).to.include('csv'); + }); + + it('should have examples defined', () => { + expect(ExportToCsv.examples).to.be.an('array'); + expect(ExportToCsv.examples.length).to.be.greaterThan(0); + }); + + it('should have correct flag defaults', () => { + const flags = ExportToCsv.flags; + + // include-fallback should default to false + expect(flags['include-fallback'].default).to.equal(false); + + // delimiter should default to comma + expect(flags['delimiter'].default).to.equal(','); + }); + + it('should have action flag with correct options', () => { + const actionFlag = ExportToCsv.flags['action'] as { options?: string[] }; + expect(actionFlag.options).to.deep.equal(['entries', 'users', 'teams', 'taxonomies']); + }); + }); +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/api-client.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/api-client.test.ts new file mode 100644 index 0000000000..915218a4ca --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/api-client.test.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { + getOrganizations, + getOrganizationsWhereUserIsAdmin, + getOrgUsers, + getOrgRoles, + getStacks, + getContentTypeCount, + getContentTypes, + getLanguages, + getEntriesCount, + getEntries, + getEnvironments, + getAllTeams, + exportOrgTeams, + getAllTaxonomies, + getAllTermsOfTaxonomy, + getTaxonomy, + createImportableCSV, +} from '../../../src/utils/api-client'; + +// API client functions are tightly coupled to the Contentstack SDK +// These tests verify the function signatures and basic structure +// Full integration testing requires actual SDK mocking or E2E tests + +describe('api-client', () => { + describe('module exports', () => { + it('should export all expected functions', () => { + expect(getOrganizations).to.be.a('function'); + expect(getOrganizationsWhereUserIsAdmin).to.be.a('function'); + expect(getOrgUsers).to.be.a('function'); + expect(getOrgRoles).to.be.a('function'); + expect(getStacks).to.be.a('function'); + expect(getContentTypeCount).to.be.a('function'); + expect(getContentTypes).to.be.a('function'); + expect(getLanguages).to.be.a('function'); + expect(getEntriesCount).to.be.a('function'); + expect(getEntries).to.be.a('function'); + expect(getEnvironments).to.be.a('function'); + expect(getAllTeams).to.be.a('function'); + expect(exportOrgTeams).to.be.a('function'); + expect(getAllTaxonomies).to.be.a('function'); + expect(getAllTermsOfTaxonomy).to.be.a('function'); + expect(getTaxonomy).to.be.a('function'); + expect(createImportableCSV).to.be.a('function'); + }); + }); + + // Note: Full functional tests for api-client require mocking the @contentstack/management SDK + // This is complex due to the SDK's internal structure. These tests are better suited for + // integration testing with a test stack or using more sophisticated mocking tools. +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/csv-writer.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/csv-writer.test.ts new file mode 100644 index 0000000000..397a2c9f25 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/csv-writer.test.ts @@ -0,0 +1,75 @@ +import { expect } from 'chai'; +import { csvParse } from '../../../src/utils/csv-writer'; + +describe('csv-writer', () => { + describe('module exports', () => { + it('should export write function', async () => { + const csvWriter = await import('../../../src/utils/csv-writer'); + expect(csvWriter.write).to.be.a('function'); + }); + + it('should export csvParse function', async () => { + const csvWriter = await import('../../../src/utils/csv-writer'); + expect(csvWriter.csvParse).to.be.a('function'); + }); + }); + + describe('csvParse', () => { + it('should parse CSV data and extract headers', async () => { + const csvData = 'name,value\ntest1,100\ntest2,200'; + const headers: string[] = []; + + const result = await csvParse(csvData, headers); + + expect(headers).to.include('name'); + expect(headers).to.include('value'); + expect(result).to.have.lengthOf(2); + expect(result[0]).to.deep.equal(['test1', '100']); + expect(result[1]).to.deep.equal(['test2', '200']); + }); + + it('should not duplicate existing headers', async () => { + const csvData = 'name,value\ntest,100'; + const headers: string[] = ['name']; // pre-existing header + + await csvParse(csvData, headers); + + // Should only have 2 headers, not 3 + expect(headers).to.have.lengthOf(2); + expect(headers.filter(h => h === 'name')).to.have.lengthOf(1); + }); + + it('should handle empty CSV', async () => { + const csvData = ''; + const headers: string[] = []; + + const result = await csvParse(csvData, headers); + + expect(result).to.have.lengthOf(0); + expect(headers).to.have.lengthOf(0); + }); + + it('should handle CSV with only headers', async () => { + const csvData = 'col1,col2,col3'; + const headers: string[] = []; + + const result = await csvParse(csvData, headers); + + expect(headers).to.deep.equal(['col1', 'col2', 'col3']); + expect(result).to.have.lengthOf(0); + }); + + it('should handle CSV with special characters', async () => { + const csvData = 'name,description\n"Test, Inc","A ""quoted"" value"'; + const headers: string[] = []; + + const result = await csvParse(csvData, headers); + + expect(headers).to.deep.equal(['name', 'description']); + expect(result).to.have.lengthOf(1); + }); + }); + + // Note: The write() function modifies process.cwd() and writes to filesystem + // These side effects are better tested via integration tests +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/data-transform.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/data-transform.test.ts new file mode 100644 index 0000000000..c0a9ef6d13 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/data-transform.test.ts @@ -0,0 +1,535 @@ +import { expect } from 'chai'; +import { + flatten, + sanitizeData, + cleanEntries, + getMappedUsers, + getMappedRoles, + determineUserOrgRole, + cleanOrgUsers, + getTeamsUserDetails, + formatTaxonomiesData, + formatTermsOfTaxonomyData, + kebabize, + getFormattedDate, + getDateTime, +} from '../../../src/utils/data-transform'; + +describe('data-transform', () => { + describe('flatten', () => { + it('should flatten a simple nested object', () => { + const input = { a: { b: { c: 1 } } }; + const result = flatten(input); + expect(result).to.deep.equal({ 'a.b.c': 1 }); + }); + + it('should flatten arrays with bracket notation', () => { + const input = { items: ['a', 'b', 'c'] }; + const result = flatten(input); + expect(result).to.deep.equal({ + 'items[0]': 'a', + 'items[1]': 'b', + 'items[2]': 'c', + }); + }); + + it('should handle empty arrays', () => { + const input = { items: [] }; + const result = flatten(input); + expect(result).to.deep.equal({ items: [] }); + }); + + it('should handle empty objects', () => { + const input = { nested: {} }; + const result = flatten(input); + expect(result).to.deep.equal({ nested: {} }); + }); + + it('should handle mixed nested structures', () => { + const input = { + user: { + name: 'John', + addresses: [{ city: 'NYC' }, { city: 'LA' }], + }, + }; + const result = flatten(input); + expect(result).to.deep.equal({ + 'user.name': 'John', + 'user.addresses[0].city': 'NYC', + 'user.addresses[1].city': 'LA', + }); + }); + + it('should handle primitive values at root', () => { + const input = { name: 'test', count: 5, active: true }; + const result = flatten(input); + expect(result).to.deep.equal({ name: 'test', count: 5, active: true }); + }); + + it('should handle null values', () => { + const input = { value: null }; + const result = flatten(input); + expect(result).to.deep.equal({ value: null }); + }); + }); + + describe('sanitizeData', () => { + it('should prefix strings starting with + to prevent CSV injection', () => { + const input = { formula: '+1234' }; + const result = sanitizeData(input); + expect(result.formula).to.equal(`"'+1234"`); + }); + + it('should prefix strings starting with = to prevent CSV injection', () => { + const input = { formula: '=SUM(A1:A10)' }; + const result = sanitizeData(input); + expect(result.formula).to.equal(`"'=SUM(A1:A10)"`); + }); + + it('should prefix strings starting with @ to prevent CSV injection', () => { + const input = { mention: '@user' }; + const result = sanitizeData(input); + expect(result.mention).to.equal(`"'@user"`); + }); + + it('should prefix strings starting with - to prevent CSV injection', () => { + const input = { value: '-100' }; + const result = sanitizeData(input); + expect(result.value).to.equal(`"'-100"`); + }); + + it('should escape double quotes in dangerous strings', () => { + const input = { formula: '=A1"test"' }; + const result = sanitizeData(input); + expect(result.formula).to.equal(`"'=A1""test"""`); + }); + + it('should convert objects to JSON strings', () => { + const input = { nested: { key: 'value' } }; + const result = sanitizeData(input); + expect(result.nested).to.equal('{"key":"value"}'); + }); + + it('should convert arrays to JSON strings', () => { + const input = { items: [1, 2, 3] }; + const result = sanitizeData(input); + expect(result.items).to.equal('[1,2,3]'); + }); + + it('should not modify safe strings', () => { + const input = { safe: 'Hello World' }; + const result = sanitizeData(input); + expect(result.safe).to.equal('Hello World'); + }); + + it('should leave null values as null (typeof object but null check)', () => { + const input = { value: null } as Record; + const result = sanitizeData(input); + // null passes the typeof object check but null !== null is false, so it stays null + // The condition is: typeof value === 'object' && value !== null + // For null: typeof null === 'object' is true, but value !== null is false (null === null) + // So the JSON.stringify branch is NOT taken for null + expect(result.value).to.be.null; + }); + }); + + describe('cleanEntries', () => { + const mockEnvironments = { + env1: 'production', + env2: 'staging', + }; + + it('should filter entries by language', () => { + const entries = [ + { uid: '1', title: 'English', locale: 'en-us' }, + { uid: '2', title: 'French', locale: 'fr-fr' }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result).to.have.lengthOf(1); + expect(result[0].uid).to.equal('1'); + }); + + it('should flatten entry data', () => { + const entries = [ + { + uid: '1', + title: 'Test', + locale: 'en-us', + nested: { field: 'value' }, + }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result[0]['nested.field']).to.equal('value'); + }); + + it('should format publish_details with environment names', () => { + const entries = [ + { + uid: '1', + title: 'Test', + locale: 'en-us', + publish_details: [ + { environment: 'env1', locale: 'en-us', time: '2024-01-01' }, + ], + }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result[0].publish_details).to.have.lengthOf(1); + expect(result[0].publish_details[0]).to.include('production'); + }); + + it('should extract workflow name', () => { + const entries = [ + { + uid: '1', + title: 'Test', + locale: 'en-us', + _workflow: { name: 'Review' }, + }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result[0]._workflow).to.equal('Review'); + }); + + it('should set content_type_uid', () => { + const entries = [ + { uid: '1', title: 'Test', locale: 'en-us' }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog_post'); + expect(result[0].content_type_uid).to.equal('blog_post'); + }); + + it('should remove SDK methods from entry', () => { + const entries = [ + { + uid: '1', + title: 'Test', + locale: 'en-us', + stackHeaders: {}, + update: () => {}, + delete: () => {}, + fetch: () => {}, + publish: () => {}, + unpublish: () => {}, + import: () => {}, + publishRequest: () => {}, + }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result[0]).to.not.have.property('stackHeaders'); + expect(result[0]).to.not.have.property('update'); + expect(result[0]).to.not.have.property('delete'); + expect(result[0]).to.not.have.property('fetch'); + }); + + it('should return empty array when no entries match locale', () => { + const entries = [ + { uid: '1', title: 'French', locale: 'fr-fr' }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result).to.have.lengthOf(0); + }); + + it('should set ACL to empty object JSON', () => { + const entries = [ + { uid: '1', title: 'Test', locale: 'en-us' }, + ] as any[]; + + const result = cleanEntries(entries, 'en-us', mockEnvironments, 'blog'); + expect(result[0].ACL).to.equal('{}'); + }); + }); + + describe('getMappedUsers', () => { + it('should map user UIDs to emails', () => { + const users = { + items: [ + { user_uid: 'uid1', email: 'user1@test.com' }, + { user_uid: 'uid2', email: 'user2@test.com' }, + ], + } as any; + + const result = getMappedUsers(users); + expect(result).to.deep.equal({ + uid1: 'user1@test.com', + uid2: 'user2@test.com', + System: 'System', + }); + }); + + it('should always include System mapping', () => { + const users = { items: [] } as any; + const result = getMappedUsers(users); + expect(result).to.have.property('System', 'System'); + }); + }); + + describe('getMappedRoles', () => { + it('should map role UIDs to names', () => { + const roles = { + items: [ + { uid: 'role1', name: 'Admin' }, + { uid: 'role2', name: 'Editor' }, + ], + } as any; + + const result = getMappedRoles(roles); + expect(result).to.deep.equal({ + role1: 'Admin', + role2: 'Editor', + }); + }); + + it('should return empty object for empty roles', () => { + const roles = { items: [] } as any; + const result = getMappedRoles(roles); + expect(result).to.deep.equal({}); + }); + }); + + describe('determineUserOrgRole', () => { + const mockRoles = { + role1: 'Admin', + role2: 'Editor', + }; + + it('should return role name from org_roles', () => { + const user = { org_roles: ['role1'] } as any; + const result = determineUserOrgRole(user, mockRoles); + expect(result).to.equal('Admin'); + }); + + it('should return Owner if user is owner', () => { + const user = { org_roles: ['role1'], is_owner: true } as any; + const result = determineUserOrgRole(user, mockRoles); + expect(result).to.equal('Owner'); + }); + + it('should return No Role if no org_roles', () => { + const user = {} as any; + const result = determineUserOrgRole(user, mockRoles); + expect(result).to.equal('No Role'); + }); + + it('should return No Role if org_roles is empty', () => { + const user = { org_roles: [] } as any; + const result = determineUserOrgRole(user, mockRoles); + expect(result).to.equal('No Role'); + }); + }); + + describe('cleanOrgUsers', () => { + it('should format org users for CSV export', () => { + const orgUsers = { + items: [ + { + email: 'user@test.com', + user_uid: 'uid1', + org_roles: ['role1'], + status: 'active', + invited_by: 'uid2', + created_at: '2024-01-15T00:00:00Z', + updated_at: '2024-01-16T00:00:00Z', + }, + ], + } as any; + + const mappedUsers = { uid1: 'user@test.com', uid2: 'inviter@test.com', System: 'System' }; + const mappedRoles = { role1: 'Admin' }; + + const result = cleanOrgUsers(orgUsers, mappedUsers, mappedRoles); + + expect(result).to.have.lengthOf(1); + expect(result[0].Email).to.equal('user@test.com'); + expect(result[0]['User UID']).to.equal('uid1'); + expect(result[0]['Organization Role']).to.equal('Admin'); + expect(result[0].Status).to.equal('active'); + expect(result[0]['Invited By']).to.equal('inviter@test.com'); + }); + + it('should use System for unknown inviter', () => { + const orgUsers = { + items: [ + { + email: 'user@test.com', + user_uid: 'uid1', + org_roles: [], + status: 'active', + invited_by: 'unknown_uid', + created_at: '2024-01-15T00:00:00Z', + updated_at: '2024-01-16T00:00:00Z', + }, + ], + } as any; + + const mappedUsers = { uid1: 'user@test.com', System: 'System' }; + const mappedRoles = {}; + + const result = cleanOrgUsers(orgUsers, mappedUsers, mappedRoles); + expect(result[0]['Invited By']).to.equal('System'); + }); + }); + + describe('getTeamsUserDetails', () => { + it('should extract all users from teams with team info', () => { + const teams = [ + { + uid: 'team1', + name: 'Team A', + users: [ + { userId: 'u1', email: 'user1@test.com', active: true }, + { userId: 'u2', email: 'user2@test.com', active: false }, + ], + }, + { + uid: 'team2', + name: 'Team B', + users: [ + { userId: 'u3', email: 'user3@test.com', orgInvitationStatus: 'pending' }, + ], + }, + ] as any[]; + + const result = getTeamsUserDetails(teams); + + expect(result).to.have.lengthOf(3); + expect(result[0]['team-name']).to.equal('Team A'); + expect(result[0]['team-uid']).to.equal('team1'); + expect(result[0]).to.not.have.property('active'); + expect(result[2]).to.not.have.property('orgInvitationStatus'); + }); + + it('should handle teams with no users', () => { + const teams = [ + { uid: 'team1', name: 'Team A', users: [] }, + { uid: 'team2', name: 'Team B' }, + ] as any[]; + + const result = getTeamsUserDetails(teams); + expect(result).to.have.lengthOf(0); + }); + }); + + describe('formatTaxonomiesData', () => { + it('should format taxonomies for CSV export', () => { + const taxonomies = [ + { uid: 'tax1', name: 'Category', description: 'Main categories' }, + { uid: 'tax2', name: 'Tags', description: '' }, + ] as any[]; + + const result = formatTaxonomiesData(taxonomies); + + expect(result).to.have.lengthOf(2); + expect(result![0]['Taxonomy UID']).to.equal('tax1'); + expect(result![0].Name).to.equal('Category'); + expect(result![0].Description).to.equal('Main categories'); + expect(result![1].Description).to.equal(''); + }); + + it('should handle missing description', () => { + const taxonomies = [ + { uid: 'tax1', name: 'Category' }, + ] as any[]; + + const result = formatTaxonomiesData(taxonomies); + expect(result![0].Description).to.equal(''); + }); + + it('should return undefined for empty array', () => { + const result = formatTaxonomiesData([]); + expect(result).to.be.undefined; + }); + + it('should return undefined for undefined input', () => { + const result = formatTaxonomiesData(undefined as any); + expect(result).to.be.undefined; + }); + }); + + describe('formatTermsOfTaxonomyData', () => { + it('should format terms for CSV export', () => { + const terms = [ + { uid: 'term1', name: 'Tech', parent_uid: null, depth: 0 }, + { uid: 'term2', name: 'Software', parent_uid: 'term1', depth: 1 }, + ] as any[]; + + const result = formatTermsOfTaxonomyData(terms, 'tax1'); + + expect(result).to.have.lengthOf(2); + expect(result![0]['Taxonomy UID']).to.equal('tax1'); + expect(result![0].UID).to.equal('term1'); + expect(result![0].Name).to.equal('Tech'); + expect(result![0]['Parent UID']).to.be.null; + expect(result![0].Depth).to.equal(0); + expect(result![1]['Parent UID']).to.equal('term1'); + }); + + it('should return undefined for empty array', () => { + const result = formatTermsOfTaxonomyData([], 'tax1'); + expect(result).to.be.undefined; + }); + + it('should return undefined for undefined input', () => { + const result = formatTermsOfTaxonomyData(undefined as any, 'tax1'); + expect(result).to.be.undefined; + }); + }); + + describe('kebabize', () => { + it('should convert spaces to hyphens and lowercase', () => { + expect(kebabize('Hello World')).to.equal('hello-world'); + }); + + it('should handle single word', () => { + expect(kebabize('Hello')).to.equal('hello'); + }); + + it('should handle multiple spaces', () => { + expect(kebabize('One Two Three')).to.equal('one-two-three'); + }); + + it('should handle already lowercase', () => { + expect(kebabize('already lowercase')).to.equal('already-lowercase'); + }); + + it('should handle empty string', () => { + expect(kebabize('')).to.equal(''); + }); + }); + + describe('getFormattedDate', () => { + it('should format Date object to MM/DD/YYYY', () => { + const date = new Date('2024-01-15T12:00:00Z'); + const result = getFormattedDate(date); + expect(result).to.match(/^\d{2}\/\d{2}\/\d{4}$/); + }); + + it('should format date string to MM/DD/YYYY', () => { + const result = getFormattedDate('2024-01-15T12:00:00Z'); + expect(result).to.match(/^\d{2}\/\d{2}\/\d{4}$/); + }); + + it('should pad single digit months and days', () => { + const date = new Date('2024-01-05T12:00:00Z'); + const result = getFormattedDate(date); + expect(result).to.include('/05/'); + }); + }); + + describe('getDateTime', () => { + it('should return formatted date-time string', () => { + const result = getDateTime(); + // Format should be like "1-15-2024_12:00:00PM" or similar locale-dependent + expect(result).to.be.a('string'); + expect(result).to.include('_'); + expect(result).to.include('-'); + }); + }); +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/error-handler.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/error-handler.test.ts new file mode 100644 index 0000000000..3f775e3c0c --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/error-handler.test.ts @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + formatError, + wait, +} from '../../../src/utils/error-handler'; + +describe('error-handler', () => { + describe('formatError', () => { + it('should handle string errors', () => { + const result = formatError('Simple error message'); + expect(result).to.equal('Simple error message'); + }); + + it('should handle JSON string errors', () => { + const jsonError = JSON.stringify({ errorMessage: 'JSON error' }); + const result = formatError(jsonError); + expect(result).to.equal('JSON error'); + }); + + it('should handle error objects with errorMessage', () => { + const error = { errorMessage: 'Error message from API' }; + const result = formatError(error); + expect(result).to.equal('Error message from API'); + }); + + it('should handle error objects with error_message', () => { + const error = { error_message: 'Error message with underscore' }; + const result = formatError(error); + expect(result).to.equal('Error message with underscore'); + }); + + it('should handle error objects with message', () => { + const error = { message: 'Standard error message' }; + const result = formatError(error); + expect(result).to.equal('Standard error message'); + }); + + it('should handle Error objects with JSON message', () => { + const error = new Error(JSON.stringify({ errorMessage: 'Nested JSON error' })); + const result = formatError(error); + expect(result).to.equal('Nested JSON error'); + }); + + it('should handle Error objects with plain message', () => { + const error = new Error('Plain error message'); + const result = formatError(error); + expect(result).to.equal('Plain error message'); + }); + + it('should append authorization error details', () => { + const error = { + errorMessage: 'Unauthorized', + errors: { authorization: 'is invalid' }, + }; + const result = formatError(error); + expect(result).to.include('Management Token'); + expect(result).to.include('is invalid'); + }); + + it('should append api_key error details', () => { + const error = { + errorMessage: 'Invalid request', + errors: { api_key: 'is required' }, + }; + const result = formatError(error); + expect(result).to.include('Stack API key'); + expect(result).to.include('is required'); + }); + + it('should append uid error details', () => { + const error = { + errorMessage: 'Not found', + errors: { uid: 'does not exist' }, + }; + const result = formatError(error); + expect(result).to.include('Content Type'); + expect(result).to.include('does not exist'); + }); + + it('should append access_token error details', () => { + const error = { + errorMessage: 'Unauthorized', + errors: { access_token: 'is expired' }, + }; + const result = formatError(error); + expect(result).to.include('Delivery Token'); + expect(result).to.include('is expired'); + }); + + it('should handle multiple error fields', () => { + const error = { + errorMessage: 'Multiple errors', + errors: { + authorization: 'is invalid', + api_key: 'is missing', + }, + }; + const result = formatError(error); + expect(result).to.include('Management Token'); + expect(result).to.include('Stack API key'); + }); + + it('should handle unknown error fields', () => { + const error = { + errorMessage: 'Unknown error', + errors: { custom_field: 'has issue' }, + }; + const result = formatError(error); + expect(result).to.include('custom_field'); + expect(result).to.include('has issue'); + }); + + it('should handle null error', () => { + const result = formatError(null); + expect(result).to.equal('null'); + }); + + it('should handle undefined error', () => { + const result = formatError(undefined); + expect(result).to.equal('undefined'); + }); + + it('should handle empty object', () => { + const result = formatError({}); + expect(result).to.be.a('string'); + }); + }); + + describe('wait', () => { + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should resolve after specified time', async () => { + let resolved = false; + const waitPromise = wait(1000).then(() => { + resolved = true; + }); + + expect(resolved).to.be.false; + + clock.tick(999); + await Promise.resolve(); // Allow microtasks to run + expect(resolved).to.be.false; + + clock.tick(1); + await waitPromise; + expect(resolved).to.be.true; + }); + + it('should resolve immediately for 0ms', async () => { + let resolved = false; + const waitPromise = wait(0).then(() => { + resolved = true; + }); + + clock.tick(0); + await waitPromise; + expect(resolved).to.be.true; + }); + }); + + // Note: handleErrorMsg, handleTaxonomyErrorMsg, and exitProgram call process.exit() + // Testing these would require stubbing process.exit which can be complex. + // For coverage purposes, we test formatError and wait which cover most logic. +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/interactive.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/interactive.test.ts new file mode 100644 index 0000000000..e5518bb38e --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/interactive.test.ts @@ -0,0 +1,32 @@ +import { expect } from 'chai'; +import { + startupQuestions, + chooseOrganization, + chooseStack, + chooseBranch, + chooseContentType, + chooseInMemContentTypes, + chooseLanguage, + chooseFallbackOptions, + promptContinueExport, +} from '../../../src/utils/interactive'; + +describe('interactive', () => { + describe('module exports', () => { + it('should export all interactive functions', () => { + expect(startupQuestions).to.be.a('function'); + expect(chooseOrganization).to.be.a('function'); + expect(chooseStack).to.be.a('function'); + expect(chooseBranch).to.be.a('function'); + expect(chooseContentType).to.be.a('function'); + expect(chooseInMemContentTypes).to.be.a('function'); + expect(chooseLanguage).to.be.a('function'); + expect(chooseFallbackOptions).to.be.a('function'); + expect(promptContinueExport).to.be.a('function'); + }); + }); + + // Note: Interactive functions use inquirer.prompt() which requires + // user input simulation. These are better tested via integration tests + // or using tools like @inquirer/testing. +}); diff --git a/packages/contentstack-export-to-csv/test/unit/utils/teams-export.test.ts b/packages/contentstack-export-to-csv/test/unit/utils/teams-export.test.ts new file mode 100644 index 0000000000..c7d03a9464 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/unit/utils/teams-export.test.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { + exportTeams, + getTeamsDetail, + exportRoleMappings, + mapRoleWithTeams, +} from '../../../src/utils/teams-export'; + +describe('teams-export', () => { + describe('module exports', () => { + it('should export all team export functions', () => { + expect(exportTeams).to.be.a('function'); + expect(getTeamsDetail).to.be.a('function'); + expect(exportRoleMappings).to.be.a('function'); + expect(mapRoleWithTeams).to.be.a('function'); + }); + }); + + // Note: Team export functions interact with the Contentstack SDK and filesystem + // These are better tested via integration tests with proper SDK mocking +}); diff --git a/packages/contentstack-export-to-csv/test/util/common-utils.test.js b/packages/contentstack-export-to-csv/test/util/common-utils.test.js deleted file mode 100644 index 033694b28e..0000000000 --- a/packages/contentstack-export-to-csv/test/util/common-utils.test.js +++ /dev/null @@ -1,56 +0,0 @@ -const { expect } = require('chai'); -const inquirer = require('inquirer'); -const sinon = require('sinon'); -const { configHandler, managementSDKClient } = require('@contentstack/cli-utilities'); -const mockData = require('../mock-data/common.mock.json'); -const { getStacks, chooseBranch } = require('../../src/util/index'); -const nock = require('nock'); -const regionConfig = configHandler.get('region') || {}; -const cma = regionConfig.cma || 'https://api.contentstack.io/v3'; - -describe('common utils', () => { - let managementSdk; - let sandbox; - - beforeEach(async () => { - managementSdk = await managementSDKClient({ - host: cma.replace('https://', ''), - }); - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - nock.cleanAll(); - }); - - describe('getStacks', () => { - it('should return a list of stacks for a given organization', async () => { - sandbox.stub(inquirer, 'prompt').resolves({ - stack: mockData.stacks[0].name, - }); - - nock(cma) - .get(`/v3/stacks?query={"org_uid":"${mockData.organizations[0].uid}"}`) - .reply(200, { stacks: mockData.stacks }); - - const result = await getStacks(managementSdk, mockData.organizations[0].uid); - - expect(result).to.be.an('object'); - }); - }); - - describe('chooseBranch', () => { - describe('choose branch from list of branch', () => { - beforeEach(() => { - sandbox.stub(inquirer, 'prompt').returns({ - branch: mockData.branch.uid, - }); - }); - it('Returns list of stacks', async () => { - const { branch } = await chooseBranch([mockData.branch]); - expect(branch).to.equal(mockData.branch.uid); - }); - }); - }); -}); diff --git a/packages/contentstack-export-to-csv/test/util/index.test.js b/packages/contentstack-export-to-csv/test/util/index.test.js deleted file mode 100644 index 80bcba2bfc..0000000000 --- a/packages/contentstack-export-to-csv/test/util/index.test.js +++ /dev/null @@ -1,204 +0,0 @@ - -const { expect } = require('chai'); -const inquirer = require('inquirer'); -const { - chooseStack, - getEntries, - getEnvironments, - chooseLanguage, - getOrgUsers, - getOrgRoles, - getMappedUsers, - getMappedRoles, - cleanEntries, - determineUserOrgRole, -} = require('../../src/util/index'); -const sinon = require('sinon'); -const util = require('../../src/util'); - -describe('Test Util functions', () => { - let managementAPIClientMock; - - beforeEach(() => { - managementAPIClientMock = { - organization: sinon.stub(), - getUser: sinon.stub(), - stack: sinon.stub(), - contentType: sinon.stub(), - locale: sinon.stub(), - environment: sinon.stub(), - }; - }); - - describe('Choose Stack', () => { - it('should return chosen stack', async () => { - managementAPIClientMock.stack.returns({ - query: () => ({ - find: async () => ({ items: [{ name: 'Stack1', api_key: 'key1' }] }), - }), - }); - inquirer.prompt = async () => ({ chosenStack: 'Stack1' }); - const result = await chooseStack(managementAPIClientMock, 'orgUid'); - expect(result).to.deep.equal({ name: 'Stack1', apiKey: 'key1' }); - }); - }); - - describe('Get Entries', () => { - it('should return entries', async () => { - managementAPIClientMock.contentType.returns({ - entry: () => ({ - query: () => ({ - find: async () => [{ title: 'Entry1' }, { title: 'Entry2' }], - }), - }), - }); - - const result = await getEntries(managementAPIClientMock, 'contentTypeUid', 'en', 0, 100); - expect(result).to.deep.equal([{ title: 'Entry1' }, { title: 'Entry2' }]); - }); - }); - - describe('Get Environments', () => { - it('should return environments', async () => { - managementAPIClientMock.environment.returns({ - query: () => ({ - find: async () => ({ items: [{ uid: 'env1', name: 'Environment1' }] }), - }), - }); - - const result = await getEnvironments(managementAPIClientMock); - expect(result).to.deep.equal({ env1: 'Environment1' }); - }); - }); - - describe('Choose Language', () => { - it('should return chosen language', async () => { - managementAPIClientMock.locale.returns({ - query: () => ({ - find: async () => ({ items: [{ name: 'English', code: 'en' }] }), - }), - }); - inquirer.prompt = async () => ({ chosenLanguage: 'English' }); - - const result = await chooseLanguage(managementAPIClientMock); - expect(result).to.deep.equal({ name: 'English', code: 'en' }); - }); - }); - - describe('Get Org Users', () => { - it('should return organization users', async () => { - managementAPIClientMock.getUser.returns(Promise.resolve({ - organizations: [{ uid: 'orgUid', is_owner: true }], - })); - managementAPIClientMock.organization.returns({ - getInvitations: async () => ({ items: [{ user_uid: 'user1', email: 'user1@example.com' }] }), - }); - const result = await getOrgUsers(managementAPIClientMock, 'orgUid'); - expect(result).to.deep.equal({ items: [{ user_uid: 'user1', email: 'user1@example.com' }] }); - }); - - it('should return an error when user is not an admin of the organization', async () => { - managementAPIClientMock.getUser.returns(Promise.resolve({ - organizations: [{ uid: 'orgUid', org_roles: [] }], - })); - try { await getOrgUsers(managementAPIClientMock, 'orgUid'); } - catch (error) { - expect(error.message).to.include('Unable to export data. Make sure you\'re an admin or owner of this organization'); - } - }); - }); - - describe('Get Org Roles', () => { - it('should return organization roles', async () => { - managementAPIClientMock.getUser.returns(Promise.resolve({ - organizations: [{ uid: 'orgUid', is_owner: true }], - })); - managementAPIClientMock.organization.returns({ - roles: async () => ({ items: [{ uid: 'role1', name: 'Admin' }] }), - }); - - const result = await getOrgRoles(managementAPIClientMock, 'orgUid'); - expect(result).to.deep.equal({ items: [{ uid: 'role1', name: 'Admin' }] }); - }); - - it('should return an error when user is not an admin of the organization', async () => { - managementAPIClientMock.getUser.returns(Promise.resolve({ - organizations: [{ uid: 'orgUid', org_roles: [] }], - })); - try { await getOrgRoles(managementAPIClientMock, 'orgUid'); } - catch (error) { - expect(error.message).to.include('Unable to export data. Make sure you\'re an admin or owner of this organization'); - } - }); - }); - - describe('Get Mapped Users', () => { - it('should return mapped users', () => { - const users = { items: [{ user_uid: 'user1', email: 'user1@example.com' }] }; - const result = getMappedUsers(users); - expect(result).to.deep.equal({ user1: 'user1@example.com', System: 'System' }); - }); - }); - - describe('Get Mapped Roles', () => { - it('should return mapped roles', () => { - const roles = { items: [{ uid: 'role1', name: 'Admin' }] }; - const result = getMappedRoles(roles); - expect(result).to.deep.equal({ role1: 'Admin' }); - }); - }); - - describe('Clean Entries', () => { - it('should filter and format entries correctly', () => { - const entries = [ - { - locale: 'en', - publish_details: [{ environment: 'env1', locale: 'en', time: '2021-01-01' }], - _workflow: { name: 'Workflow1' }, - otherField: 'value', - }, - ]; - const environments = { env1: 'Production' }; - const contentTypeUid = 'contentTypeUid'; - - const result = cleanEntries(entries, 'en', environments, contentTypeUid); - expect(result).to.deep.equal([ - { - locale: 'en', - publish_details: ['["Production","en","2021-01-01"]'], - _workflow: 'Workflow1', - ACL: '{}', - content_type_uid: contentTypeUid, - otherField: 'value', - }, - ]); - }); - }); - - describe('Get DateTime', () => { - it('should return a string', () => { - expect(util.getDateTime()).to.be.a('string'); - }); - }); - - describe('Determine User Organization Role', () => { - it('should return "Owner" if the user is an owner', () => { - const user = { is_owner: true }; - const result = determineUserOrgRole(user, {}); - expect(result).to.equal('Owner'); - }); - - it('should return role name based on org roles', () => { - const user = { org_roles: ['role1'] }; - const roles = { role1: 'Admin' }; - const result = determineUserOrgRole(user, roles); - expect(result).to.equal('Admin'); - }); - - it('should return "No Role" if there are no roles', () => { - const user = { org_roles: [] }; - const result = determineUserOrgRole(user, {}); - expect(result).to.equal('No Role'); - }); - }); -}); diff --git a/packages/contentstack-export-to-csv/tsconfig.json b/packages/contentstack-export-to-csv/tsconfig.json new file mode 100644 index 0000000000..23137623ce --- /dev/null +++ b/packages/contentstack-export-to-csv/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "declaration": true, + "importHelpers": true, + "module": "commonjs", + "outDir": "lib", + "rootDir": "src", + "strict": true, + "target": "es2017", + "esModuleInterop": true, + "composite": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "lib": [ + "ES2019", + "es2020.promise" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/contentstack-export/README.md b/packages/contentstack-export/README.md index 2b7706c9b8..cac9da9931 100755 --- a/packages/contentstack-export/README.md +++ b/packages/contentstack-export/README.md @@ -48,7 +48,7 @@ $ npm install -g @contentstack/cli-cm-export $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-export/1.22.2 darwin-arm64 node-v22.13.0 +@contentstack/cli-cm-export/1.23.1 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-import-setup/README.md b/packages/contentstack-import-setup/README.md index 2d809be65d..9cd591edbe 100644 --- a/packages/contentstack-import-setup/README.md +++ b/packages/contentstack-import-setup/README.md @@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import-setup $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-import-setup/1.7.2 darwin-arm64 node-v22.13.1 +@contentstack/cli-cm-import-setup/1.7.3 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-import/README.md b/packages/contentstack-import/README.md index 8265bc6bea..8a97a7731f 100644 --- a/packages/contentstack-import/README.md +++ b/packages/contentstack-import/README.md @@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-import/1.30.2 darwin-arm64 node-v22.13.1 +@contentstack/cli-cm-import/1.31.2 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-migrate-rte/README.md b/packages/contentstack-migrate-rte/README.md index 2b7162f90d..0333bba660 100644 --- a/packages/contentstack-migrate-rte/README.md +++ b/packages/contentstack-migrate-rte/README.md @@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-migrate-rte $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-migrate-rte/1.6.3 darwin-arm64 node-v22.14.0 +@contentstack/cli-cm-migrate-rte/1.6.4 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-migration/README.md b/packages/contentstack-migration/README.md index 61c19956a8..bc918ec3c4 100644 --- a/packages/contentstack-migration/README.md +++ b/packages/contentstack-migration/README.md @@ -21,7 +21,7 @@ $ npm install -g @contentstack/cli-migration $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-migration/1.10.2 darwin-arm64 node-v22.14.0 +@contentstack/cli-migration/1.10.3 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 5d52f3251c..3121de5259 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli $ csdx COMMAND running command... $ csdx (--version|-v) -@contentstack/cli/1.54.0 darwin-arm64 node-v22.14.0 +@contentstack/cli/1.56.0 darwin-arm64 node-v24.13.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND @@ -2271,6 +2271,9 @@ FLAGS DESCRIPTION Export entries, taxonomies, terms or organization users to csv using this command +ALIASES + $ csdx cm:export-to-csv + EXAMPLES $ csdx cm:export-to-csv @@ -2278,94 +2281,52 @@ EXAMPLES Exporting entries to CSV - $ csdx cm:export-to-csv --action --locale --alias --content-type + $ csdx cm:export-to-csv --action entries --locale --alias --content-type - Exporting entries to CSV with stack name provided and branch name provided + Exporting entries to CSV with stack name and branch - $ csdx cm:export-to-csv --action --locale --alias --content-type --stack-name --branch + $ csdx cm:export-to-csv --action entries --locale --alias --content-type --stack-name --branch Exporting organization users to CSV - $ csdx cm:export-to-csv --action --org - - - - Exporting organization users to CSV with organization name provided - - $ csdx cm:export-to-csv --action --org --org-name + $ csdx cm:export-to-csv --action users --org Exporting organization teams to CSV - $ csdx cm:export-to-csv --action - - - - Exporting organization teams to CSV with org UID - - $ csdx cm:export-to-csv --action --org - - - - Exporting organization teams to CSV with team UID - - $ csdx cm:export-to-csv --action --team-uid - - - - Exporting organization teams to CSV with org UID and team UID - - $ csdx cm:export-to-csv --action --org --team-uid - - - - Exporting organization teams to CSV with org UID and team UID - - $ csdx cm:export-to-csv --action --org --team-uid --org-name - - - - Exporting taxonomies and related terms to a .CSV file with the provided taxonomy UID - - $ csdx cm:export-to-csv --action --alias --taxonomy-uid - - - - Exporting taxonomies and respective terms to a .CSV file - - $ csdx cm:export-to-csv --action --alias + $ csdx cm:export-to-csv --action teams --org - Exporting taxonomies and respective terms to a .CSV file with a delimiter + Exporting teams with specific team UID - $ csdx cm:export-to-csv --action --alias --delimiter + $ csdx cm:export-to-csv --action teams --org --team-uid - Exporting taxonomies with specific locale + Exporting taxonomies to CSV - $ csdx cm:export-to-csv --action --alias --locale + $ csdx cm:export-to-csv --action taxonomies --alias - Exporting taxonomies with fallback locale support + Exporting specific taxonomy with locale - $ csdx cm:export-to-csv --action --alias --locale --include-fallback + $ csdx cm:export-to-csv --action taxonomies --alias --taxonomy-uid --locale - Exporting taxonomies with custom fallback locale + Exporting taxonomies with fallback locale - $ csdx cm:export-to-csv --action --alias --locale --include-fallback --fallback-locale + $ csdx cm:export-to-csv --action taxonomies --alias --locale --include-fallback --fallback-locale ``` -_See code: [@contentstack/cli-cm-export-to-csv](https://github.com/contentstack/cli/blob/main/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js)_ +_See code: [@contentstack/cli-cm-export-to-csv](https://github.com/contentstack/cli/blob/main/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.ts)_ ## `csdx cm:stacks:import [-c ] [-k ] [-d ] [-a ] [--module ] [--backup-dir ] [--branch ] [--import-webhook-status disable|current]` @@ -2864,7 +2825,7 @@ EXAMPLES $ csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias --destination-management-token-alias --type ``` -_See code: [@contentstack/cli-cm-clone](https://github.com/contentstack/cli/blob/main/packages/contentstack-clone/src/commands/cm/stacks/clone.js)_ +_See code: [@contentstack/cli-cm-clone](https://github.com/contentstack/cli/blob/main/packages/contentstack-clone/src/commands/cm/stacks/clone.ts)_ ## `csdx cm:stacks:export [-c ] [-k ] [-d ] [-a ] [--module ] [--content-types ] [--branch ] [--secured-assets]` @@ -3868,7 +3829,7 @@ DESCRIPTION Display help for csdx. ``` -_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.36/src/commands/help.ts)_ +_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.37/src/commands/help.ts)_ ## `csdx launch` @@ -4011,7 +3972,8 @@ USAGE $ csdx launch:functions [-p ] [-d ] FLAGS - -d, --data-dir= [default: /cli/packages/contentstack] Current working directory + -d, --data-dir= [default: /Users/netraj.patel/projects/contentstack/cli/packages/contentstack] Current working + directory -p, --port= [default: 3000] Port number DESCRIPTION @@ -4187,7 +4149,7 @@ EXAMPLES $ csdx plugins ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/index.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/index.ts)_ ## `csdx plugins:add PLUGIN` @@ -4261,7 +4223,7 @@ EXAMPLES $ csdx plugins:inspect myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/inspect.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/inspect.ts)_ ## `csdx plugins:install PLUGIN` @@ -4310,7 +4272,7 @@ EXAMPLES $ csdx plugins:install someuser/someplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/install.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/install.ts)_ ## `csdx plugins:link PATH` @@ -4341,7 +4303,7 @@ EXAMPLES $ csdx plugins:link myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/link.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/link.ts)_ ## `csdx plugins:remove [PLUGIN]` @@ -4382,7 +4344,7 @@ FLAGS --reinstall Reinstall all plugins after uninstalling. ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/reset.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/reset.ts)_ ## `csdx plugins:uninstall [PLUGIN]` @@ -4410,7 +4372,7 @@ EXAMPLES $ csdx plugins:uninstall myplugin ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/uninstall.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/uninstall.ts)_ ## `csdx plugins:unlink [PLUGIN]` @@ -4454,7 +4416,7 @@ DESCRIPTION Update installed plugins. ``` -_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/update.ts)_ +_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.55/src/commands/plugins/update.ts)_ ## `csdx tokens` diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index 8425a7543a..03b31c24a4 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -30,7 +30,7 @@ "@contentstack/cli-cm-branches": "~1.6.3", "@contentstack/cli-cm-bulk-publish": "~1.10.6", "@contentstack/cli-cm-clone": "~1.20.0", - "@contentstack/cli-cm-export-to-csv": "~1.10.3", + "@contentstack/cli-cm-export-to-csv": "~1.11.0", "@contentstack/cli-cm-import-setup": "~1.7.3", "@contentstack/cli-cm-migrate-rte": "~1.6.4", "@contentstack/cli-cm-seed": "~1.14.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e287ddfa2..5d356d2cb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: '@contentstack/cli-cm-bulk-publish': ~1.10.6 '@contentstack/cli-cm-clone': ~1.20.0 '@contentstack/cli-cm-export': ~1.23.1 - '@contentstack/cli-cm-export-to-csv': ~1.10.3 + '@contentstack/cli-cm-export-to-csv': ~1.11.0 '@contentstack/cli-cm-import': ~1.31.2 '@contentstack/cli-cm-import-setup': ~1.7.3 '@contentstack/cli-cm-migrate-rte': ~1.6.4 @@ -631,38 +631,52 @@ importers: '@oclif/plugin-help': ^6.2.32 '@oclif/test': ^4.1.13 '@types/chai': ^4.3.20 + '@types/inquirer': ^9.0.8 + '@types/mkdirp': ^1.0.2 '@types/mocha': ^10.0.10 + '@types/node': ^20.17.50 chai: ^4.5.0 - debug: ^4.4.1 - eslint: ^7.32.0 - eslint-config-oclif: ^6.0.15 + eslint: ^8.57.1 + eslint-config-oclif: ^6.0.62 + eslint-config-oclif-typescript: ^3.1.14 fast-csv: ^4.3.6 inquirer: 8.2.7 inquirer-checkbox-plus-prompt: 1.4.2 mkdirp: ^3.0.1 mocha: ^10.8.2 + nock: ^13.5.6 nyc: ^15.1.0 oclif: ^4.17.46 + sinon: ^19.0.5 + ts-node: ^10.9.2 + typescript: ^5.8.3 dependencies: '@contentstack/cli-command': link:../contentstack-command '@contentstack/cli-utilities': link:../contentstack-utilities '@oclif/core': 4.8.0 '@oclif/plugin-help': 6.2.37 fast-csv: 4.3.6 - inquirer: 8.2.7 + inquirer: 8.2.7_@types+node@20.19.30 inquirer-checkbox-plus-prompt: 1.4.2_inquirer@8.2.7 mkdirp: 3.0.1 devDependencies: '@oclif/test': 4.1.16_@oclif+core@4.8.0 '@types/chai': 4.3.20 + '@types/inquirer': 9.0.9 + '@types/mkdirp': 1.0.2 '@types/mocha': 10.0.10 + '@types/node': 20.19.30 chai: 4.5.0 - debug: 4.4.3 - eslint: 7.32.0 - eslint-config-oclif: 6.0.133_eslint@7.32.0 + eslint: 8.57.1 + eslint-config-oclif: 6.0.133_k2rwabtyo525wwqr6566umnmhy + eslint-config-oclif-typescript: 3.1.14_k2rwabtyo525wwqr6566umnmhy mocha: 10.8.2 + nock: 13.5.6 nyc: 15.1.0 - oclif: 4.22.68 + oclif: 4.22.68_@types+node@20.19.30 + sinon: 19.0.5 + ts-node: 10.9.2_l6myzfrhl2awiop7nr5zagmuta + typescript: 5.9.3 packages/contentstack-import: specifiers: @@ -2632,16 +2646,6 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils/4.9.1_eslint@7.32.0: - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 7.32.0 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/eslint-utils/4.9.1_eslint@8.57.1: resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2667,19 +2671,6 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/compat/1.4.1_eslint@7.32.0: - resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.40 || 9 - peerDependenciesMeta: - eslint: - optional: true - dependencies: - '@eslint/core': 0.17.0 - eslint: 7.32.0 - dev: true - /@eslint/compat/1.4.1_eslint@8.57.1: resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3239,7 +3230,6 @@ packages: '@types/node': 20.19.30 chardet: 2.1.1 iconv-lite: 0.7.2 - dev: true /@inquirer/figures/1.0.15: resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} @@ -4490,6 +4480,12 @@ packages: '@sinonjs/commons': 3.0.1 dev: true + /@sinonjs/fake-timers/13.0.5: + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: true + /@sinonjs/fake-timers/15.1.0: resolution: {integrity: sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==} dependencies: @@ -5029,23 +5025,6 @@ packages: - typescript dev: true - /@stylistic/eslint-plugin/3.1.0_eslint@7.32.0: - resolution: {integrity: sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.40.0' - dependencies: - '@typescript-eslint/utils': 8.53.1_eslint@7.32.0 - eslint: 7.32.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - estraverse: 5.3.0 - picomatch: 4.0.3 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@stylistic/eslint-plugin/3.1.0_eslint@8.57.1: resolution: {integrity: sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5080,21 +5059,6 @@ packages: - typescript dev: true - /@stylistic/eslint-plugin/5.7.0_eslint@7.32.0: - resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=9.0.0' - dependencies: - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - '@typescript-eslint/types': 8.53.1 - eslint: 7.32.0 - eslint-visitor-keys: 5.0.0 - espree: 11.1.0 - estraverse: 5.3.0 - picomatch: 4.0.3 - dev: true - /@stylistic/eslint-plugin/5.7.0_eslint@8.57.1: resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5592,28 +5556,6 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin/8.53.1_7f3ev5f4rgmdwp43lafel2uhym: - resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.53.1 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.53.1_eslint@7.32.0 - '@typescript-eslint/scope-manager': 8.53.1 - '@typescript-eslint/type-utils': 8.53.1_eslint@7.32.0 - '@typescript-eslint/utils': 8.53.1_eslint@7.32.0 - '@typescript-eslint/visitor-keys': 8.53.1 - eslint: 7.32.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.4.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/eslint-plugin/8.53.1_pakcvjk7ygtc4zwskulha7s734: resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5720,23 +5662,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/8.53.1_eslint@7.32.0: - resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - dependencies: - '@typescript-eslint/scope-manager': 8.53.1 - '@typescript-eslint/types': 8.53.1 - '@typescript-eslint/typescript-estree': 8.53.1 - '@typescript-eslint/visitor-keys': 8.53.1 - debug: 4.4.3 - eslint: 7.32.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser/8.53.1_eslint@8.57.1: resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5948,23 +5873,6 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils/8.53.1_eslint@7.32.0: - resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - dependencies: - '@typescript-eslint/types': 8.53.1 - '@typescript-eslint/typescript-estree': 8.53.1 - '@typescript-eslint/utils': 8.53.1_eslint@7.32.0 - debug: 4.4.3 - eslint: 7.32.0 - ts-api-utils: 2.4.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/type-utils/8.53.1_eslint@8.57.1: resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6295,22 +6203,6 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/8.53.1_eslint@7.32.0: - resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - dependencies: - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - '@typescript-eslint/scope-manager': 8.53.1 - '@typescript-eslint/types': 8.53.1 - '@typescript-eslint/typescript-estree': 8.53.1 - eslint: 7.32.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/utils/8.53.1_eslint@8.57.1: resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8095,6 +7987,11 @@ packages: engines: {node: '>=0.3.1'} dev: true + /diff/7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + dev: true + /diff/8.0.3: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} @@ -8436,16 +8333,6 @@ packages: source-map: 0.6.1 dev: false - /eslint-compat-utils/0.5.1_eslint@7.32.0: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - dependencies: - eslint: 7.32.0 - semver: 7.7.3 - dev: true - /eslint-compat-utils/0.5.1_eslint@8.57.1: resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} @@ -8506,18 +8393,6 @@ packages: - vue-eslint-parser dev: true - /eslint-config-oclif/5.2.2_eslint@7.32.0: - resolution: {integrity: sha512-NNTyyolSmKJicgxtoWZ/hoy2Rw56WIoWCFxgnBkXqDgi9qPKMwZs2Nx2b6SHLJvCiWWhZhWr5V46CFPo3PSPag==} - engines: {node: '>=18.0.0'} - dependencies: - eslint-config-xo-space: 0.35.0_eslint@7.32.0 - eslint-plugin-mocha: 10.5.0_eslint@7.32.0 - eslint-plugin-n: 15.7.0_eslint@7.32.0 - eslint-plugin-unicorn: 48.0.1_eslint@7.32.0 - transitivePeerDependencies: - - eslint - dev: true - /eslint-config-oclif/5.2.2_eslint@8.57.1: resolution: {integrity: sha512-NNTyyolSmKJicgxtoWZ/hoy2Rw56WIoWCFxgnBkXqDgi9qPKMwZs2Nx2b6SHLJvCiWWhZhWr5V46CFPo3PSPag==} engines: {node: '>=18.0.0'} @@ -8559,35 +8434,6 @@ packages: - typescript dev: true - /eslint-config-oclif/6.0.133_eslint@7.32.0: - resolution: {integrity: sha512-cCa/K8JwanplkBku4wiRuWAZ4VF6z0V23Y0wC5Sjdf2x7ZJsZ5vqg6/OJqKELXc/0TzfoLX56luHS/IIeYdnag==} - engines: {node: '>=18.18.0'} - dependencies: - '@eslint/compat': 1.4.1_eslint@7.32.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 - '@stylistic/eslint-plugin': 3.1.0_eslint@7.32.0 - '@typescript-eslint/eslint-plugin': 8.53.1_7f3ev5f4rgmdwp43lafel2uhym - '@typescript-eslint/parser': 8.53.1_eslint@7.32.0 - eslint-config-oclif: 5.2.2_eslint@7.32.0 - eslint-config-xo: 0.49.0_eslint@7.32.0 - eslint-config-xo-space: 0.35.0_eslint@7.32.0 - eslint-import-resolver-typescript: 3.10.1_euuv2s2m4azrqak6tzap5kwzai - eslint-plugin-import: 2.32.0_6icihbrq4zzyh6aatylqrjvwgq - eslint-plugin-jsdoc: 50.8.0_eslint@7.32.0 - eslint-plugin-mocha: 10.5.0_eslint@7.32.0 - eslint-plugin-n: 17.23.2_eslint@7.32.0 - eslint-plugin-perfectionist: 4.15.1_eslint@7.32.0 - eslint-plugin-unicorn: 56.0.1_eslint@7.32.0 - typescript-eslint: 8.53.1_eslint@7.32.0 - transitivePeerDependencies: - - eslint - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - - typescript - dev: true - /eslint-config-oclif/6.0.133_eslint@8.57.1: resolution: {integrity: sha512-cCa/K8JwanplkBku4wiRuWAZ4VF6z0V23Y0wC5Sjdf2x7ZJsZ5vqg6/OJqKELXc/0TzfoLX56luHS/IIeYdnag==} engines: {node: '>=18.18.0'} @@ -8646,16 +8492,6 @@ packages: - typescript dev: true - /eslint-config-xo-space/0.35.0_eslint@7.32.0: - resolution: {integrity: sha512-+79iVcoLi3PvGcjqYDpSPzbLfqYpNcMlhsCBRsnmDoHAn4npJG6YxmHpelQKpXM7v/EeZTUKb4e1xotWlei8KA==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=8.56.0' - dependencies: - eslint: 7.32.0 - eslint-config-xo: 0.44.0_eslint@7.32.0 - dev: true - /eslint-config-xo-space/0.35.0_eslint@8.57.1: resolution: {integrity: sha512-+79iVcoLi3PvGcjqYDpSPzbLfqYpNcMlhsCBRsnmDoHAn4npJG6YxmHpelQKpXM7v/EeZTUKb4e1xotWlei8KA==} engines: {node: '>=12'} @@ -8666,16 +8502,6 @@ packages: eslint-config-xo: 0.44.0_eslint@8.57.1 dev: true - /eslint-config-xo/0.44.0_eslint@7.32.0: - resolution: {integrity: sha512-YG4gdaor0mJJi8UBeRJqDPO42MedTWYMaUyucF5bhm2pi/HS98JIxfFQmTLuyj6hGpQlAazNfyVnn7JuDn+Sew==} - engines: {node: '>=18'} - peerDependencies: - eslint: '>=8.56.0' - dependencies: - confusing-browser-globals: 1.0.11 - eslint: 7.32.0 - dev: true - /eslint-config-xo/0.44.0_eslint@8.57.1: resolution: {integrity: sha512-YG4gdaor0mJJi8UBeRJqDPO42MedTWYMaUyucF5bhm2pi/HS98JIxfFQmTLuyj6hGpQlAazNfyVnn7JuDn+Sew==} engines: {node: '>=18'} @@ -8686,20 +8512,6 @@ packages: eslint: 8.57.1 dev: true - /eslint-config-xo/0.49.0_eslint@7.32.0: - resolution: {integrity: sha512-hGtD689+fdJxggx1QbEjWfgGOsTasmYqtfk3Rsxru9QyKg2iOhXO2fvR9C7ck8AGw+n2wy6FsA8/MBIzznt5/Q==} - engines: {node: '>=20'} - peerDependencies: - eslint: '>=9.33.0' - dependencies: - '@eslint/css': 0.10.0 - '@eslint/json': 0.13.2 - '@stylistic/eslint-plugin': 5.7.0_eslint@7.32.0 - confusing-browser-globals: 1.0.11 - eslint: 7.32.0 - globals: 16.5.0 - dev: true - /eslint-config-xo/0.49.0_eslint@8.57.1: resolution: {integrity: sha512-hGtD689+fdJxggx1QbEjWfgGOsTasmYqtfk3Rsxru9QyKg2iOhXO2fvR9C7ck8AGw+n2wy6FsA8/MBIzznt5/Q==} engines: {node: '>=20'} @@ -8750,32 +8562,6 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript/3.10.1_euuv2s2m4azrqak6tzap5kwzai: - 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.3 - eslint: 7.32.0 - eslint-plugin-import: 2.32.0_6icihbrq4zzyh6aatylqrjvwgq - get-tsconfig: 4.13.0 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - transitivePeerDependencies: - - supports-color - dev: true - /eslint-module-utils/2.12.1_p6iyeyvinjevldd7q4c5gl6b6e: resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} engines: {node: '>=4'} @@ -8836,48 +8622,6 @@ packages: - supports-color dev: true - /eslint-module-utils/2.12.1_zvqgrm2bcvv543jqlnq6q5dv5y: - 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': 8.53.1_eslint@7.32.0 - debug: 3.2.7 - eslint: 7.32.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1_euuv2s2m4azrqak6tzap5kwzai - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-plugin-es-x/7.8.0_eslint@7.32.0: - resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '>=8' - dependencies: - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - '@eslint-community/regexpp': 4.12.2 - eslint: 7.32.0 - eslint-compat-utils: 0.5.1_eslint@7.32.0 - dev: true - /eslint-plugin-es-x/7.8.0_eslint@8.57.1: resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8890,17 +8634,6 @@ packages: eslint-compat-utils: 0.5.1_eslint@8.57.1 dev: true - /eslint-plugin-es/4.1.0_eslint@7.32.0: - resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} - engines: {node: '>=8.10.0'} - peerDependencies: - eslint: '>=4.19.1' - dependencies: - eslint: 7.32.0 - eslint-utils: 2.1.0 - regexpp: 3.2.0 - dev: true - /eslint-plugin-es/4.1.0_eslint@8.57.1: resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} engines: {node: '>=8.10.0'} @@ -8912,43 +8645,6 @@ packages: regexpp: 3.2.0 dev: true - /eslint-plugin-import/2.32.0_6icihbrq4zzyh6aatylqrjvwgq: - resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 8.53.1_eslint@7.32.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 7.32.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1_zvqgrm2bcvv543jqlnq6q5dv5y - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - /eslint-plugin-import/2.32.0_ar3c7zjwtto324sxhascv2p7uq: resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} engines: {node: '>=4'} @@ -9023,27 +8719,6 @@ packages: - supports-color dev: true - /eslint-plugin-jsdoc/50.8.0_eslint@7.32.0: - resolution: {integrity: sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - dependencies: - '@es-joy/jsdoccomment': 0.50.2 - are-docs-informative: 0.0.2 - comment-parser: 1.4.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - eslint: 7.32.0 - espree: 10.4.0 - esquery: 1.7.0 - parse-imports-exports: 0.2.4 - semver: 7.7.3 - spdx-expression-parse: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint-plugin-jsdoc/50.8.0_eslint@8.57.1: resolution: {integrity: sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==} engines: {node: '>=18'} @@ -9065,18 +8740,6 @@ packages: - supports-color dev: true - /eslint-plugin-mocha/10.5.0_eslint@7.32.0: - resolution: {integrity: sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==} - engines: {node: '>=14.0.0'} - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 7.32.0 - eslint-utils: 3.0.0_eslint@7.32.0 - globals: 13.24.0 - rambda: 7.5.0 - dev: true - /eslint-plugin-mocha/10.5.0_eslint@8.57.1: resolution: {integrity: sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==} engines: {node: '>=14.0.0'} @@ -9089,23 +8752,6 @@ packages: rambda: 7.5.0 dev: true - /eslint-plugin-n/15.7.0_eslint@7.32.0: - resolution: {integrity: sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==} - engines: {node: '>=12.22.0'} - peerDependencies: - eslint: '>=7.0.0' - dependencies: - builtins: 5.1.0 - eslint: 7.32.0 - eslint-plugin-es: 4.1.0_eslint@7.32.0 - eslint-utils: 3.0.0_eslint@7.32.0 - ignore: 5.3.2 - is-core-module: 2.16.1 - minimatch: 3.1.2 - resolve: 1.22.11 - semver: 7.7.3 - dev: true - /eslint-plugin-n/15.7.0_eslint@8.57.1: resolution: {integrity: sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==} engines: {node: '>=12.22.0'} @@ -9143,26 +8789,6 @@ packages: - typescript dev: true - /eslint-plugin-n/17.23.2_eslint@7.32.0: - resolution: {integrity: sha512-RhWBeb7YVPmNa2eggvJooiuehdL76/bbfj/OJewyoGT80qn5PXdz8zMOTO6YHOsI7byPt7+Ighh/i/4a5/v7hw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.23.0' - dependencies: - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - enhanced-resolve: 5.18.4 - eslint: 7.32.0 - eslint-plugin-es-x: 7.8.0_eslint@7.32.0 - get-tsconfig: 4.13.0 - globals: 15.15.0 - globrex: 0.1.2 - ignore: 5.3.2 - semver: 7.7.3 - ts-declaration-location: 1.0.7 - transitivePeerDependencies: - - typescript - dev: true - /eslint-plugin-n/17.23.2_eslint@8.57.1: resolution: {integrity: sha512-RhWBeb7YVPmNa2eggvJooiuehdL76/bbfj/OJewyoGT80qn5PXdz8zMOTO6YHOsI7byPt7+Ighh/i/4a5/v7hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -9272,21 +8898,6 @@ packages: - typescript dev: true - /eslint-plugin-perfectionist/4.15.1_eslint@7.32.0: - resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - eslint: '>=8.45.0' - dependencies: - '@typescript-eslint/types': 8.53.1 - '@typescript-eslint/utils': 8.53.1_eslint@7.32.0 - eslint: 7.32.0 - natural-orderby: 5.0.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /eslint-plugin-perfectionist/4.15.1_eslint@8.57.1: resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9317,30 +8928,6 @@ packages: - typescript dev: true - /eslint-plugin-unicorn/48.0.1_eslint@7.32.0: - resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} - engines: {node: '>=16'} - peerDependencies: - eslint: '>=8.44.0' - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - ci-info: 3.9.0 - clean-regexp: 1.0.0 - eslint: 7.32.0 - esquery: 1.7.0 - indent-string: 4.0.0 - is-builtin-module: 3.2.1 - jsesc: 3.1.0 - lodash: 4.17.23 - pluralize: 8.0.0 - read-pkg-up: 7.0.1 - regexp-tree: 0.1.27 - regjsparser: 0.10.0 - semver: 7.7.3 - strip-indent: 3.0.0 - dev: true - /eslint-plugin-unicorn/48.0.1_eslint@8.57.1: resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} engines: {node: '>=16'} @@ -9365,31 +8952,6 @@ packages: strip-indent: 3.0.0 dev: true - /eslint-plugin-unicorn/56.0.1_eslint@7.32.0: - resolution: {integrity: sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==} - engines: {node: '>=18.18'} - peerDependencies: - eslint: '>=8.56.0' - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1_eslint@7.32.0 - ci-info: 4.3.1 - clean-regexp: 1.0.0 - core-js-compat: 3.48.0 - eslint: 7.32.0 - esquery: 1.7.0 - globals: 15.15.0 - indent-string: 4.0.0 - is-builtin-module: 3.2.1 - jsesc: 3.1.0 - pluralize: 8.0.0 - read-pkg-up: 7.0.1 - regexp-tree: 0.1.27 - regjsparser: 0.10.0 - semver: 7.7.3 - strip-indent: 3.0.0 - dev: true - /eslint-plugin-unicorn/56.0.1_eslint@8.57.1: resolution: {integrity: sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==} engines: {node: '>=18.18'} @@ -9446,16 +9008,6 @@ packages: eslint-visitor-keys: 1.3.0 dev: true - /eslint-utils/3.0.0_eslint@7.32.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 7.32.0 - eslint-visitor-keys: 2.1.0 - dev: true - /eslint-utils/3.0.0_eslint@8.57.1: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} @@ -10741,7 +10293,7 @@ packages: chalk: 4.1.2 cli-cursor: 3.1.0 figures: 3.2.0 - inquirer: 8.2.7 + inquirer: 8.2.7_@types+node@20.19.30 lodash: 4.17.23 rxjs: 6.6.7 dev: false @@ -10829,6 +10381,29 @@ packages: - '@types/node' dev: false + /inquirer/8.2.7_@types+node@20.19.30: + resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} + engines: {node: '>=12.0.0'} + dependencies: + '@inquirer/external-editor': 1.0.3_@types+node@20.19.30 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + figures: 3.2.0 + lodash: 4.17.23 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + transitivePeerDependencies: + - '@types/node' + dev: false + /internal-slot/1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -12592,6 +12167,16 @@ packages: path-to-regexp: 6.3.0 dev: true + /nise/6.1.1: + resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==} + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 13.0.5 + '@sinonjs/text-encoding': 0.7.3 + just-extend: 6.2.0 + path-to-regexp: 8.3.0 + dev: true + /no-case/3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: @@ -13307,6 +12892,10 @@ packages: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} dev: true + /path-to-regexp/8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + dev: true + /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -14202,6 +13791,17 @@ packages: supports-color: 7.2.0 dev: true + /sinon/19.0.5: + resolution: {integrity: sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==} + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 13.0.5 + '@sinonjs/samsam': 8.0.3 + diff: 7.0.0 + nise: 6.1.1 + supports-color: 7.2.0 + dev: true + /sinon/21.0.1: resolution: {integrity: sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==} dependencies: @@ -15197,22 +14797,6 @@ packages: - supports-color dev: true - /typescript-eslint/8.53.1_eslint@7.32.0: - resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - dependencies: - '@typescript-eslint/eslint-plugin': 8.53.1_7f3ev5f4rgmdwp43lafel2uhym - '@typescript-eslint/parser': 8.53.1_eslint@7.32.0 - '@typescript-eslint/typescript-estree': 8.53.1 - '@typescript-eslint/utils': 8.53.1_eslint@7.32.0 - eslint: 7.32.0 - transitivePeerDependencies: - - supports-color - dev: true - /typescript-eslint/8.53.1_eslint@8.57.1: resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}