From 0b06f796b6dd439878c7cb08aba9f50156b80014 Mon Sep 17 00:00:00 2001 From: sauravraw Date: Wed, 4 Feb 2026 10:24:28 +0530 Subject: [PATCH 1/8] Upgrade dependencies in package.json and package-lock.json; add new helper functions for database connection and taxonomies retrieval; implement Drupal service for asset management and migration processes. --- api/package-lock.json | 3565 +++++------------ api/package.json | 10 +- api/src/constants/index.ts | 345 +- .../projects.contentMapper.controller.ts | 16 + api/src/helper/index.ts | 159 + api/src/routes/contentMapper.routes.ts | 9 + api/src/services/contentMapper.service.ts | 705 +++- api/src/services/drupal.service.ts | 70 + api/src/services/drupal/assets.service.ts | 905 +++++ .../services/drupal/content-types.service.ts | 416 ++ api/src/services/drupal/entries.service.ts | 1782 ++++++++ .../services/drupal/field-analysis.service.ts | 599 +++ .../services/drupal/field-fetcher.service.ts | 228 ++ api/src/services/drupal/locales.service.ts | 367 ++ api/src/services/drupal/query.service.ts | 646 +++ api/src/services/drupal/references.service.ts | 421 ++ api/src/services/drupal/taxonomy.service.ts | 465 +++ api/src/services/drupal/version.service.ts | 61 + api/src/services/migration.service.ts | 470 ++- api/src/services/projects.service.ts | 1 + api/src/utils/batch-processor.utils.ts | 119 + api/src/utils/content-type-creator.utils.ts | 34 +- .../utils/optimized-query-builder.utils.ts | 479 +++ ui/package-lock.json | 88 +- ui/package.json | 6 +- ui/src/cmsData/legacyCms.json | 58 +- ui/src/components/AdvancePropertise/index.tsx | 280 +- .../ContentMapper/contentMapper.interface.ts | 8 + ui/src/components/ContentMapper/index.tsx | 37 +- .../Actions/LoadLanguageMapper.tsx | 21 +- .../DestinationStack/Actions/LoadStacks.tsx | 24 +- .../LegacyCms/Actions/LoadFileFormat.tsx | 12 +- .../LegacyCms/Actions/LoadSelectCms.tsx | 59 +- .../LegacyCms/Actions/LoadUploadFile.tsx | 187 +- ui/src/context/app/app.interface.ts | 12 + ui/src/pages/Migration/index.tsx | 2 - ui/src/services/api/migration.service.ts | 12 + upload-api/migration-drupal/config/index.json | 81 + upload-api/migration-drupal/index.js | 12 + .../libs/contentTypeMapper.js | 866 ++++ .../libs/createInitialMapper.js | 321 ++ .../migration-drupal/libs/extractLocale.js | 98 + .../migration-drupal/libs/extractTaxonomy.js | 114 + upload-api/migration-drupal/utils/helper.js | 71 + .../utils/restrictedKeyWords/index.json | 47 + upload-api/package-lock.json | 1593 ++++---- upload-api/package.json | 8 +- upload-api/src/config/index.ts | 20 +- upload-api/src/helper/index.ts | 53 + upload-api/src/models/types.ts | 12 + upload-api/src/routes/index.ts | 473 ++- upload-api/src/services/createMapper.ts | 73 + upload-api/src/services/drupal/index.ts | 116 + upload-api/src/services/fileProcessing.ts | 62 +- upload-api/src/utils/sanitize-path.utils.ts | 106 + upload-api/src/validators/drupal/index.ts | 446 +++ upload-api/src/validators/index.ts | 17 +- 57 files changed, 13122 insertions(+), 4145 deletions(-) create mode 100644 api/src/helper/index.ts create mode 100644 api/src/services/drupal.service.ts create mode 100644 api/src/services/drupal/assets.service.ts create mode 100644 api/src/services/drupal/content-types.service.ts create mode 100644 api/src/services/drupal/entries.service.ts create mode 100644 api/src/services/drupal/field-analysis.service.ts create mode 100644 api/src/services/drupal/field-fetcher.service.ts create mode 100644 api/src/services/drupal/locales.service.ts create mode 100644 api/src/services/drupal/query.service.ts create mode 100644 api/src/services/drupal/references.service.ts create mode 100644 api/src/services/drupal/taxonomy.service.ts create mode 100644 api/src/services/drupal/version.service.ts create mode 100644 api/src/utils/batch-processor.utils.ts create mode 100644 api/src/utils/optimized-query-builder.utils.ts create mode 100755 upload-api/migration-drupal/config/index.json create mode 100644 upload-api/migration-drupal/index.js create mode 100644 upload-api/migration-drupal/libs/contentTypeMapper.js create mode 100644 upload-api/migration-drupal/libs/createInitialMapper.js create mode 100644 upload-api/migration-drupal/libs/extractLocale.js create mode 100644 upload-api/migration-drupal/libs/extractTaxonomy.js create mode 100755 upload-api/migration-drupal/utils/helper.js create mode 100644 upload-api/migration-drupal/utils/restrictedKeyWords/index.json create mode 100644 upload-api/src/services/drupal/index.ts create mode 100644 upload-api/src/utils/sanitize-path.utils.ts create mode 100644 upload-api/src/validators/drupal/index.ts diff --git a/api/package-lock.json b/api/package-lock.json index 0f4be77c2..bdb004025 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -9,10 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@contentstack/cli": "^1.53.1", - "@contentstack/cli-utilities": "^1.16.0", - "@contentstack/json-rte-serializer": "^3.0.4", - "@contentstack/marketplace-sdk": "^1.4.1", + "@contentstack/cli": "^1.57.0", + "@contentstack/cli-utilities": "^1.17.1", + "@contentstack/json-rte-serializer": "^3.0.5", + "@contentstack/marketplace-sdk": "^1.5.0", "axios": "^1.12.0", "chokidar": "^3.6.0", "cors": "^2.8.5", @@ -32,8 +32,10 @@ "jsonwebtoken": "^9.0.3", "lowdb": "^7.0.1", "mkdirp": "^3.0.1", + "mysql2": "^3.16.2", "p-limit": "^6.2.0", "path-to-regexp": "^8.2.0", + "php-serialize": "^5.1.3", "router": "^2.0.0", "shelljs": "^0.9.0", "socket.io": "^4.7.5", @@ -126,29 +128,30 @@ } }, "node_modules/@contentstack/cli": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli/-/cli-1.55.0.tgz", - "integrity": "sha512-nUPT9keZWHIX1cGkm01SqglzGtPH760L4DolUcZ2XjeEl+h/jSvpZSk6EBcoC+fH6glPbXMmtPzOe8Z/ORx3RQ==", - "dependencies": { - "@contentstack/cli-audit": "~1.17.0", - "@contentstack/cli-auth": "~1.7.0", - "@contentstack/cli-cm-bootstrap": "~1.18.0", - "@contentstack/cli-cm-branches": "~1.6.2", - "@contentstack/cli-cm-bulk-publish": "~1.10.5", - "@contentstack/cli-cm-clone": "~1.19.0", - "@contentstack/cli-cm-export": "~1.23.0", - "@contentstack/cli-cm-export-to-csv": "~1.10.2", - "@contentstack/cli-cm-import": "~1.31.0", - "@contentstack/cli-cm-import-setup": "~1.7.2", - "@contentstack/cli-cm-migrate-rte": "~1.6.3", - "@contentstack/cli-cm-seed": "~1.14.0", - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-config": "~1.17.0", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@contentstack/cli/-/cli-1.57.0.tgz", + "integrity": "sha512-F3jMaZlPwRgJ9ZqVZSUIPmHTqEaD8nA/YVLUEfEf8zQwt34hEAdAIFWtD7W7d5VX6m24wm29LX5fib5V0tcgWQ==", + "license": "MIT", + "dependencies": { + "@contentstack/cli-audit": "~1.17.1", + "@contentstack/cli-auth": "~1.7.3", + "@contentstack/cli-cm-bootstrap": "~1.18.2", + "@contentstack/cli-cm-branches": "~1.6.3", + "@contentstack/cli-cm-bulk-publish": "~1.10.6", + "@contentstack/cli-cm-clone": "~1.20.1", + "@contentstack/cli-cm-export": "~1.23.1", + "@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", + "@contentstack/cli-cm-seed": "~1.14.2", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-config": "~1.18.0", "@contentstack/cli-launch": "^1.9.2", - "@contentstack/cli-migration": "~1.10.2", - "@contentstack/cli-utilities": "~1.16.0", - "@contentstack/cli-variants": "~1.3.6", - "@contentstack/management": "~1.22.0", + "@contentstack/cli-migration": "~1.11.0", + "@contentstack/cli-utilities": "~1.17.1", + "@contentstack/cli-variants": "~1.3.7", + "@contentstack/management": "~1.27.3", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "@oclif/plugin-not-found": "^3.2.53", @@ -172,12 +175,13 @@ } }, "node_modules/@contentstack/cli-audit": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-audit/-/cli-audit-1.17.0.tgz", - "integrity": "sha512-JusFlWsabUEwm3eifX7P90kWCKZxipHcyM/k7haiYXCL5NquQQjYlYfPiNQBvk8t3CnYkCVngVNM1uagqXW2eA==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-audit/-/cli-audit-1.17.1.tgz", + "integrity": "sha512-xJCA+oPqj5mmOkkK5+ElCEPLnbzlA/p1/HYnX3JLDJ8FvFEinVicUiTtH2xWdXXCQuBVnA/XgA/5ntkIzvxMWQ==", + "license": "MIT", "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "@oclif/plugin-plugins": "^5.4.54", @@ -195,88 +199,42 @@ "node": ">=16" } }, - "node_modules/@contentstack/cli-audit/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-auth": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@contentstack/cli-auth/-/cli-auth-1.7.3.tgz", + "integrity": "sha512-C34CpY/avOhPgMPHEQVxpECtg5XzPM8hPut2ubUhmty8GT7YpacVkskEd3UwvAptc/cmVtdZgPf/3w1fp108pw==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.1", "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-audit/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", + "@oclif/plugin-help": "^6.2.28", + "otplib": "^12.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-audit/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-bootstrap": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bootstrap/-/cli-cm-bootstrap-1.18.2.tgz", + "integrity": "sha512-Qt0SIQgzMC1d/MyvxnWzjTeISkkzFFWD7UaN9Z8weXvkdKdzCznDousyszR02+hnKb86q6HU0kjY5phuVPw/mQ==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "@contentstack/cli-cm-seed": "~1.14.2", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.28", + "inquirer": "8.2.7", + "mkdirp": "^1.0.4", + "tar": "^7.5.6" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-audit/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-audit/node_modules/mkdirp": { + "node_modules/@contentstack/cli-cm-bootstrap/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", @@ -288,137 +246,115 @@ "node": ">=10" } }, - "node_modules/@contentstack/cli-audit/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-audit/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-cm-branches": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-branches/-/cli-cm-branches-1.6.3.tgz", + "integrity": "sha512-t1z9MX7Egql3TG9QbxFAOswqVvknv0AeCcv8j+4dydPvdcZQ3UgUxyQdc6tUHLFaBqhjqIcPiZP8Dbf14bSenw==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-auth": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-auth/-/cli-auth-1.7.0.tgz", - "integrity": "sha512-33gwkZMvz7LE7Zj3avltxGXuskxeX8x4izJR2Z6oN4dHeQ1HXpjgglo//5Rsh8Crd99YgBcfxbZyKXR2dQ/YWA==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", - "otplib": "^12.0.1" + "chalk": "^4.1.2", + "just-diff": "^6.0.2", + "lodash": "^4.17.21" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-auth/node_modules/@contentstack/cli-command": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.6.2.tgz", - "integrity": "sha512-h4I484kSYuelqZnwFhKL9IkaYlHbcZzMv3mhpKZBzIgbATMuI0Li+1haJNo+Ao7JqQmzT+a00QNtTHqpNDjngA==", + "node_modules/@contentstack/cli-cm-bulk-publish": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bulk-publish/-/cli-cm-bulk-publish-1.10.6.tgz", + "integrity": "sha512-0FuEoaL0dHvNzU+u2UYpj25sGVN+aYrfhGTjM9UPnlVk1sx+h0xN75YEjqLtju7PrikgWWUIa3RzR1QYphzO7A==", + "license": "MIT", "dependencies": { - "@contentstack/cli-utilities": "~1.15.0", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-config": "~1.18.0", + "@contentstack/cli-utilities": "~1.17.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", - "contentstack": "^3.25.3" + "chalk": "^4.1.2", + "dotenv": "^16.5.0", + "inquirer": "8.2.7", + "lodash": "^4.17.21", + "winston": "^3.17.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-auth/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-cm-clone": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-clone/-/cli-cm-clone-1.20.1.tgz", + "integrity": "sha512-nWBFLtDWoe7TOjf00zL/hrdZvdwgIbhM02qsd6oBizQLcXEAhVs9vsuZpRBhfbYoPCFGP98dSrvDZ8weFQaJ9A==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", + "@colors/colors": "^1.6.0", + "@contentstack/cli-cm-export": "~1.23.1", + "@contentstack/cli-cm-import": "~1.31.2", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", "@oclif/core": "^4.3.0", - "axios": "^1.9.0", + "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", + "inquirer": "8.2.7", "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", + "merge": "^2.1.1", "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-auth/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", + "prompt": "^1.3.0", + "rimraf": "^6.1.0" + }, "engines": { - "node": ">= 10" + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-auth/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-export": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export/-/cli-cm-export-1.23.1.tgz", + "integrity": "sha512-q0JLGU/f4K7atipmTxE6LUxeMgabt8+UVVUwKprg5yJMogGLut6U8ZcAH/t5l0sJTmQkWrloQngmsgIk27dSWg==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@contentstack/cli-variants": "~1.3.7", + "@oclif/core": "^4.3.3", + "async": "^3.2.6", + "big-json": "^3.2.0", + "bluebird": "^3.7.2", + "chalk": "^4.1.2", "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "merge": "^2.1.1", + "mkdirp": "^1.0.4", + "progress-stream": "^2.0.0", + "promise-limit": "^2.7.0", + "winston": "^3.17.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-auth/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", + "node_modules/@contentstack/cli-cm-export-to-csv": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export-to-csv/-/cli-cm-export-to-csv-1.11.0.tgz", + "integrity": "sha512-S7C5MCiEu3z9Unk3p6majXILpqmDNSuJPmZqt9m+f11+Wk3S1gHq2+mDre8hAx9zpN6Y9wloQCI3MIuC4vcp4g==", + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@oclif/core": "^4.8.0", + "@oclif/plugin-help": "^6.2.32", + "fast-csv": "^4.3.6", + "inquirer": "8.2.7", + "inquirer-checkbox-plus-prompt": "1.4.2", + "mkdirp": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@contentstack/cli-auth/node_modules/mkdirp": { + "node_modules/@contentstack/cli-cm-export/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", @@ -430,129 +366,73 @@ "node": ">=10" } }, - "node_modules/@contentstack/cli-auth/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-auth/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-cm-import": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import/-/cli-cm-import-1.31.2.tgz", + "integrity": "sha512-3NU4eoBhytxd/fKVnTYHl0t593fvrppma9mGmnzhD/V1rueVdf7VRxs/G5+l+N35bJNtdRoSEczBLlzgUgGSqw==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-bootstrap": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bootstrap/-/cli-cm-bootstrap-1.18.0.tgz", - "integrity": "sha512-3o8Q9DeKcisEhJhbkD4DIPEsVf9BVkDHYSQYDVxFw8mdW89twecJz5ofdTEedU1/LwaQTvg8YOEwCAgr3cjiZA==", - "dependencies": { - "@contentstack/cli-cm-seed": "~1.14.0", + "@contentstack/cli-audit": "~1.17.1", "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", + "@contentstack/cli-utilities": "~1.17.0", + "@contentstack/cli-variants": "~1.3.7", + "@contentstack/management": "~1.27.3", "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "inquirer": "8.2.7", + "big-json": "^3.2.0", + "bluebird": "^3.7.2", + "chalk": "^4.1.2", + "debug": "^4.4.1", + "fs-extra": "^11.3.0", + "lodash": "^4.17.21", + "marked": "^4.3.0", + "merge": "^2.1.1", "mkdirp": "^1.0.4", - "tar": "^6.2.1 " + "promise-limit": "^2.7.0", + "uuid": "^9.0.1", + "winston": "^3.17.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-cm-import-setup": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import-setup/-/cli-cm-import-setup-1.7.3.tgz", + "integrity": "sha512-dEPtaNCaf/oROG6s7McXHy2Eh00WBmGuwZyKLG7+a5k0j6zfkpbI18l8CziI1C7xWtAUAsufO7jGlCLDfjL9uA==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", "@oclif/core": "^4.3.0", - "axios": "^1.9.0", + "big-json": "^3.2.0", "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", + "fs-extra": "^11.3.0", "lodash": "^4.17.21", + "merge": "^2.1.1", "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" + "winston": "^3.17.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-import-setup/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" + "node": ">=10" } }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/mkdirp": { + "node_modules/@contentstack/cli-cm-import/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -560,2192 +440,321 @@ "node": ">=10" } }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-bootstrap/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-cm-migrate-rte": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-migrate-rte/-/cli-cm-migrate-rte-1.6.4.tgz", + "integrity": "sha512-TKswWWtq+DmUDXv+Fx88vxBAhS3kqfBoRXIOGJQNIdpQzHzaLxiCyOdmcCFyK1YhYDWzU19ad/s0GLBlv0D8fg==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-branches": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-branches/-/cli-cm-branches-1.6.2.tgz", - "integrity": "sha512-cI6dbVMBJRJJKFgNNBjFdvx21i8PKwmpiRStB6SncVP1wVQIiMSX4p7Fee487YrLG2dm/3Fl7NfOPGqp/vlpXg==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@contentstack/json-rte-serializer": "~2.1.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", - "just-diff": "^6.0.2", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-branches/node_modules/@contentstack/cli-command": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.6.2.tgz", - "integrity": "sha512-h4I484kSYuelqZnwFhKL9IkaYlHbcZzMv3mhpKZBzIgbATMuI0Li+1haJNo+Ao7JqQmzT+a00QNtTHqpNDjngA==", - "dependencies": { - "@contentstack/cli-utilities": "~1.15.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "contentstack": "^3.25.3" + "collapse-whitespace": "^1.1.7", + "jsdom": "^20.0.3", + "jsonschema": "^1.5.0", + "lodash": "^4.17.21", + "nock": "^13.5.6", + "omit-deep-lodash": "^1.1.7", + "sinon": "^21.0.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-cm-branches/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/@contentstack/json-rte-serializer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-2.1.0.tgz", + "integrity": "sha512-klw+0kH5UtL4mHGDP7A8olZIaA4CoyAVzveYqso8uxeDXKkTvwF8D5HBhCqQLr0NXwhofl+FF431cbzGZ3TNCg==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", + "array-flat-polyfill": "^1.0.1", "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" + "lodash.clonedeep": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isempty": "^4.4.0", + "lodash.isequal": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isundefined": "^3.0.1", + "lodash.kebabcase": "^4.1.1", + "slate": "^0.103.0", + "uuid": "^8.3.2" } }, - "node_modules/@contentstack/cli-cm-branches/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">= 10" + "node": ">= 6.0.0" } }, - "node_modules/@contentstack/cli-cm-branches/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "cssom": "~0.3.6" }, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/@contentstack/cli-cm-branches/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT" }, - "node_modules/@contentstack/cli-cm-branches/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-branches/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-branches/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, - "node_modules/@contentstack/cli-cm-bulk-publish": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bulk-publish/-/cli-cm-bulk-publish-1.10.5.tgz", - "integrity": "sha512-UYhLEKZTA0d2mPvNgLdKDIV+4NWiz6lv49wOKOlqQu77Lohf2mbrGMGVhtL8bq3zqBd7FBK3ng7QEDw6RHNG/w==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-config": "~1.17.0", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "chalk": "^4.1.2", - "dotenv": "^16.5.0", - "inquirer": "8.2.7", - "lodash": "^4.17.21", - "winston": "^3.17.0" + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=4" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/@contentstack/cli-config": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@contentstack/cli-config/-/cli-config-1.15.3.tgz", - "integrity": "sha512-sZlJt2C28ReIZpFcBNkXy41QDZvMhDzpLfD3EjGLZYGD82/qqT/7mhdsOScigu5PXUmhHI1z+5yx/DaAEAkBnQ==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "license": "MIT", "dependencies": { - "@contentstack/cli-command": "~1.6.1", - "@contentstack/cli-utilities": "~1.15.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "lodash": "^4.17.21" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/@contentstack/cli-config/node_modules/@contentstack/cli-command": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.6.2.tgz", - "integrity": "sha512-h4I484kSYuelqZnwFhKL9IkaYlHbcZzMv3mhpKZBzIgbATMuI0Li+1haJNo+Ao7JqQmzT+a00QNtTHqpNDjngA==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", "dependencies": { - "@contentstack/cli-utilities": "~1.15.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "contentstack": "^3.25.3" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=14.0.0" + "node": ">= 6" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=0.10.0" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/cli-width": { + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/tr46": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, "engines": { - "node": ">= 10" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "uuid": "dist/bin/uuid" } }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-bulk-publish/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/@contentstack/cli-cm-clone": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-clone/-/cli-cm-clone-1.19.0.tgz", - "integrity": "sha512-QtW1rFknlTXExTfNzg6VGSswOWYA+jYhLWL8WDOSwaDnWScAAnT8SEtkE0lngglJDxjIHRuic9Kw0ZEcP4EUZg==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "license": "MIT", "dependencies": { - "@colors/colors": "^1.6.0", - "@contentstack/cli-cm-export": "~1.23.0", - "@contentstack/cli-cm-import": "~1.31.0", - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "chalk": "^4.1.2", - "inquirer": "8.2.7", - "lodash": "^4.17.21", - "merge": "^2.1.1", - "ora": "^5.4.1", - "prompt": "^1.3.0", - "rimraf": "^6.1.0" + "iconv-lite": "0.6.3" }, "engines": { - "node": ">=14.0.0" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-clone/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" + "engines": { + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-clone/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-clone/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-clone/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-clone/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@contentstack/cli-cm-clone/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-clone/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-cm-seed": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-seed/-/cli-cm-seed-1.14.2.tgz", + "integrity": "sha512-9k24YmdfkqqCOlYSBRy4ckbMkuAwJfS5RL3OemOG3QQYIu2D1MjHpBZyWUe27P/Tr9AufLBF/1YIgz4Fr8bmbQ==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-export": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export/-/cli-cm-export-1.23.0.tgz", - "integrity": "sha512-ckR6/+4GJ6aS7wOOx79PtEb9Ql4rrwgpSFzDoAhENqHY1O4qJ3oEUmNNXKJmqD7/gfexvmb47X6Fm91wgNLQDw==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@contentstack/cli-variants": "~1.3.6", - "@oclif/core": "^4.3.3", - "async": "^3.2.6", - "big-json": "^3.2.0", - "bluebird": "^3.7.2", - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "merge": "^2.1.1", - "mkdirp": "^1.0.4", - "progress-stream": "^2.0.0", - "promise-limit": "^2.7.0", - "winston": "^3.17.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export-to-csv/-/cli-cm-export-to-csv-1.10.2.tgz", - "integrity": "sha512-oe8+TzH0fo+A540uWz0rmu92T+I8fnKxYYe9F6AD+eQi9tPt+4HzvSYYwQqb+fBnHvkVfcxOBQ3x5hZ9Kp3STA==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.32", - "fast-csv": "^4.3.6", - "inquirer": "8.2.7", - "inquirer-checkbox-plus-prompt": "1.4.2", - "mkdirp": "^3.0.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/@contentstack/cli-utilities/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-export-to-csv/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-export/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-export/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-import": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import/-/cli-cm-import-1.31.0.tgz", - "integrity": "sha512-iJW28DwQZRu3GIZdoXsK7FlV3OoAtx3hkNDiGDyN9fbpD3pGENJAzQDQGh8NxPMZktWB3j3BmERDBjPFjoutig==", - "dependencies": { - "@contentstack/cli-audit": "~1.17.0", - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@contentstack/cli-variants": "~1.3.6", - "@contentstack/management": "~1.22.0", - "@oclif/core": "^4.3.0", - "big-json": "^3.2.0", - "bluebird": "^3.7.2", - "chalk": "^4.1.2", - "debug": "^4.4.1", - "fs-extra": "^11.3.0", - "lodash": "^4.17.21", - "marked": "^4.3.0", - "merge": "^2.1.1", - "mkdirp": "^1.0.4", - "promise-limit": "^2.7.0", - "uuid": "^9.0.1", - "winston": "^3.17.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import-setup": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import-setup/-/cli-cm-import-setup-1.7.2.tgz", - "integrity": "sha512-rjLue5ZwW0QHtWNGKIPcaI5FdqhXDAn2CMZ7iWZfDsIlX18VhXJkDrcRRL77QxX2ppMvs7PdT/03vEPioNm9qA==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "big-json": "^3.2.0", - "chalk": "^4.1.2", - "fs-extra": "^11.3.0", - "lodash": "^4.17.21", - "merge": "^2.1.1", - "mkdirp": "^1.0.4", - "winston": "^3.17.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-import-setup/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-import/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-import/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-migrate-rte/-/cli-cm-migrate-rte-1.6.3.tgz", - "integrity": "sha512-bsHF+La03FNfrEGqwmi8Rc4K05WJ+LJ6STnpzDgWRvHSoxi+fnxRIINkDLLK8HI/akJ/k+LKrBA8+gAalSyOSA==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@contentstack/json-rte-serializer": "~2.1.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "chalk": "^4.1.2", - "collapse-whitespace": "^1.1.7", - "jsdom": "^20.0.3", - "jsonschema": "^1.5.0", - "lodash": "^4.17.21", - "nock": "^13.5.6", - "omit-deep-lodash": "^1.1.7", - "sinon": "^19.0.5" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/@contentstack/cli-command": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.6.2.tgz", - "integrity": "sha512-h4I484kSYuelqZnwFhKL9IkaYlHbcZzMv3mhpKZBzIgbATMuI0Li+1haJNo+Ao7JqQmzT+a00QNtTHqpNDjngA==", - "dependencies": { - "@contentstack/cli-utilities": "~1.15.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "contentstack": "^3.25.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/@contentstack/cli-utilities/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/@contentstack/json-rte-serializer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-2.1.0.tgz", - "integrity": "sha512-klw+0kH5UtL4mHGDP7A8olZIaA4CoyAVzveYqso8uxeDXKkTvwF8D5HBhCqQLr0NXwhofl+FF431cbzGZ3TNCg==", - "dependencies": { - "array-flat-polyfill": "^1.0.1", - "lodash": "^4.17.21", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isempty": "^4.4.0", - "lodash.isequal": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isundefined": "^3.0.1", - "lodash.kebabcase": "^4.1.1", - "slate": "^0.103.0", - "uuid": "^8.3.2" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-cm-migrate-rte/node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/@contentstack/cli-cm-seed": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-seed/-/cli-cm-seed-1.14.0.tgz", - "integrity": "sha512-wA9yAca1UgZbi2fRHYfYVl717FYhafzBAVZ8/4eTTfKycI/Y1hLrkfrFb5xAIm+eP6+ySmlL2Xh73MNOmaTjXQ==", - "dependencies": { - "@contentstack/cli-cm-import": "~1.31.0", - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@contentstack/management": "~1.22.0", - "inquirer": "8.2.7", - "mkdirp": "^1.0.4", - "tar": "^6.2.1", - "tmp": "^0.2.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/@contentstack/cli-cm-import": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import/-/cli-cm-import-1.30.0.tgz", - "integrity": "sha512-FD+KY+RkAwBP2GK7rg8Zbgp/a4fMSYcXUqwqCZRRlgHUl1T9LOjXAtwktgqHBOdN596WPPihVFh8XZ9wWIwWpA==", - "dependencies": { - "@contentstack/cli-audit": "~1.16.1", - "@contentstack/cli-command": "~1.7.0", - "@contentstack/cli-utilities": "~1.15.0", - "@contentstack/cli-variants": "~1.3.5", - "@contentstack/management": "~1.22.0", - "@oclif/core": "^4.3.0", - "big-json": "^3.2.0", - "bluebird": "^3.7.2", - "chalk": "^4.1.2", - "debug": "^4.4.1", - "fs-extra": "^11.3.0", - "lodash": "^4.17.21", - "marked": "^4.3.0", - "merge": "^2.1.1", - "mkdirp": "^1.0.4", - "promise-limit": "^2.7.0", - "uuid": "^9.0.1", - "winston": "^3.17.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-cm-seed/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-command": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.7.1.tgz", - "integrity": "sha512-VS+f+hwStXNShyVs9m/xV5l+KVZZ81k9lhJu+XjO5zXV/ZS3BNzW96xS6oAOUvSURVUPmZvELzjXFIvwbdBnGQ==", - "dependencies": { - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "contentstack": "^3.25.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-command/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-command/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-command/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-command/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-command/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-command/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-command/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-config": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-config/-/cli-config-1.17.0.tgz", - "integrity": "sha512-OW0ThVjabSQGSzHUJYLQANfR2ym6QNbyjb+zQV/5IezLITyrA6YZN7Iot1EZeMN3H14hm5GUjYKyx7KBbUKBqg==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-config/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-config/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-config/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-config/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-config/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-config/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-config/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-launch": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@contentstack/cli-launch/-/cli-launch-1.9.4.tgz", - "integrity": "sha512-Wev1zDBZ61eJSOGEaj08LZXkK6+sX7TM9TBxNd5ZHc0tkl4y8gDtuoo+wfaCV0/WcXmglUHazFX4ZgDYyB9UDQ==", - "dependencies": { - "@apollo/client": "^3.14.0", - "@contentstack/cli-command": "^1.4.0", - "@contentstack/cli-utilities": "^1.12.0", - "@oclif/core": "^4.2.7", - "@oclif/plugin-help": "^6.2.25", - "@oclif/plugin-plugins": "^5.4.15", - "@rollup/plugin-commonjs": "^28.0.2", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-typescript": "^12.1.2", - "@types/express": "^4.17.21", - "@types/express-serve-static-core": "^4.17.34", - "adm-zip": "^0.5.16", - "chalk": "^4.1.2", - "cross-fetch": "^4.1.0", - "dotenv": "^16.4.7", - "express": "^4.21.1", - "form-data": "4.0.4", - "graphql": "^16.9.0", - "ini": "^3.0.1", - "lodash": "^4.17.21", - "open": "^8.4.2", - "rollup": "^4.34.6", - "winston": "^3.17.0" - }, - "bin": { - "launch": "bin/run.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@contentstack/cli-migration": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@contentstack/cli-migration/-/cli-migration-1.10.2.tgz", - "integrity": "sha512-nA/2lNwCrOdFc7LqyFoWP52jCLF5FXmAIBS9uejbkOjGx7eo2y0UTfCOwS6h77wqdpbcqvdnpxNz+94rGp8Wyw==", - "dependencies": { - "@contentstack/cli-command": "~1.7.1", - "@contentstack/cli-utilities": "~1.16.0", - "@oclif/core": "^4.3.0", - "@oclif/plugin-help": "^6.2.28", - "async": "^3.2.6", - "callsites": "^3.1.0", - "cardinal": "^2.1.1", - "chalk": "^4.1.2", - "concat-stream": "^2.0.0", - "listr": "^0.14.3", - "winston": "^3.17.0" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-migration/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli-migration/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@contentstack/cli-utilities": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.16.0.tgz", - "integrity": "sha512-2SlKE9bJH3bd+ESNq1c9FWRCYzIjuWxRtXp+83eX7qDzHA7Lgu2EsRjfq+TcYVtBdCd0BzVATRIU2t/vNUl5Zw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.25.1", - "@contentstack/marketplace-sdk": "^1.4.0", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", + "@contentstack/cli-cm-import": "~1.31.2", + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@contentstack/management": "~1.27.3", "inquirer": "8.2.7", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.1", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-utilities/node_modules/@contentstack/management": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.25.1.tgz", - "integrity": "sha512-454V3zGw4nrxnlYxXm82Z+yNjuechiN+TRE7SXWyHFUsexYVpKNyGyKZCvG6b4JymRTVUZpy/KnFixo01GP9Sg==", - "license": "MIT", - "dependencies": { - "assert": "^2.1.0", - "axios": "^1.12.2", - "buffer": "^6.0.3", - "form-data": "^4.0.4", - "husky": "^9.1.7", - "lodash": "^4.17.21", - "otplib": "^12.0.1", - "qs": "^6.14.0", - "stream-browserify": "^3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@contentstack/cli-utilities/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@contentstack/cli-variants": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@contentstack/cli-variants/-/cli-variants-1.3.5.tgz", - "integrity": "sha512-HdIRDBaAsIj1nPTGo6pKaDc5F3nDVdRldAtc+ti0iERJHuw+zM7NVAezuB8yMh7tl841B0wDRiI9pytIXxzwVg==", - "dependencies": { - "@contentstack/cli-utilities": "~1.15.0", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", - "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-variants/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", - "license": "MIT", - "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", - "@oclif/core": "^4.3.0", - "axios": "^1.9.0", - "chalk": "^4.1.2", - "cli-cursor": "^3.1.0", - "cli-progress": "^3.12.0", - "cli-table": "^0.3.11", - "conf": "^10.2.0", - "dotenv": "^16.5.0", - "figures": "^3.2.0", - "inquirer": "8.2.6", - "inquirer-search-checkbox": "^1.0.0", - "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", - "klona": "^2.0.6", - "lodash": "^4.17.21", "mkdirp": "^1.0.4", - "open": "^8.4.2", - "ora": "^5.4.1", - "papaparse": "^5.5.3", - "recheck": "~4.4.5", - "rxjs": "^6.6.7", - "traverse": "^0.6.11", - "tty-table": "^4.2.3", - "unique-string": "^2.0.0", - "uuid": "^9.0.1", - "winston": "^3.17.0", - "xdg-basedir": "^4.0.0" - } - }, - "node_modules/@contentstack/cli-variants/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@contentstack/cli-variants/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@contentstack/cli-variants/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "tar": "^7.5.6", + "tmp": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli-variants/node_modules/mkdirp": { + "node_modules/@contentstack/cli-cm-seed/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", @@ -2757,34 +766,104 @@ "node": ">=10" } }, - "node_modules/@contentstack/cli-variants/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" + "node_modules/@contentstack/cli-command": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@contentstack/cli-command/-/cli-command-1.7.2.tgz", + "integrity": "sha512-dtXc3gIcnivfLegADy5/PZb+1x/esZ65H2E1CjO/pg50UC8Vy1U+U0ozS0hJZTFoaVjeG+1VJRoxf5MrtUGnNA==", + "license": "MIT", + "dependencies": { + "@contentstack/cli-utilities": "~1.17.0", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.28", + "contentstack": "^3.25.3" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@contentstack/cli-variants/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@contentstack/cli-config": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@contentstack/cli-config/-/cli-config-1.18.0.tgz", + "integrity": "sha512-rJpCndpIxavo/V4cFgyor/Njk0rVO2h+IiIX4CMVNi0UtDhXWMl7Eca0Bzkc5t3BiAtdO/eywrnnwomzN2JbXA==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.0", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.28", + "lodash": "^4.17.21" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/@contentstack/cli/node_modules/@contentstack/cli-utilities": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.15.0.tgz", - "integrity": "sha512-Q3csEjZk7rdEvbhRyq41jMT9nFduxR7zVpyGAkYdialh4KjMHxJvzVUdmYuA3PA92xoTlFdcY97yXPQJpmptTw==", + "node_modules/@contentstack/cli-launch": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@contentstack/cli-launch/-/cli-launch-1.9.4.tgz", + "integrity": "sha512-Wev1zDBZ61eJSOGEaj08LZXkK6+sX7TM9TBxNd5ZHc0tkl4y8gDtuoo+wfaCV0/WcXmglUHazFX4ZgDYyB9UDQ==", + "dependencies": { + "@apollo/client": "^3.14.0", + "@contentstack/cli-command": "^1.4.0", + "@contentstack/cli-utilities": "^1.12.0", + "@oclif/core": "^4.2.7", + "@oclif/plugin-help": "^6.2.25", + "@oclif/plugin-plugins": "^5.4.15", + "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-typescript": "^12.1.2", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.34", + "adm-zip": "^0.5.16", + "chalk": "^4.1.2", + "cross-fetch": "^4.1.0", + "dotenv": "^16.4.7", + "express": "^4.21.1", + "form-data": "4.0.4", + "graphql": "^16.9.0", + "ini": "^3.0.1", + "lodash": "^4.17.21", + "open": "^8.4.2", + "rollup": "^4.34.6", + "winston": "^3.17.0" + }, + "bin": { + "launch": "bin/run.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@contentstack/cli-migration": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@contentstack/cli-migration/-/cli-migration-1.11.0.tgz", + "integrity": "sha512-lbPNtSK9EKm1EA9X6IyiijrELsjv4TRNZoHpuLNUB5iKzXWAM63L4e/q57sx7S9TkRR4NxMM+ckNwNfSj51Q9Q==", + "license": "MIT", + "dependencies": { + "@contentstack/cli-command": "~1.7.2", + "@contentstack/cli-utilities": "~1.17.1", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.28", + "async": "^3.2.6", + "callsites": "^3.1.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "concat-stream": "^2.0.0", + "listr": "^0.14.3", + "winston": "^3.17.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/@contentstack/cli-utilities": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.17.1.tgz", + "integrity": "sha512-4N25Nq+stSoSRwK+otBwzkyYg6DycwY+AAHMkUGKWSRqrtyieitQfWe///+kghqmfTE81A9BHO7Pv0j00KKpLQ==", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.22.0", - "@contentstack/marketplace-sdk": "^1.2.8", + "@contentstack/management": "~1.27.3", + "@contentstack/marketplace-sdk": "^1.4.0", "@oclif/core": "^4.3.0", "axios": "^1.9.0", "chalk": "^4.1.2", @@ -2794,10 +873,10 @@ "conf": "^10.2.0", "dotenv": "^16.5.0", "figures": "^3.2.0", - "inquirer": "8.2.6", + "inquirer": "8.2.7", "inquirer-search-checkbox": "^1.0.0", "inquirer-search-list": "^1.2.6", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "klona": "^2.0.6", "lodash": "^4.17.21", "mkdirp": "^1.0.4", @@ -2814,51 +893,32 @@ "xdg-basedir": "^4.0.0" } }, - "node_modules/@contentstack/cli/node_modules/@contentstack/cli-utilities/node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "node_modules/@contentstack/cli-utilities/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=10" } }, - "node_modules/@contentstack/cli/node_modules/@contentstack/cli-utilities/node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", + "node_modules/@contentstack/cli-variants": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@contentstack/cli-variants/-/cli-variants-1.3.7.tgz", + "integrity": "sha512-MrOCjU1sJpgHmTmTNrRuOiJFwwoDp4uLvZebO2K9UHiMwkgPv7vGDcqTTAFXedDp+CA16YKX0OAA+nc9EcX7OQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@contentstack/cli/node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "license": "ISC", - "engines": { - "node": ">= 10" + "@contentstack/cli-utilities": "~1.17.0", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.28", + "lodash": "^4.17.21", + "mkdirp": "^1.0.4", + "winston": "^3.17.0" } }, - "node_modules/@contentstack/cli/node_modules/mkdirp": { + "node_modules/@contentstack/cli-variants/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", @@ -2870,33 +930,14 @@ "node": ">=10" } }, - "node_modules/@contentstack/cli/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" - }, - "node_modules/@contentstack/cli/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@contentstack/json-rte-serializer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-3.0.4.tgz", - "integrity": "sha512-dvJYLApd7O/aw2tufA3810LwDRk0LHO53V4tBY5kYRoJkpiuZZOEmP/vUD7gh8lP4mT5RBoL4f6s9gBa6uvWzQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-3.0.5.tgz", + "integrity": "sha512-WWntS1vO8Hzacf5YU14w1my7oivcwFs9l0gqaq2r5tPJ/5bWtgIW8zOVO5teMX63pMHWCUy+69FmD/xMoGFIsw==", + "license": "MIT", "dependencies": { "array-flat-polyfill": "^1.0.1", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", @@ -2918,29 +959,49 @@ } }, "node_modules/@contentstack/management": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.22.0.tgz", - "integrity": "sha512-TmwCKhdZnmGpcTuXn5JWbvMqbu0PqEn8Z/oEUlCelAxpo9vSC2qS4aejJtLTqC3Gii/7cJwjqF1BoFpwSO5J9A==", + "version": "1.27.4", + "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.27.4.tgz", + "integrity": "sha512-p1xQNsyLejeaZ1rcobkwZRhoTiFvR1SeAhC95RCGm8XXyQ6JaNwn2EJQsZvhv0vT8NcRQlvFXtQhVu1zHiPQ8w==", + "license": "MIT", "dependencies": { + "@contentstack/utils": "^1.6.3", "assert": "^2.1.0", - "axios": "^1.9.0", + "axios": "^1.12.2", "buffer": "^6.0.3", - "form-data": "^4.0.2", + "form-data": "^4.0.5", "husky": "^9.1.7", - "lodash": "^4.17.21", - "qs": "^6.14.0", + "lodash": "^4.17.23", + "otplib": "^12.0.1", + "qs": "6.14.1", "stream-browserify": "^3.0.0" }, "engines": { "node": ">=8.0.0" } }, + "node_modules/@contentstack/management/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@contentstack/marketplace-sdk": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@contentstack/marketplace-sdk/-/marketplace-sdk-1.4.2.tgz", - "integrity": "sha512-eFwSWif5RmHJqniYHNzkC1P0WUN90t1BPfEx8zR9pF0GKETELGVtIwf0tFJ9Ag7zPchQZ63qtcbAQ4WJYWN/+w==", - "hasInstallScript": true, + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@contentstack/marketplace-sdk/-/marketplace-sdk-1.5.0.tgz", + "integrity": "sha512-n2USMwswXBDtmVOg0t5FUks8X0d49u0UDFSrwxti09X/SONeP0P8wSqIDCjoB2gGRQc6fg/Fg2YPRvejUWeR4A==", + "license": "MIT", "dependencies": { + "@contentstack/utils": "^1.6.3", "axios": "^1.13.2" } }, @@ -3587,6 +1648,7 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", @@ -3599,12 +1661,14 @@ "node_modules/@fast-csv/format/node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" }, "node_modules/@fast-csv/parse": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", @@ -3618,7 +1682,8 @@ "node_modules/@fast-csv/parse/node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", @@ -4025,9 +2090,10 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" }, @@ -4076,12 +2142,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -4131,6 +2191,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -4712,6 +2784,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==", + "license": "MIT", "dependencies": { "any-observable": "^0.3.0" }, @@ -4731,14 +2804,16 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "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==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", + "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" } @@ -4747,6 +2822,7 @@ "version": "8.0.3", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", "type-detect": "^4.1.0" @@ -4756,15 +2832,11 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==" - }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", @@ -4783,6 +2855,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -5287,7 +3360,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" }, "node_modules/accepts": { "version": "1.3.8", @@ -5316,6 +3390,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "license": "MIT", "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -5334,6 +3409,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", "dependencies": { "acorn": "^8.11.0" }, @@ -5427,7 +3503,8 @@ "node_modules/ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "license": "MIT" }, "node_modules/ansis": { "version": "3.17.0", @@ -5446,6 +3523,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5686,6 +3764,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", "engines": { "node": ">=0.8" } @@ -5738,6 +3817,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/axe-core": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", @@ -5805,6 +3893,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/big-json/-/big-json-3.2.0.tgz", "integrity": "sha512-2Etdurszm1CAFxqpH08lItXyf5CI1OBKRn7imCeI8Lh+a2UvdN2WpuSduxB/3ccao6v93SxiS5fIlE/v1QLoPg==", + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "into-stream": "^5.1.0", @@ -5861,12 +3950,14 @@ "node_modules/block-elements": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/block-elements/-/block-elements-1.2.0.tgz", - "integrity": "sha512-4E+pnt4v8HSEEH3Dwe2Bcu8TIbdReez7b5Qjs11dJIdbGFaNSobDgphWxy9NtjYB9ZsZd7DzByDbeXy4DvYz5Q==" + "integrity": "sha512-4E+pnt4v8HSEEH3Dwe2Bcu8TIbdReez7b5Qjs11dJIdbGFaNSobDgphWxy9NtjYB9ZsZd7DzByDbeXy4DvYz5Q==", + "license": "MIT" }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.4", @@ -5973,7 +4064,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/bytes": { "version": "3.1.2", @@ -6047,6 +4139,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "license": "MIT", "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" @@ -6110,11 +4203,12 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/clean-stack": { @@ -6179,6 +4273,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", "integrity": "sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==", + "license": "MIT", "dependencies": { "slice-ansi": "0.0.4", "string-width": "^1.0.1" @@ -6191,6 +4286,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6199,6 +4295,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "license": "MIT", "dependencies": { "number-is-nan": "^1.0.0" }, @@ -6210,6 +4307,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "license": "MIT", "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6223,6 +4321,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -6263,6 +4362,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6271,6 +4371,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/collapse-whitespace/-/collapse-whitespace-1.1.7.tgz", "integrity": "sha512-24up1hbQSsnaDSGHPOvGQT84vmxvG0QUrI8tguiQpo9I5irrnypCKwddXindXMyXhoTe+9V6LYj3aFIhTQ4UCg==", + "license": "MIT", "dependencies": { "block-elements": "^1.0.0", "void-elements": "^2.0.1" @@ -6379,6 +4480,7 @@ "engines": [ "node >= 6.0" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -6464,7 +4566,8 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", @@ -6532,7 +4635,8 @@ "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" }, "node_modules/cssstyle": { "version": "4.6.0", @@ -6659,7 +4763,8 @@ "node_modules/date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "license": "MIT" }, "node_modules/dayjs": { "version": "1.11.19", @@ -6781,6 +4886,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6799,9 +4913,10 @@ } }, "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -6835,6 +4950,7 @@ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "deprecated": "Use your platform's native DOMException instead", + "license": "MIT", "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -6917,6 +5033,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6924,7 +5041,8 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, "node_modules/enabled": { "version": "2.0.0", @@ -7650,14 +5768,6 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8210,9 +6320,9 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "license": "MIT", "dependencies": { "chardet": "^0.4.0", @@ -8224,9 +6334,9 @@ } }, "node_modules/external-editor/node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==", "license": "MIT" }, "node_modules/external-editor/node_modules/iconv-lite": { @@ -8265,6 +6375,7 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" @@ -8648,6 +6759,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -8656,12 +6768,14 @@ "node_modules/from2/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/from2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8675,12 +6789,14 @@ "node_modules/from2/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/from2/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -8698,28 +6814,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -8787,6 +6881,15 @@ "node": ">= 0.6.0" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -9049,6 +7152,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -9060,6 +7164,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9390,6 +7495,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/inquirer-checkbox-plus-prompt/-/inquirer-checkbox-plus-prompt-1.4.2.tgz", "integrity": "sha512-W8/NL9x5A81Oq9ZfbYW5c1LuwtAhc/oB/u9YZZejna0pqrajj27XhnUHygJV0Vn5TvcDy1VJcD2Ld9kTk40dvg==", + "license": "MIT", "dependencies": { "chalk": "4.1.2", "cli-cursor": "^3.1.0", @@ -9882,6 +7988,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", + "license": "MIT", "dependencies": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" @@ -10201,6 +8308,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "license": "MIT", "dependencies": { "symbol-observable": "^1.1.0" }, @@ -10212,6 +8320,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10241,7 +8350,14 @@ "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" }, "node_modules/is-reference": { "version": "1.2.1", @@ -10424,7 +8540,8 @@ "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" }, "node_modules/iterator.prototype": { "version": "1.1.5", @@ -10555,12 +8672,14 @@ "node_modules/json-stream-stringify": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-2.0.4.tgz", - "integrity": "sha512-gIPoa6K5w6j/RnQ3fOtmvICKNJGViI83A7dnTIL+0QJ/1GKuNvCPFvbFWxt0agruF4iGgDFJvge4Gua4ZoiggQ==" + "integrity": "sha512-gIPoa6K5w6j/RnQ3fOtmvICKNJGViI83A7dnTIL+0QJ/1GKuNvCPFvbFWxt0agruF4iGgDFJvge4Gua4ZoiggQ==", + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" }, "node_modules/json5": { "version": "1.0.2", @@ -10593,7 +8712,8 @@ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "engines": [ "node >= 0.2.0" - ] + ], + "license": "MIT" }, "node_modules/jsonpath": { "version": "1.1.1", @@ -10609,6 +8729,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", + "license": "MIT", "engines": { "node": "*" } @@ -10617,6 +8738,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "license": "(MIT OR Apache-2.0)", "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -10669,12 +8791,8 @@ "node_modules/just-diff": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", - "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==" - }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "license": "MIT" }, "node_modules/jwa": { "version": "2.0.1", @@ -10775,6 +8893,7 @@ "version": "0.14.3", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "license": "MIT", "dependencies": { "@samverschueren/stream-to-observable": "^0.3.0", "is-observable": "^1.1.0", @@ -10794,6 +8913,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", "integrity": "sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10802,6 +8922,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "license": "MIT", "dependencies": { "chalk": "^1.1.3", "cli-truncate": "^0.2.1", @@ -10823,6 +8944,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10831,6 +8953,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10839,6 +8962,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -10854,6 +8978,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10862,6 +8987,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5", "object-assign": "^4.1.0" @@ -10874,6 +9000,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10882,6 +9009,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -10893,6 +9021,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10901,6 +9030,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "license": "MIT", "dependencies": { "chalk": "^2.4.1", "cli-cursor": "^2.1.0", @@ -10915,6 +9045,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -10926,6 +9057,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10939,6 +9071,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "license": "MIT", "dependencies": { "restore-cursor": "^2.0.0" }, @@ -10950,6 +9083,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -10957,12 +9091,14 @@ "node_modules/listr-verbose-renderer/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -10971,6 +9107,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -10982,6 +9119,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10990,6 +9128,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10998,6 +9137,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "license": "MIT", "dependencies": { "mimic-fn": "^1.0.0" }, @@ -11009,6 +9149,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "license": "MIT", "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -11020,12 +9161,14 @@ "node_modules/listr-verbose-renderer/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/listr-verbose-renderer/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -11057,9 +9200,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -11069,7 +9213,8 @@ "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" }, "node_modules/lodash.flatten": { "version": "4.4.0", @@ -11079,7 +9224,8 @@ "node_modules/lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", - "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -11105,7 +9251,8 @@ "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", @@ -11115,7 +9262,8 @@ "node_modules/lodash.isnil": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", - "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", @@ -11161,12 +9309,14 @@ "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" }, "node_modules/log-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==", + "license": "MIT", "dependencies": { "chalk": "^1.0.0" }, @@ -11178,6 +9328,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11186,6 +9337,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11194,6 +9346,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -11209,6 +9362,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -11217,6 +9371,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", "dependencies": { "ansi-regex": "^2.0.0" }, @@ -11228,6 +9383,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -11236,6 +9392,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", + "license": "MIT", "dependencies": { "ansi-escapes": "^3.0.0", "cli-cursor": "^2.0.0", @@ -11249,6 +9406,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -11257,6 +9415,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "license": "MIT", "engines": { "node": ">=4" } @@ -11265,6 +9424,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "license": "MIT", "dependencies": { "restore-cursor": "^2.0.0" }, @@ -11276,6 +9436,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "license": "MIT", "engines": { "node": ">=4" } @@ -11284,6 +9445,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -11292,6 +9454,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "license": "MIT", "dependencies": { "mimic-fn": "^1.0.0" }, @@ -11303,6 +9466,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "license": "MIT", "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -11314,12 +9478,14 @@ "node_modules/log-update/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/log-update/node_modules/string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "license": "MIT", "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -11332,6 +9498,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "license": "MIT", "dependencies": { "ansi-regex": "^3.0.0" }, @@ -11343,6 +9510,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", + "license": "MIT", "dependencies": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0" @@ -11367,6 +9535,12 @@ "node": ">= 12.0.0" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11397,6 +9571,21 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, + "node_modules/lru.min": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", + "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -11409,6 +9598,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -11435,7 +9625,8 @@ "node_modules/merge": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz", - "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==" + "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==", + "license": "MIT" }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -11556,26 +9747,15 @@ } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, "node_modules/mixme": { @@ -11613,6 +9793,38 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mysql2": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.2.tgz", + "integrity": "sha512-JsqBpYNy7pH20lGfPuSyRSIcCxSeAIwxWADpV64nP9KeyN3ZKpHZgjKXuBKsh7dH6FbOvf1bOgoVKjSUPXRMTw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.3", + "named-placeholders": "^1.1.6", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.3" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11632,22 +9844,11 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dependencies": { - "@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": "^8.1.0" - } - }, "node_modules/nock": { "version": "13.5.6", "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", + "license": "MIT", "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", @@ -14224,6 +12425,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14377,6 +12579,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/omit-deep-lodash/-/omit-deep-lodash-1.1.7.tgz", "integrity": "sha512-9m9gleSMoxq3YO8aCq5pGUrqG9rKF0w/P70JHQ1ymjUQA/3+fVa2Stju9XORJKLmyLYEO3zzX40MJYaYl5Og4w==", + "license": "MIT", "dependencies": { "lodash": "~4.17.21" }, @@ -14524,6 +12727,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14566,6 +12770,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -14630,6 +12835,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -14755,6 +12961,15 @@ "node": ">=8" } }, + "node_modules/php-serialize": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/php-serialize/-/php-serialize-5.1.3.tgz", + "integrity": "sha512-p7zXX8xjGgddgP6byN+KmGKM0x6uoMZBRZteBa9LonqgrDV3LyMxUeGVX7RTFYwWaUAnTEsUWJfHI3N7eKvJgw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -14881,12 +13096,14 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/progress-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-2.0.0.tgz", "integrity": "sha512-xJwOWR46jcXUq6EH9yYyqp+I52skPySOeHfkxOZ2IY1AiBi/sFJhbhAKHoV3OTw/omQ45KTio9215dRJ2Yxd3Q==", + "license": "BSD-2-Clause", "dependencies": { "speedometer": "~1.0.0", "through2": "~2.0.3" @@ -14895,12 +13112,14 @@ "node_modules/progress-stream/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/progress-stream/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14914,12 +13133,14 @@ "node_modules/progress-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/progress-stream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -14928,6 +13149,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -14936,12 +13158,14 @@ "node_modules/promise-limit": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", - "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" }, "node_modules/prompt": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", + "license": "MIT", "dependencies": { "@colors/colors": "1.5.0", "async": "3.2.3", @@ -14957,6 +13181,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -14964,12 +13189,14 @@ "node_modules/prompt/node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "license": "MIT" }, "node_modules/prompt/node_modules/winston": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz", "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==", + "license": "MIT", "dependencies": { "async": "^2.6.4", "colors": "1.0.x", @@ -14986,6 +13213,7 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } @@ -15004,6 +13232,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "license": "MIT", "engines": { "node": ">= 8" } @@ -15133,6 +13362,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "license": "ISC", "dependencies": { "mute-stream": "~0.0.4" }, @@ -15143,7 +13373,8 @@ "node_modules/read/node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" }, "node_modules/readable-stream": { "version": "3.6.2", @@ -15251,6 +13482,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "license": "MIT", "dependencies": { "esprima": "~4.0.0" } @@ -15259,6 +13491,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -15417,6 +13650,7 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", "integrity": "sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==", + "license": "Apache 2.0", "engines": { "node": ">= 0.4.0" } @@ -15425,6 +13659,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz", "integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==", + "license": "BlueOak-1.0.0", "dependencies": { "glob": "^13.0.0", "package-json-from-dist": "^1.0.1" @@ -15440,11 +13675,12 @@ } }, "node_modules/rimraf/node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", + "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.1", + "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" }, @@ -15456,11 +13692,12 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.1" }, "engines": { "node": "20 || >=22" @@ -15735,6 +13972,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", @@ -15940,15 +14182,15 @@ } }, "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==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", + "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.5", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", + "@sinonjs/fake-timers": "^15.1.0", + "@sinonjs/samsam": "^8.0.3", + "diff": "^8.0.2", "supports-color": "^7.2.0" }, "funding": { @@ -15960,6 +14202,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -15990,6 +14233,7 @@ "version": "0.0.4", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "integrity": "sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -16192,7 +14436,17 @@ "node_modules/speedometer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz", - "integrity": "sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw==" + "integrity": "sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw==", + "license": "MIT" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/stack-trace": { "version": "0.0.10", @@ -16294,6 +14548,19 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -16498,39 +14765,19 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/text-hex": { @@ -16561,6 +14808,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "license": "MIT", "dependencies": { "inherits": "^2.0.4", "readable-stream": "2 || 3" @@ -16590,6 +14838,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", "engines": { "node": ">=14.14" } @@ -16767,6 +15016,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -16867,7 +15117,8 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" }, "node_modules/typedarray.prototype.slice": { "version": "1.0.5", @@ -17039,6 +15290,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -17366,6 +15618,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", "engines": { "node": ">=0.4" } @@ -17379,9 +15632,13 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yargs": { "version": "17.7.2", diff --git a/api/package.json b/api/package.json index f5bba717b..c151afbb2 100644 --- a/api/package.json +++ b/api/package.json @@ -25,10 +25,10 @@ }, "homepage": "https://github.com/contentstack/migration-v2.git#readme", "dependencies": { - "@contentstack/cli": "^1.53.1", - "@contentstack/cli-utilities": "^1.16.0", - "@contentstack/json-rte-serializer": "^3.0.4", - "@contentstack/marketplace-sdk": "^1.4.1", + "@contentstack/cli": "^1.57.0", + "@contentstack/cli-utilities": "^1.17.1", + "@contentstack/json-rte-serializer": "^3.0.5", + "@contentstack/marketplace-sdk": "^1.5.0", "axios": "^1.12.0", "chokidar": "^3.6.0", "cors": "^2.8.5", @@ -48,8 +48,10 @@ "jsonwebtoken": "^9.0.3", "lowdb": "^7.0.1", "mkdirp": "^3.0.1", + "mysql2": "^3.16.2", "p-limit": "^6.2.0", "path-to-regexp": "^8.2.0", + "php-serialize": "^5.1.3", "router": "^2.0.0", "shelljs": "^0.9.0", "socket.io": "^4.7.5", diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index 777712bd6..52351c6d2 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -1,29 +1,38 @@ -export const CS_REGIONS = ["NA", "EU", "AZURE_NA", "AZURE_EU", "GCP_NA", "AU", "GCP_EU"]; +export const CS_REGIONS = [ + 'NA', + 'EU', + 'AZURE_NA', + 'AZURE_EU', + 'GCP_NA', + 'AU', + 'GCP_EU', +]; export const DEVURLS: any = { - NA: "developerhub-api.contentstack.com", - EU: "eu-developerhub-api.contentstack.com", - AZURE_NA: "azure-na-developerhub-api.contentstack.com", - AZURE_EU: "azure-eu-developerhub-api.contentstack.com", - GCP_NA: "gcp-na-developerhub-api.contentstack.com", - AU: "au-developerhub-api.contentstack.com", - GCP_EU: "gcp-eu-developerhub-api.contentstack.com", + NA: 'developerhub-api.contentstack.com', + EU: 'eu-developerhub-api.contentstack.com', + AZURE_NA: 'azure-na-developerhub-api.contentstack.com', + AZURE_EU: 'azure-eu-developerhub-api.contentstack.com', + GCP_NA: 'gcp-na-developerhub-api.contentstack.com', + AU: 'au-developerhub-api.contentstack.com', + GCP_EU: 'gcp-eu-developerhub-api.contentstack.com', }; export const CMS = { - CONTENTFUL: "contentful", - SITECORE_V8: "sitecore v8", - SITECORE_V9: "sitecore v9", - SITECORE_V10: "sitecore v10", - WORDPRESS: "wordpress", - AEM: "aem", + CONTENTFUL: 'contentful', + SITECORE_V8: 'sitecore v8', + SITECORE_V9: 'sitecore v9', + SITECORE_V10: 'sitecore v10', + WORDPRESS: 'wordpress', + DRUPAL: 'drupal', + AEM: 'aem', }; export const MODULES = [ - "Project", - "Migration", - "Content Mapping", - "Legacy CMS", - "Destination Stack", + 'Project', + 'Migration', + 'Content Mapping', + 'Legacy CMS', + 'Destination Stack', ]; -export const MODULES_ACTIONS = ["Create", "Update", "Delete"]; +export const MODULES_ACTIONS = ['Create', 'Update', 'Delete']; export const AXIOS_TIMEOUT = 60 * 1000; export const HTTP_CODES = { OK: 200, @@ -40,19 +49,19 @@ export const HTTP_CODES = { }; export const HTTP_TEXTS = { UNAUTHORIZED: "You're unauthorized to access this resource.", - S3_ERROR: "Something went wrong while handing the file", - INTERNAL_ERROR: "Internal server error, please try again later.", + S3_ERROR: 'Something went wrong while handing the file', + INTERNAL_ERROR: 'Internal server error, please try again later.', SOMETHING_WENT_WRONG: - "Something went wrong while processing your request, please try again.", - CS_ERROR: "Contentstack API error", - NO_CS_USER: "No user found with the credentials", - SUCCESS_LOGIN: "Login Successful.", - TOKEN_ERROR: "Error occurred during token generation.", - LOGIN_ERROR: "Error occurred during login", - ROUTE_ERROR: "Sorry, the requested resource is not available.", - PROJECT_NOT_FOUND: "Sorry, the requested project does not exists.", - PROJECT_CREATION_FAILED: "Error occurred while creating project.", - NO_PROJECT: "resource not found with the given ID(s).", + 'Something went wrong while processing your request, please try again.', + CS_ERROR: 'Contentstack API error', + NO_CS_USER: 'No user found with the credentials', + SUCCESS_LOGIN: 'Login Successful.', + TOKEN_ERROR: 'Error occurred during token generation.', + LOGIN_ERROR: 'Error occurred during login', + ROUTE_ERROR: 'Sorry, the requested resource is not available.', + PROJECT_NOT_FOUND: 'Sorry, the requested project does not exists.', + PROJECT_CREATION_FAILED: 'Error occurred while creating project.', + NO_PROJECT: 'resource not found with the given ID(s).', AFFIX_UPDATED: "Project's Affix updated successfully", AFFIX_CONFIRMATION_UPDATED: "Project's Affix confirmation updated successfully", @@ -65,82 +74,82 @@ export const HTTP_TEXTS = { FILE_FORMAT_UPDATED: "Project's migration file format updated successfully", DESTINATION_STACK_UPDATED: "Project's migration destination stack updated successfully", - DESTINATION_STACK_NOT_FOUND: "Destination stack does not exist", - DESTINATION_STACK_ERROR: "Error occurred during verifying destination stack", - INVALID_ID: "Provided $ ID is invalid.", - CONTENT_TYPE_NOT_FOUND: "ContentType does not exist", - CONTENT_TYPE_MISSING: "ContentType is missing in request.", - INVALID_CONTENT_TYPE: "Provide valid ContentType data", + DESTINATION_STACK_NOT_FOUND: 'Destination stack does not exist', + DESTINATION_STACK_ERROR: 'Error occurred during verifying destination stack', + INVALID_ID: 'Provided $ ID is invalid.', + CONTENT_TYPE_NOT_FOUND: 'ContentType does not exist', + CONTENT_TYPE_MISSING: 'ContentType is missing in request.', + INVALID_CONTENT_TYPE: 'Provide valid ContentType data', RESET_CONTENT_MAPPING: - "ContentType has been successfully restored to its initial mapping", - UPLOAD_SUCCESS: "File uploaded successfully", + 'ContentType has been successfully restored to its initial mapping', + UPLOAD_SUCCESS: 'File uploaded successfully', CANNOT_UPDATE_LEGACY_CMS: - "Updating the legacy CMS is not possible as the migration process is either in progress or has already been successfully completed.", + 'Updating the legacy CMS is not possible as the migration process is either in progress or has already been successfully completed.', CANNOT_UPDATE_FILE_FORMAT: - "Updating the file format is not possible as the migration process is either in progress or has already been successfully completed.", + 'Updating the file format is not possible as the migration process is either in progress or has already been successfully completed.', CANNOT_UPDATE_DESTINATION_STACK: - "Updating the destination stack is restricted. Please verify the status and review preceding actions.", + 'Updating the destination stack is restricted. Please verify the status and review preceding actions.', CANNOT_PROCEED_LEGACY_CMS: - "You cannot proceed if the project is not in draft or if any Legacy CMS details are missing.", + 'You cannot proceed if the project is not in draft or if any Legacy CMS details are missing.', CANNOT_PROCEED_DESTINATION_STACK: - "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack details are missing.", + 'You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack details are missing.', CANNOT_PROCEED_CONTENT_MAPPING: - "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping details are missing.", + 'You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping details are missing.', CANNOT_PROCEED_TEST_MIGRATION: - "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing.", + 'You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing.', CANNOT_PROCEED_MIGRATION: - "You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing or Migration is not completed.", + 'You cannot proceed if the project is not in draft or if any Legacy CMS or Destination Stack or Content Mapping or Test Migration details are missing or Migration is not completed.', CANNOT_UPDATE_CONTENT_MAPPING: - "Updating the content mapping is restricted. Please verify the status and review preceding actions.", + 'Updating the content mapping is restricted. Please verify the status and review preceding actions.', CANNOT_RESET_CONTENT_MAPPING: - "Reseting the content mapping is restricted. Please verify the status and review preceding actions.", + 'Reseting the content mapping is restricted. Please verify the status and review preceding actions.', CONTENTMAPPER_NOT_FOUND: - "Sorry, the requested content mapper id does not exists.", + 'Sorry, the requested content mapper id does not exists.', ADMIN_LOGIN_ERROR: "Sorry, You Don't have admin access in any of the Organisation", - PROJECT_DELETE: "Project Deleted Successfully", - PROJECT_REVERT: "Project Reverted Successfully", - LOGS_NOT_FOUND: "Sorry, no logs found for requested stack migration.", + PROJECT_DELETE: 'Project Deleted Successfully', + PROJECT_REVERT: 'Project Reverted Successfully', + LOGS_NOT_FOUND: 'Sorry, no logs found for requested stack migration.', MIGRATION_EXECUTION_KEY_UPDATED: "Project's migration execution key updated successfully", - CONTENT_TYPE_INVALID: "Invalid contentTypes: Expected an array." + CONTENT_TYPE_INVALID: 'Invalid contentTypes: Expected an array.', }; export const HTTP_RESPONSE_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - Connection: "close", + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + Connection: 'close', }; export const METHODS_TO_INCLUDE_DATA_IN_AXIOS = [ - "PUT", - "POST", - "DELETE", - "PATCH", + 'PUT', + 'POST', + 'DELETE', + 'PATCH', ]; export const VALIDATION_ERRORS = { - INVALID_EMAIL: "Given email ID is invalid.", + INVALID_EMAIL: 'Given email ID is invalid.', EMAIL_LIMIT: "Email's max limit reached.", LENGTH_LIMIT: "$'s max limit reached.", - STRING_REQUIRED: "Provided $ should be a string.", - BOOLEAN_REQUIRED: "Provided $ should be a boolean.", + STRING_REQUIRED: 'Provided $ should be a string.', + BOOLEAN_REQUIRED: 'Provided $ should be a boolean.', INVALID_REGION: "Provided region doesn't exists.", FIELD_REQUIRED: "Field '$' is required.", - INVALID_AFFIX: "Invalid affix format", + INVALID_AFFIX: 'Invalid affix format', }; -export const POPULATE_CONTENT_MAPPER = "content_mapper"; -export const POPULATE_FIELD_MAPPING = "fieldMapping"; +export const POPULATE_CONTENT_MAPPER = 'content_mapper'; +export const POPULATE_FIELD_MAPPING = 'fieldMapping'; export const CONTENT_TYPE_POPULATE_FIELDS = - "otherCmsTitle otherCmsUid isUpdated updateAt contentstackTitle contentstackUid"; + 'otherCmsTitle otherCmsUid isUpdated updateAt contentstackTitle contentstackUid'; export const PROJECT_UNSELECTED_FIELDS = - "-content_mapper -legacy_cms -destination_stack_id -execution_log"; -export const EXCLUDE_CONTENT_MAPPER = "-content_mapper -execution_log"; + '-content_mapper -legacy_cms -destination_stack_id -execution_log'; +export const EXCLUDE_CONTENT_MAPPER = '-content_mapper -execution_log'; export const AFFIX_REGEX = /^[a-zA-Z][a-zA-Z0-9]{1,4}$/; export const PROJECT_STATUS = { - DRAFT: "Draft", - READY: "Ready", - INPROGRESS: "InProgress", - FAILED: "Failed", - SUCCESS: "Success", + DRAFT: 'Draft', + READY: 'Ready', + INPROGRESS: 'InProgress', + FAILED: 'Failed', + SUCCESS: 'Success', }; export const STEPPER_STEPS: any = { LEGACY_CMS: 1, @@ -150,11 +159,11 @@ export const STEPPER_STEPS: any = { MIGRATION: 5, }; export const PREDEFINED_STATUS = [ - "Draft", - "Ready", - "InProgress", - "Failed", - "Success", + 'Draft', + 'Ready', + 'InProgress', + 'Failed', + 'Success', ]; export const PREDEFINED_STEPS = [1, 2, 3, 4, 5]; @@ -178,123 +187,123 @@ export const CONTENT_TYPE_STATUS = { export const LOCALE_MAPPER: any = { //not more than one locale mapping in master locale masterLocale: { - "en-us": "en", + 'en-us': 'en', }, - locales: { fr: "fr-fr", } + locales: { fr: 'fr-fr' }, }; export const CHUNK_SIZE = 1048576; -export const LIST_EXTENSION_UID = "bltc44e51cc9f4b0d80"; +export const LIST_EXTENSION_UID = 'bltc44e51cc9f4b0d80'; export const KEYTOREMOVE = [ - "update", - "fetch", - "delete", - "oauth", - "hosting", - "install", - "reinstall", - "upgrade", - "getRequests", - "authorize", - "authorization", - "listInstallations", + 'update', + 'fetch', + 'delete', + 'oauth', + 'hosting', + 'install', + 'reinstall', + 'upgrade', + 'getRequests', + 'authorize', + 'authorization', + 'listInstallations', ]; export const MIGRATION_DATA_CONFIG = { - DATA: "./cmsMigrationData", + DATA: './cmsMigrationData', - BACKUP_DATA: "migration-data", - BACKUP_LOG_DIR: "logs", - BACKUP_FOLDER_NAME: "import", - BACKUP_FILE_NAME: "success.log", + BACKUP_DATA: 'migration-data', + BACKUP_LOG_DIR: 'logs', + BACKUP_FOLDER_NAME: 'import', + BACKUP_FILE_NAME: 'success.log', - LOCALE_DIR_NAME: "locales", - LOCALE_FILE_NAME: "locales.json", - LOCALE_MASTER_LOCALE: "master-locale.json", - LOCALE_CF_LANGUAGE: "language.json", + LOCALE_DIR_NAME: 'locales', + LOCALE_FILE_NAME: 'locales.json', + LOCALE_MASTER_LOCALE: 'master-locale.json', + LOCALE_CF_LANGUAGE: 'language.json', - WEBHOOKS_DIR_NAME: "webhooks", - WEBHOOKS_FILE_NAME: "webhooks.json", + WEBHOOKS_DIR_NAME: 'webhooks', + WEBHOOKS_FILE_NAME: 'webhooks.json', - ENVIRONMENTS_DIR_NAME: "environments", - ENVIRONMENTS_FILE_NAME: "environments.json", + ENVIRONMENTS_DIR_NAME: 'environments', + ENVIRONMENTS_FILE_NAME: 'environments.json', - CONTENT_TYPES_DIR_NAME: "content_types", - EXTENSIONS_MAPPER_DIR_NAME: "extension-mapper.json", - CUSTOM_MAPPER_FILE_NAME: "custmon-mapper.json", - CONTENT_TYPES_FILE_NAME: "contenttype.json", - CONTENT_TYPES_MASTER_FILE: "contenttypes.json", - CONTENT_TYPES_SCHEMA_FILE: "schema.json", - MARKETPLACE_APPS_DIR_NAME: "marketplace_apps", - MARKETPLACE_APPS_FILE_NAME: "marketplace_apps.json", - EXTENSION_APPS_DIR_NAME: "extensions", - EXTENSION_APPS_FILE_NAME: "extensions.json", - REFERENCES_DIR_NAME: "reference", - REFERENCES_FILE_NAME: "reference.json", - TAXONOMIES_DIR_NAME: "taxonomies", - TAXONOMIES_FILE_NAME: "taxonomies.json", + CONTENT_TYPES_DIR_NAME: 'content_types', + EXTENSIONS_MAPPER_DIR_NAME: 'extension-mapper.json', + CUSTOM_MAPPER_FILE_NAME: 'custmon-mapper.json', + CONTENT_TYPES_FILE_NAME: 'contenttype.json', + CONTENT_TYPES_MASTER_FILE: 'contenttypes.json', + CONTENT_TYPES_SCHEMA_FILE: 'schema.json', + MARKETPLACE_APPS_DIR_NAME: 'marketplace_apps', + MARKETPLACE_APPS_FILE_NAME: 'marketplace_apps.json', + EXTENSION_APPS_DIR_NAME: 'extensions', + EXTENSION_APPS_FILE_NAME: 'extensions.json', + REFERENCES_DIR_NAME: 'reference', + REFERENCES_FILE_NAME: 'reference.json', + TAXONOMIES_DIR_NAME: 'taxonomies', + TAXONOMIES_FILE_NAME: 'taxonomies.json', - RTE_REFERENCES_DIR_NAME: "rteReference", - RTE_REFERENCES_FILE_NAME: "rteReference.json", + RTE_REFERENCES_DIR_NAME: 'rteReference', + RTE_REFERENCES_FILE_NAME: 'rteReference.json', - ASSETS_DIR_NAME: "assets", - ASSETS_FILE_NAME: "assets.json", + ASSETS_DIR_NAME: 'assets', + ASSETS_FILE_NAME: 'assets.json', // ASSETS_SCHEMA_FILE : "index.json", - ASSETS_SCHEMA_FILE: "index.json", - ASSETS_FAILED_FILE: "cs_failed.json", - ASSETS_METADATA_FILE: "metadata.json", - ASSETS_FOLDER_FILE_NAME: "folders.json", + ASSETS_SCHEMA_FILE: 'index.json', + ASSETS_FAILED_FILE: 'cs_failed.json', + ASSETS_METADATA_FILE: 'metadata.json', + ASSETS_FOLDER_FILE_NAME: 'folders.json', - ENTRIES_DIR_NAME: "entries", - ENTRIES_MASTER_FILE: "index.json", + ENTRIES_DIR_NAME: 'entries', + ENTRIES_MASTER_FILE: 'index.json', - AUTHORS_DIR_NAME: "authors", - AUTHORS_FILE_NAME: "en-us.json", - AUTHORS_MASTER_FILE: "authors.json", + AUTHORS_DIR_NAME: 'authors', + AUTHORS_FILE_NAME: 'en-us.json', + AUTHORS_MASTER_FILE: 'authors.json', - CATEGORIES_DIR_NAME: "categories", - CATEGORIES_FILE_NAME: "en-us.json", - CATEGORIES_MASTER_FILE: "categories.json", + CATEGORIES_DIR_NAME: 'categories', + CATEGORIES_FILE_NAME: 'en-us.json', + CATEGORIES_MASTER_FILE: 'categories.json', - TAG_DIR_NAME: "tag", - TAG_FILE_NAME: "en-us.json", - TAG_MASTER_FILE: "tag.json", + TAG_DIR_NAME: 'tag', + TAG_FILE_NAME: 'en-us.json', + TAG_MASTER_FILE: 'tag.json', - TERMS_DIR_NAME: "terms", - TERMS_FILE_NAME: "en-us.json", - TERMS_MASTER_FILE: "terms.json", + TERMS_DIR_NAME: 'terms', + TERMS_FILE_NAME: 'en-us.json', + TERMS_MASTER_FILE: 'terms.json', - POSTS_DIR_NAME: "posts", - POSTS_FOLDER_NAME: "en-us", - POSTS_FILE_NAME: "en-us.json", - POSTS_MASTER_FILE: "posts.json", + POSTS_DIR_NAME: 'posts', + POSTS_FOLDER_NAME: 'en-us', + POSTS_FILE_NAME: 'en-us.json', + POSTS_MASTER_FILE: 'posts.json', - PAGES_DIR_NAME: "pages", - PAGES_FOLDER_NAME: "en-us", - PAGES_FILE_NAME: "en-us.json", - PAGES_MASTER_FILE: "pages.json", + PAGES_DIR_NAME: 'pages', + PAGES_FOLDER_NAME: 'en-us', + PAGES_FILE_NAME: 'en-us.json', + PAGES_MASTER_FILE: 'pages.json', - CHUNKS_DIR_NAME: "chunks", + CHUNKS_DIR_NAME: 'chunks', - GLOBAL_FIELDS_DIR_NAME: "global_fields", - GLOBAL_FIELDS_FILE_NAME: "globalfields.json", + GLOBAL_FIELDS_DIR_NAME: 'global_fields', + GLOBAL_FIELDS_FILE_NAME: 'globalfields.json', - EXPORT_INFO_FILE: "export-info.json", + EXPORT_INFO_FILE: 'export-info.json', - AEM_DAM_DIR: 'dam-downloads' + AEM_DAM_DIR: 'dam-downloads', }; export const GET_AUDIT_DATA = { - MIGRATION: "migration-v2", - API_DIR: "api", - MIGRATION_DATA_DIR: "migration-data", - LOGS_DIR: "logs", - AUDIT_DIR: "audit", - AUDIT_REPORT: "audit-report", - FILTERALL: "all", -} + MIGRATION: 'migration-v2', + API_DIR: 'api', + MIGRATION_DATA_DIR: 'migration-data', + LOGS_DIR: 'logs', + AUDIT_DIR: 'audit', + AUDIT_REPORT: 'audit-report', + FILTERALL: 'all', +}; export const RESERVED_FIELD_MAPPINGS: Record = { - 'locale': 'cm_locale' + locale: 'cm_locale', // Add other reserved fields if needed -}; \ No newline at end of file +}; diff --git a/api/src/controllers/projects.contentMapper.controller.ts b/api/src/controllers/projects.contentMapper.controller.ts index af49095fa..8b9ca120d 100644 --- a/api/src/controllers/projects.contentMapper.controller.ts +++ b/api/src/controllers/projects.contentMapper.controller.ts @@ -64,6 +64,21 @@ const getExistingGlobalFields = async ( res.status(201).json(resp); }; +/** + * Retrieves existing taxonomies from source and destination. + * + * @param {Request} req - The request object. + * @param {Response} res - The response object. + * @returns {Promise} - A promise that resolves when the operation is complete. + */ +const getExistingTaxonomies = async ( + req: Request, + res: Response +): Promise => { + const resp = await contentMapperService.getExistingTaxonomies(req); + res.status(resp?.status || 200).json(resp); +}; + /** * Updates the content type fields. * @@ -158,6 +173,7 @@ export const contentMapperController = { resetContentType, // removeMapping, getSingleContentTypes, + getExistingTaxonomies, removeContentMapper, updateContentMapper, getExistingGlobalFields, diff --git a/api/src/helper/index.ts b/api/src/helper/index.ts new file mode 100644 index 000000000..e62775bf8 --- /dev/null +++ b/api/src/helper/index.ts @@ -0,0 +1,159 @@ +import mysql from 'mysql2'; +import customLogger from '../utils/custom-logger.utils.js'; + +// Default connection timeout in milliseconds (30 seconds) +const CONNECTION_TIMEOUT_MS = 30000; + +const createDbConnection = async ( + config: any, + projectId: string = '', + stackId: string = '', + timeoutMs: number = CONNECTION_TIMEOUT_MS +): Promise => { + try { + // Create the connection with config values and built-in timeout + const connection = mysql.createConnection({ + host: config?.host, + user: config?.user, + password: config?.password, + database: config?.database, + port: Number(config?.port), + connectTimeout: timeoutMs, // MySQL2 built-in connection timeout + }); + + // Test the connection by wrapping the connect method in a promise with timeout + return new Promise((resolve, reject) => { + let isSettled = false; + let timeoutId: NodeJS.Timeout | null = null; + + // Timeout handler to prevent indefinite hanging + timeoutId = setTimeout(() => { + if (!isSettled) { + isSettled = true; + // Attempt to destroy the connection on timeout + try { + connection.destroy(); + } catch { + // Ignore errors during destroy + } + const timeoutError = new Error( + `Database connection timed out after ${timeoutMs}ms` + ); + customLogger(projectId, stackId, 'error', timeoutError.message).catch( + () => {} + ); + reject(timeoutError); + } + }, timeoutMs); + + connection.connect((err) => { + // Clear timeout since callback was invoked + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + + // Prevent double-settlement if timeout already fired + if (isSettled) { + // Connection callback came after timeout, clean up + try { + connection.destroy(); + } catch { + // Ignore cleanup errors + } + return; + } + isSettled = true; + + if (err) { + // Close the connection properly with callback to prevent resource leaks + // Wait for connection.end() to complete before rejecting + connection.end((endErr) => { + // Log any errors from connection.end but proceed with original error + if (endErr) { + customLogger( + projectId, + stackId, + 'warn', + `Error closing failed connection: ${endErr.message}` + ).catch(() => {}); // Fire-and-forget warning log + } + // Log error then reject - wrapped in try-catch for synchronous exceptions + try { + customLogger( + projectId, + stackId, + 'error', + `Database connection failed: ${err.message}` + ) + .then(() => reject(err)) + .catch(() => reject(err)); // Reject even if logging fails + } catch { + // Handle synchronous exceptions from customLogger itself + reject(err); + } + }); + return; + } + + // Log success then resolve - wrapped in try-catch for synchronous exceptions + // Note: In success path, connection should remain open (returned to caller for use) + try { + customLogger( + projectId, + stackId, + 'info', + 'Database connection established successfully' + ) + .then(() => resolve(connection)) + .catch(() => resolve(connection)); // Resolve even if logging fails + } catch { + // Handle synchronous exceptions from customLogger itself + // Connection remains open intentionally - it's working and returned to caller + try { + resolve(connection); + } catch { + // If resolve itself fails (extremely rare), close connection to prevent leak + connection.end(() => {}); + } + } + }); + }); + } catch (error: any) { + // Handles synchronous errors from mysql.createConnection() or other setup + customLogger( + projectId, + stackId, + 'error', + `Failed to create database connection: ${error.message}` + ).catch(() => { + // Silently ignore logging errors + }); + return null; + } +}; + +// Usage example +const getDbConnection = async ( + config: any, + projectId: string = '', + stackId: string = '' +) => { + try { + const connection = await createDbConnection(config, projectId, stackId); + if (!connection) { + throw new Error('Could not establish database connection'); + } + return connection; + } catch (error: any) { + await customLogger( + projectId, + stackId, + 'error', + `Database connection error: ${error.message}` + ); + throw error; // Re-throw so caller can handle it + } +}; + +export { createDbConnection, getDbConnection }; diff --git a/api/src/routes/contentMapper.routes.ts b/api/src/routes/contentMapper.routes.ts index d9fbfd8c1..86be4fe85 100644 --- a/api/src/routes/contentMapper.routes.ts +++ b/api/src/routes/contentMapper.routes.ts @@ -49,6 +49,15 @@ router.get( asyncRouter(contentMapperController.getExistingGlobalFields) ); +/** + * Get Existing Taxonomies from source and destination + * @route GET /:projectId/taxonomies + */ +router.get( + "/:projectId/taxonomies", + asyncRouter(contentMapperController.getExistingTaxonomies) +); + /** * Update FieldMapping or contentType * @route PUT /contentTypes/:orgId/:projectId/:contentTypeId diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index 9e81fddc8..391e1a404 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -1,9 +1,11 @@ -import { Request } from "express"; -import { getLogMessage, isEmpty, safePromise } from "../utils/index.js"; +import { Request } from 'express'; +import fs from 'fs'; +import path from 'path'; +import { getLogMessage, isEmpty, safePromise } from '../utils/index.js'; import { BadRequestError, ExceptionFunction, -} from "../utils/custom-errors.utils.js"; +} from '../utils/custom-errors.utils.js'; import { HTTP_TEXTS, HTTP_CODES, @@ -11,18 +13,19 @@ import { NEW_PROJECT_STATUS, CONTENT_TYPE_STATUS, VALIDATION_ERRORS, -} from "../constants/index.js"; -import logger from "../utils/logger.js"; -import { config } from "../config/index.js"; -import https from "../utils/https.utils.js"; -import getAuthtoken from "../utils/auth.utils.js"; -import getProjectUtil from "../utils/get-project.utils.js"; -import fetchAllPaginatedData from "../utils/pagination.utils.js"; -import ProjectModelLowdb from "../models/project-lowdb.js"; -import FieldMapperModel from "../models/FieldMapper.js"; -import { v4 as uuidv4 } from "uuid"; -import ContentTypesMapperModelLowdb from "../models/contentTypesMapper-lowdb.js"; -import { ContentTypesMapper } from "../models/contentTypesMapper-lowdb.js"; + MIGRATION_DATA_CONFIG, +} from '../constants/index.js'; +import logger from '../utils/logger.js'; +import { config } from '../config/index.js'; +import https from '../utils/https.utils.js'; +import getAuthtoken from '../utils/auth.utils.js'; +import getProjectUtil from '../utils/get-project.utils.js'; +import fetchAllPaginatedData from '../utils/pagination.utils.js'; +import ProjectModelLowdb from '../models/project-lowdb.js'; +import FieldMapperModel from '../models/FieldMapper.js'; +import { v4 as uuidv4 } from 'uuid'; +import ContentTypesMapperModelLowdb from '../models/contentTypesMapper-lowdb.js'; +import { ContentTypesMapper } from '../models/contentTypesMapper-lowdb.js'; // Developer service to create dummy contentmapping data /** @@ -36,7 +39,6 @@ const putTestData = async (req: Request) => { const contentTypes = req.body.contentTypes; try { - /* this code snippet is iterating over an array called contentTypes and transforming each element by adding a unique identifier (id) if it doesn't already exist. @@ -49,7 +51,7 @@ const putTestData = async (req: Request) => { } const contentIds: any[] = []; const contentType = contentTypes.map((item: any) => { - const id = item?.id?.replace(/[{}]/g, "")?.toLowerCase() || uuidv4(); + const id = item?.id?.replace(/[{}]/g, '')?.toLowerCase() || uuidv4(); item.id = id; contentIds.push(id); return { ...item, id, projectId }; @@ -60,25 +62,23 @@ const putTestData = async (req: Request) => { if (item?.advanced) { item.advanced.initial = structuredClone(item?.advanced); } - if(item?.refrenceTo) { + if (item?.refrenceTo) { item.initialRefrenceTo = item?.refrenceTo; } }); }); - - const sanitizeObject = (obj: Record) => { const blockedKeys = ['__proto__', 'prototype', 'constructor']; const safeObj: Record = {}; - + for (const key in obj) { if (!blockedKeys.includes(key)) { safeObj[key] = obj[key]; } } return safeObj; - }; + }; /* this code snippet iterates over an array of contentTypes and performs @@ -92,37 +92,34 @@ const putTestData = async (req: Request) => { await FieldMapperModel.read(); contentTypes.forEach((type: any, index: number) => { const fieldIds: string[] = []; - - const fields = Array.isArray(type?.fieldMapping) ? - type.fieldMapping - .filter(Boolean) - .map((field: any) => { - const safeField = sanitizeObject(field); - - const id = - safeField?.id ? - safeField.id.replace(/[{}]/g, '').toLowerCase() - : uuidv4(); - safeField.id = id; - - fieldIds.push(id); - - return { - ...safeField, - id, - projectId, - contentTypeId: type?.id, - isDeleted: false, - }; - }) + + const fields = Array.isArray(type?.fieldMapping) + ? type.fieldMapping.filter(Boolean).map((field: any) => { + const safeField = sanitizeObject(field); + + const id = safeField?.id + ? safeField.id.replace(/[{}]/g, '').toLowerCase() + : uuidv4(); + safeField.id = id; + + fieldIds.push(id); + + return { + ...safeField, + id, + projectId, + contentTypeId: type?.id, + isDeleted: false, + }; + }) : []; - + FieldMapperModel.update((data: any) => { data.field_mapper = [ ...(Array.isArray(data?.field_mapper) ? data.field_mapper : []), ...fields, ]; - }); + }); if ( Array?.isArray?.(contentType) && Number?.isInteger?.(index) && @@ -133,8 +130,6 @@ const putTestData = async (req: Request) => { } }); - - await ContentTypesMapperModelLowdb.update((data: any) => { data.ContentTypesMappers = [ ...(data?.ContentTypesMappers ?? []), @@ -144,36 +139,65 @@ const putTestData = async (req: Request) => { await ProjectModelLowdb.read(); const index = ProjectModelLowdb.chain - .get("projects") + .get('projects') .findIndex({ id: projectId }) .value(); if (index > -1 && contentIds?.length) { ProjectModelLowdb.data.projects[index].content_mapper = contentIds; - ProjectModelLowdb.data.projects[index].extract_path = req?.body?.extractPath; + ProjectModelLowdb.data.projects[index].extract_path = + req?.body?.extractPath; + + // Update assetsConfig if provided (for Drupal asset URL configuration) + if ( + req?.body?.assetsConfig && + ProjectModelLowdb.data.projects[index].legacy_cms + ) { + ( + ProjectModelLowdb.data.projects[index].legacy_cms as any + ).assetsConfig = req.body.assetsConfig; + } + + // Update mySQLDetails if provided (for Drupal database connection) + if ( + req?.body?.mySQLDetails && + ProjectModelLowdb.data.projects[index].legacy_cms + ) { + ( + ProjectModelLowdb.data.projects[index].legacy_cms as any + ).mySQLDetails = req.body.mySQLDetails; + // Set is_sql flag when MySQL details are provided + (ProjectModelLowdb.data.projects[index].legacy_cms as any).is_sql = + true; + } + + // Store taxonomies if provided + if (req?.body?.taxonomies && Array.isArray(req.body.taxonomies)) { + ProjectModelLowdb.data.projects[index].taxonomies = req.body.taxonomies; + logger.info( + `✓ Stored ${req.body.taxonomies.length} taxonomies for project ${projectId}`, + ); + } + await ProjectModelLowdb.write(); } else { throw new BadRequestError(HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND); } const pData = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); return { status: HTTP_CODES?.OK, - data: pData - } - + data: pData, + }; } catch (error: any) { - throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); - } - }; /** @@ -182,7 +206,7 @@ const putTestData = async (req: Request) => { * @returns An object containing the total count and the array of content types. */ const getContentTypes = async (req: Request) => { - const sourceFn = "getContentTypes"; + const sourceFn = 'getContentTypes'; const projectId = req?.params?.projectId; const skip: any = req?.params?.skip; const limit: any = req?.params?.limit; @@ -193,7 +217,7 @@ const getContentTypes = async (req: Request) => { try { await ProjectModelLowdb.read(); const projectDetails = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); @@ -201,8 +225,8 @@ const getContentTypes = async (req: Request) => { logger.error( getLogMessage( sourceFn, - `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}` - ) + `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.PROJECT_NOT_FOUND); } @@ -211,22 +235,37 @@ const getContentTypes = async (req: Request) => { await FieldMapperModel.read(); const content_mapper: any = []; + logger.info( + `📦 [getContentTypes] Looking for content mappers with projectId: ${projectId}`, + ); + logger.info( + `📦 [getContentTypes] contentMapperId array: ${JSON.stringify( + contentMapperId, + )}`, + ); + contentMapperId.map((data: any) => { const contentMapperData = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: data, projectId: projectId }) .value(); - content_mapper.push(contentMapperData); + if (contentMapperData) { + content_mapper.push(contentMapperData); + } }); + logger.info( + `📦 [getContentTypes] Found ${content_mapper.length} content types`, + ); + if (!isEmpty(content_mapper)) { if (search) { const filteredResult = content_mapper .filter((item: any) => - item?.otherCmsTitle?.toLowerCase().includes(search) + item?.otherCmsTitle?.toLowerCase().includes(search), ) ?.sort((a: any, b: any) => - a.otherCmsTitle.localeCompare(b.otherCmsTitle) + a.otherCmsTitle.localeCompare(b.otherCmsTitle), ); totalCount = filteredResult.length; result = filteredResult.slice(skip, Number(skip) + Number(limit)); @@ -234,7 +273,7 @@ const getContentTypes = async (req: Request) => { totalCount = content_mapper.length; result = content_mapper ?.sort((a: any, b: any) => - a.otherCmsTitle.localeCompare(b.otherCmsTitle) + a.otherCmsTitle.localeCompare(b.otherCmsTitle), ) ?.slice(skip, Number(skip) + Number(limit)); } @@ -243,27 +282,23 @@ const getContentTypes = async (req: Request) => { return { status: HTTP_CODES?.OK, count: totalCount, - contentTypes: result + contentTypes: result, }; - } catch (error: any) { // Log error message logger.error( getLogMessage( sourceFn, - "Error occurred while while getting contentTypes of projects", - error - ) + 'Error occurred while while getting contentTypes of projects', + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); - } - - }; /** @@ -273,7 +308,7 @@ const getContentTypes = async (req: Request) => { * @throws BadRequestError if the content type is not found. */ const getFieldMapping = async (req: Request) => { - const srcFunc = "getFieldMapping"; + const srcFunc = 'getFieldMapping'; const contentTypeId = req?.params?.contentTypeId; const projectId = req?.params?.projectId; const skip: any = req?.params?.skip; @@ -288,7 +323,7 @@ const getFieldMapping = async (req: Request) => { await ContentTypesMapperModelLowdb.read(); const contentType = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: contentTypeId, projectId: projectId }) .value(); @@ -296,16 +331,20 @@ const getFieldMapping = async (req: Request) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}` - ) + `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND); } await FieldMapperModel.read(); const fieldData = contentType?.fieldMapping?.map?.((fields: any) => { const fieldMapper = FieldMapperModel.chain - .get("field_mapper") - .find({ id: fields, projectId: projectId, contentTypeId: contentTypeId }) + .get('field_mapper') + .find({ + id: fields, + projectId: projectId, + contentTypeId: contentTypeId, + }) .value(); return fieldMapper; @@ -321,7 +360,7 @@ const getFieldMapping = async (req: Request) => { if (!isEmpty(fieldMapping)) { if (search) { filteredResult = fieldMapping?.filter?.((item: any) => - item?.otherCmsField?.toLowerCase().includes(search) + item?.otherCmsField?.toLowerCase().includes(search), ); totalCount = filteredResult.length; result = filteredResult.slice(skip, Number(skip) + Number(limit)); @@ -334,26 +373,23 @@ const getFieldMapping = async (req: Request) => { return { status: HTTP_CODES?.OK, count: totalCount, - fieldMapping: result + fieldMapping: result, }; - } catch (error: any) { // Log error message logger.error( getLogMessage( srcFunc, - "Error occurred while getting field mapping of projects", - error - ) + 'Error occurred while getting field mapping of projects', + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); - } - }; /** @@ -369,12 +405,12 @@ const getExistingContentTypes = async (req: Request) => { const authtoken = await getAuthtoken( token_payload?.region, - token_payload?.user_id + token_payload?.user_id, ); await ProjectModelLowdb.read(); const project = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); const stackId = project?.destination_stack_id; @@ -395,7 +431,7 @@ const getExistingContentTypes = async (req: Request) => { headers, 100, 'getExistingContentTypes', - 'content_types' + 'content_types', ); const processedContentTypes = contentTypes.map((singleCT: any) => ({ @@ -413,10 +449,9 @@ const getExistingContentTypes = async (req: Request) => { method: 'GET', url: `${baseUrl}/${contentTypeUID}`, headers, - }) + }), ); - selectedContentType = { title: res?.data?.content_type?.title, uid: res?.data?.content_type?.uid, @@ -461,7 +496,10 @@ const getExistingGlobalFields = async (req: Request) => { } try { - const authtoken = await getAuthtoken(tokenPayload.region, tokenPayload.user_id); + const authtoken = await getAuthtoken( + tokenPayload.region, + tokenPayload.user_id, + ); await ProjectModelLowdb.read(); const project = ProjectModelLowdb.chain @@ -485,7 +523,9 @@ const getExistingGlobalFields = async (req: Request) => { }; } - const baseUrl = `${config.CS_API[tokenPayload.region as keyof typeof config.CS_API]}/global_fields`; + const baseUrl = `${ + config.CS_API[tokenPayload.region as keyof typeof config.CS_API] + }/global_fields`; const headers = { api_key: stackId, authtoken, @@ -493,7 +533,13 @@ const getExistingGlobalFields = async (req: Request) => { // Step 1: Fetch the updated list of all global fields - const globalFields = await fetchAllPaginatedData(baseUrl, headers, 100, 'getExistingGlobalFields', 'global_fields'); + const globalFields = await fetchAllPaginatedData( + baseUrl, + headers, + 100, + 'getExistingGlobalFields', + 'global_fields', + ); const processedGlobalFields = globalFields.map((global: any) => ({ title: global.title, @@ -510,7 +556,7 @@ const getExistingGlobalFields = async (req: Request) => { method: 'GET', url: `${baseUrl}/${globalFieldUID}`, headers, - }) + }), ); // if (err) { @@ -545,7 +591,7 @@ const getExistingGlobalFields = async (req: Request) => { * @throws ExceptionFunction if an error occurs while updating the content type. */ const updateContentType = async (req: Request) => { - const srcFun = "updateContentType"; + const srcFun = 'updateContentType'; const { orgId, projectId, contentTypeId } = req.params; const { contentTypeData, token_payload } = req.body; const fieldMapping = contentTypeData?.fieldMapping; @@ -561,7 +607,7 @@ const updateContentType = async (req: Request) => { owner: token_payload?.user_id, }, srcFun, - true + true, )) as number; const project = ProjectModelLowdb.data.projects[projectIndex]; @@ -574,8 +620,8 @@ const updateContentType = async (req: Request) => { getLogMessage( srcFun, HTTP_TEXTS.CANNOT_UPDATE_CONTENT_MAPPING, - token_payload - ) + token_payload, + ), ); return { status: 400, @@ -588,8 +634,8 @@ const updateContentType = async (req: Request) => { logger.error( getLogMessage( srcFun, - `${HTTP_TEXTS.INVALID_CONTENT_TYPE} Id: ${contentTypeId}` - ) + `${HTTP_TEXTS.INVALID_CONTENT_TYPE} Id: ${contentTypeId}`, + ), ); return { status: 400, @@ -600,7 +646,7 @@ const updateContentType = async (req: Request) => { try { await ContentTypesMapperModelLowdb.read(); const updateIndex = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .findIndex({ id: contentTypeId, projectId: projectId }) .value(); @@ -608,18 +654,18 @@ const updateContentType = async (req: Request) => { for (const field of fieldMapping) { if ( !field.contentstackFieldType || - field.contentstackFieldType === "" || - field.contentstackFieldType === "No matches found" || - field.contentstackFieldUid === "" + field.contentstackFieldType === '' || + field.contentstackFieldType === 'No matches found' || + field.contentstackFieldUid === '' ) { logger.error( getLogMessage( srcFun, `${VALIDATION_ERRORS.STRING_REQUIRED.replace( - "$", - "contentstackFieldType or contentstackFieldUid" - )}` - ) + '$', + 'contentstackFieldType or contentstackFieldUid', + )}`, + ), ); await ContentTypesMapperModelLowdb.update((data: any) => { data.ContentTypesMappers[updateIndex].status = @@ -628,15 +674,15 @@ const updateContentType = async (req: Request) => { await ContentTypesMapperModelLowdb.read(); const updatedContentType = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: contentTypeId, projectId: projectId }) .value(); return { data: updatedContentType, status: 400, message: `${VALIDATION_ERRORS.STRING_REQUIRED.replace( - "$", - "contentstackFieldType or contentstackFieldUid" + '$', + 'contentstackFieldType or contentstackFieldUid', )}`, }; } @@ -668,8 +714,8 @@ const updateContentType = async (req: Request) => { logger.error( getLogMessage( srcFun, - `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}` - ) + `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}`, + ), ); return { status: 404, @@ -681,16 +727,15 @@ const updateContentType = async (req: Request) => { await FieldMapperModel.read(); fieldMapping.forEach((field: any) => { const fieldIndex = FieldMapperModel.data.field_mapper.findIndex( - (f: any) => f?.id === field?.id && f?.contentTypeId === field?.contentTypeId + (f: any) => + f?.id === field?.id && f?.contentTypeId === field?.contentTypeId, ); - if (fieldIndex > -1 && field?.contentstackFieldType !== "") { + if (fieldIndex > -1 && field?.contentstackFieldType !== '') { FieldMapperModel.update((data: any) => { const existingField = data?.field_mapper?.[fieldIndex]; const preservedInitial = existingField?.advanced?.initial; - data.field_mapper[fieldIndex] = field; - if (preservedInitial && field?.advanced) { data.field_mapper[fieldIndex].advanced.initial = preservedInitial; @@ -706,7 +751,7 @@ const updateContentType = async (req: Request) => { // Fetch and return updated content type await ContentTypesMapperModelLowdb.read(); const updatedContentType = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: contentTypeId, projectId: projectId }) .value(); @@ -719,8 +764,8 @@ const updateContentType = async (req: Request) => { getLogMessage( srcFun, `Error while updating ContentType Id: ${contentTypeId}`, - error - ) + error, + ), ); return { status: error?.status || 500, @@ -739,7 +784,7 @@ const updateContentType = async (req: Request) => { * @throws {ExceptionFunction} If an error occurs while resetting the field mapping. */ const resetToInitialMapping = async (req: Request) => { - const srcFunc = "resetToInitialMapping"; + const srcFunc = 'resetToInitialMapping'; const { orgId, projectId, contentTypeId } = req.params; const { token_payload } = req.body; @@ -753,7 +798,7 @@ const resetToInitialMapping = async (req: Request) => { owner: token_payload?.user_id, }, srcFunc, - true + true, )) as number; const project = ProjectModelLowdb.data.projects[projectIndex]; @@ -770,23 +815,23 @@ const resetToInitialMapping = async (req: Request) => { getLogMessage( srcFunc, HTTP_TEXTS.CANNOT_RESET_CONTENT_MAPPING, - token_payload - ) + token_payload, + ), ); throw new BadRequestError(HTTP_TEXTS.CANNOT_RESET_CONTENT_MAPPING); } await ContentTypesMapperModelLowdb.read(); const contentTypeData = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: contentTypeId, projectId: projectId }) .value(); await FieldMapperModel.read(); const fieldMappingData = contentTypeData.fieldMapping.map((itemId: any) => { const fieldData = FieldMapperModel.chain - .get("field_mapper") - .find({ id: itemId, projectId: projectId, contentTypeId: contentTypeId}) + .get('field_mapper') + .find({ id: itemId, projectId: projectId, contentTypeId: contentTypeId }) .value(); return fieldData; }); @@ -795,8 +840,8 @@ const resetToInitialMapping = async (req: Request) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}` - ) + `${HTTP_TEXTS.CONTENT_TYPE_NOT_FOUND} Id: ${contentTypeId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.INVALID_CONTENT_TYPE); } @@ -806,31 +851,34 @@ const resetToInitialMapping = async (req: Request) => { //await FieldMapperModel.read(); (fieldMappingData || []).forEach((field: any) => { const fieldIndex = FieldMapperModel.data.field_mapper.findIndex( - (f: any) => f?.id === field?.id && f?.projectId === projectId && f?.contentTypeId === contentTypeId + (f: any) => + f?.id === field?.id && + f?.projectId === projectId && + f?.contentTypeId === contentTypeId, ); if (fieldIndex > -1) { FieldMapperModel.update((data: any) => { - data.field_mapper[fieldIndex] = { - ...field, - contentstackField: field?.otherCmsField, - contentstackFieldUid: field?.backupFieldUid, - contentstackFieldType: field?.backupFieldType, - advanced: { - ...field?.advanced?.initial, - initial: field?.advanced?.initial, - }, - ...(field?.referenceTo && { - referenceTo: field?.initialRefrenceTo - }), - isDeleted: false, - } + data.field_mapper[fieldIndex] = { + ...field, + contentstackField: field?.otherCmsField, + contentstackFieldUid: field?.backupFieldUid, + contentstackFieldType: field?.backupFieldType, + advanced: { + ...field?.advanced?.initial, + initial: field?.advanced?.initial, + }, + ...(field?.referenceTo && { + referenceTo: field?.initialRefrenceTo, + }), + isDeleted: false, + }; }); } }); } const contentIndex = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .findIndex({ id: contentTypeId, projectId: projectId }) .value(); // if (contentIndex > -1) { @@ -847,21 +895,20 @@ const resetToInitialMapping = async (req: Request) => { return { status: HTTP_CODES?.OK, message: HTTP_TEXTS.RESET_CONTENT_MAPPING, - data: contentTypeData + data: contentTypeData, }; - } catch (error: any) { logger.error( getLogMessage( srcFunc, `Error occurred while resetting the field mapping for the ContentType ID: ${contentTypeId}`, {}, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.status || error.statusCode || HTTP_CODES.SERVER_ERROR + error?.status || error.statusCode || HTTP_CODES.SERVER_ERROR, ); } }; @@ -874,11 +921,11 @@ const resetToInitialMapping = async (req: Request) => { * @throws {ExceptionFunction} If an error occurs while resetting the content types mapping. */ const resetAllContentTypesMapping = async (projectId: string) => { - const srcFunc = "resetAllContentTypesMapping"; + const srcFunc = 'resetAllContentTypesMapping'; await ProjectModelLowdb.read(); const projectDetails = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); @@ -887,8 +934,8 @@ const resetAllContentTypesMapping = async (projectId: string) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.CONTENTMAPPER_NOT_FOUND} projectId: ${projectId}` - ) + `${HTTP_TEXTS.CONTENTMAPPER_NOT_FOUND} projectId: ${projectId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.CONTENTMAPPER_NOT_FOUND); } @@ -896,15 +943,15 @@ const resetAllContentTypesMapping = async (projectId: string) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}` - ) + `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.PROJECT_NOT_FOUND); } await ContentTypesMapperModelLowdb.read(); const cData = contentMapperId.map((cId: any) => { const contentTypeData = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: cId, projectId: projectId }) .value(); return contentTypeData; @@ -917,11 +964,11 @@ const resetAllContentTypesMapping = async (projectId: string) => { for (const field of contentType.fieldMapping) { await FieldMapperModel.read(); const fieldData = FieldMapperModel.chain - .get("field_mapper") + .get('field_mapper') .find({ id: field, projectId: projectId }) .value(); const fieldIndex = FieldMapperModel.chain - .get("field_mapper") + .get('field_mapper') .findIndex({ id: field, projectId: projectId }) .value(); @@ -929,8 +976,8 @@ const resetAllContentTypesMapping = async (projectId: string) => { await FieldMapperModel.update((fData: any) => { fData.field_mapper[fieldIndex] = { ...fieldData, - contentstackField: "", - contentstackFieldUid: "", + contentstackField: '', + contentstackFieldUid: '', contentstackFieldType: fieldData.backupFieldType, }; }); @@ -940,13 +987,13 @@ const resetAllContentTypesMapping = async (projectId: string) => { await ContentTypesMapperModelLowdb.read(); if (!isEmpty(contentType?.id)) { const cIndex = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .findIndex({ id: contentType?.id, projectId: projectId }) .value(); if (cIndex > -1) { await ContentTypesMapperModelLowdb.update((data: any) => { - data.ContentTypesMappers[cIndex].contentstackTitle = ""; - data.ContentTypesMappers[cIndex].contentstackUid = ""; + data.ContentTypesMappers[cIndex].contentstackTitle = ''; + data.ContentTypesMappers[cIndex].contentstackUid = ''; }); } } @@ -959,12 +1006,12 @@ const resetAllContentTypesMapping = async (projectId: string) => { srcFunc, `Error occurred while reseting all the content types mapping for the Project [Id: ${projectId}]`, {}, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -976,10 +1023,10 @@ const resetAllContentTypesMapping = async (projectId: string) => { * @throws {ExceptionFunction} If an error occurs while removing the content mapping. */ const removeMapping = async (projectId: string) => { - const srcFunc = "removeMapping"; + const srcFunc = 'removeMapping'; await ProjectModelLowdb.read(); const projectDetails = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); @@ -987,15 +1034,15 @@ const removeMapping = async (projectId: string) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}` - ) + `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.PROJECT_NOT_FOUND); } await ContentTypesMapperModelLowdb.read(); const cData = projectDetails?.content_mapper.map((cId: any) => { const contentTypeData = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: cId, projectId: projectId }) .value(); return contentTypeData; @@ -1010,7 +1057,7 @@ const removeMapping = async (projectId: string) => { for (const field of contentType.fieldMapping) { await FieldMapperModel.read(); const fieldIndex = FieldMapperModel.chain - .get("field_mapper") + .get('field_mapper') .findIndex({ id: field, projectId: projectId }) .value(); if (fieldIndex > -1) { @@ -1023,7 +1070,7 @@ const removeMapping = async (projectId: string) => { await ContentTypesMapperModelLowdb.read(); if (!isEmpty(contentType?.id)) { const cIndex = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .findIndex({ id: contentType?.id, projectId: projectId }) .value(); if (cIndex > -1) { @@ -1036,7 +1083,7 @@ const removeMapping = async (projectId: string) => { await ProjectModelLowdb.read(); const projectIndex = ProjectModelLowdb.chain - .get("projects") + .get('projects') .findIndex({ id: projectId }) .value(); @@ -1052,12 +1099,12 @@ const removeMapping = async (projectId: string) => { srcFunc, `Error occurred while removing the content mapping for the Project [Id: ${projectId}]`, {}, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -1073,18 +1120,18 @@ const getSingleContentTypes = async (req: Request) => { const authtoken = await getAuthtoken( token_payload?.region, - token_payload?.user_id + token_payload?.user_id, ); await ProjectModelLowdb.read(); const project = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); const stackId = project?.destination_stack_id; const [err, res] = await safePromise( https({ - method: "GET", + method: 'GET', url: `${config.CS_API[ token_payload?.region as keyof typeof config.CS_API ]!}/content_types/${contentTypeUID}`, @@ -1092,7 +1139,7 @@ const getSingleContentTypes = async (req: Request) => { api_key: stackId, authtoken: authtoken, }, - }) + }), ); if (err) @@ -1104,7 +1151,7 @@ const getSingleContentTypes = async (req: Request) => { return { title: res?.data?.content_type?.title, uid: res?.data?.content_type?.uid, - schema: res?.data?.content_type?.schema + schema: res?.data?.content_type?.schema, }; }; @@ -1120,18 +1167,18 @@ const getSingleGlobalField = async (req: Request) => { const authtoken = await getAuthtoken( token_payload?.region, - token_payload?.user_id + token_payload?.user_id, ); await ProjectModelLowdb.read(); const project = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); const stackId = project?.destination_stack_id; const [err, res] = await safePromise( https({ - method: "GET", + method: 'GET', url: `${config.CS_API[ token_payload?.region as keyof typeof config.CS_API ]!}/global_fields/${globalFieldUID}`, @@ -1139,7 +1186,7 @@ const getSingleGlobalField = async (req: Request) => { api_key: stackId, authtoken: authtoken, }, - }) + }), ); if (err) @@ -1151,9 +1198,9 @@ const getSingleGlobalField = async (req: Request) => { return { title: res?.data?.global_field?.title, uid: res?.data?.global_field?.uid, - schema: res?.data?.global_field?.schema + schema: res?.data?.global_field?.schema, }; -} +}; /** * Removes the content mapping for a project. * @param req - The request object containing the project ID. @@ -1163,10 +1210,10 @@ const getSingleGlobalField = async (req: Request) => { */ const removeContentMapper = async (req: Request) => { const projectId = req?.params?.projectId; - const srcFunc = "removeMapping"; + const srcFunc = 'removeMapping'; await ProjectModelLowdb.read(); const projectDetails = ProjectModelLowdb.chain - .get("projects") + .get('projects') .find({ id: projectId }) .value(); @@ -1174,8 +1221,8 @@ const removeContentMapper = async (req: Request) => { logger.error( getLogMessage( srcFunc, - `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}` - ) + `${HTTP_TEXTS.PROJECT_NOT_FOUND} projectId: ${projectId}`, + ), ); throw new BadRequestError(HTTP_TEXTS.PROJECT_NOT_FOUND); } @@ -1184,11 +1231,11 @@ const removeContentMapper = async (req: Request) => { (cId: string) => { const contentTypeData: ContentTypesMapper = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .find({ id: cId, projectId: projectId }) .value(); return contentTypeData; - } + }, ); try { @@ -1200,7 +1247,7 @@ const removeContentMapper = async (req: Request) => { for (const field of contentType.fieldMapping) { await FieldMapperModel.read(); const fieldIndex = FieldMapperModel.chain - .get("field_mapper") + .get('field_mapper') .findIndex({ id: field, projectId: projectId }) .value(); if (fieldIndex > -1) { @@ -1213,7 +1260,7 @@ const removeContentMapper = async (req: Request) => { await ContentTypesMapperModelLowdb.read(); if (!isEmpty(contentType?.id)) { const cIndex = ContentTypesMapperModelLowdb.chain - .get("ContentTypesMappers") + .get('ContentTypesMappers') .findIndex({ id: contentType?.id, projectId: projectId }) .value(); if (cIndex > -1) { @@ -1226,7 +1273,7 @@ const removeContentMapper = async (req: Request) => { await ProjectModelLowdb.read(); const projectIndex = ProjectModelLowdb.chain - .get("projects") + .get('projects') .findIndex({ id: projectId }) .value(); @@ -1242,12 +1289,12 @@ const removeContentMapper = async (req: Request) => { srcFunc, `Error occurred while removing the content mapping for the Project [Id: ${projectId}]`, {}, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -1263,7 +1310,7 @@ const removeContentMapper = async (req: Request) => { const updateContentMapper = async (req: Request) => { const { orgId, projectId } = req.params; const { token_payload, content_mapper } = req.body; - const srcFunc = "updateContentMapper"; + const srcFunc = 'updateContentMapper'; await ProjectModelLowdb.read(); const projectIndex = (await getProjectUtil( @@ -1275,7 +1322,7 @@ const updateContentMapper = async (req: Request) => { owner: token_payload?.user_id, }, srcFunc, - true + true, )) as number; try { @@ -1288,8 +1335,8 @@ const updateContentMapper = async (req: Request) => { getLogMessage( srcFunc, `Content mapping for project [Id : ${projectId}] has been successfully updated.`, - token_payload - ) + token_payload, + ), ); return { status: HTTP_CODES.OK, @@ -1303,16 +1350,241 @@ const updateContentMapper = async (req: Request) => { srcFunc, `Error occurred while updating content mapping for project [Id : ${projectId}].`, token_payload, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; +const getExistingTaxonomies = async (req: Request) => { + const projectId = req?.params?.projectId; + const { token_payload } = req.body || {}; + + try { + // Get project details + await ProjectModelLowdb.read(); + const project = ProjectModelLowdb.chain + .get('projects') + .find({ id: projectId }) + .value(); + + if (!project) { + return { + sourceTaxonomies: [], + destinationTaxonomies: [], + data: 'Project not found', + status: 404, + }; + } + + const stackId = project?.destination_stack_id; + + // Step 1: Get source taxonomies from project database (sent by upload-api) + let sourceTaxonomies: any[] = []; + + if (project?.taxonomies && Array.isArray(project.taxonomies)) { + // Taxonomies stored in project database (sent from upload-api during validation) + sourceTaxonomies = project.taxonomies.map((taxonomy: any) => ({ + uid: taxonomy.uid, + name: taxonomy.name || taxonomy.uid, + description: taxonomy.description || '', + source: 'source_cms', + })); + logger.info( + `✓ Found ${sourceTaxonomies.length} source taxonomies in project database`, + ); + } else { + // Fallback: Try reading from migration-data files + logger.warn( + 'No taxonomies found in project database, checking fallback paths...', + ); + + // Path 1: Check api/migration-data (processed taxonomies) + // Validate stackId exists before using it + if (stackId) { + // Sanitize stackId to prevent path traversal + const sanitizedStackId = path.basename(stackId); + + const apiMigrationDataPath = path.join( + MIGRATION_DATA_CONFIG.DATA, + sanitizedStackId, + MIGRATION_DATA_CONFIG.TAXONOMIES_DIR_NAME, + MIGRATION_DATA_CONFIG.TAXONOMIES_FILE_NAME, + ); + + // Resolve to absolute path and validate it's within allowed directory + const baseDirectory = path.resolve(MIGRATION_DATA_CONFIG.DATA); + const resolvedPath = path.resolve(apiMigrationDataPath); + + // Ensure the resolved path is within the base directory using path.relative() + // This is safer than startsWith() which can be bypassed on Windows (e.g., C:\data_evil vs C:\data) + const relativePath = path.relative(baseDirectory, resolvedPath); + if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { + logger.error( + `Path traversal attempt detected: ${resolvedPath} is outside ${baseDirectory}`, + ); + throw new BadRequestError('Invalid file path'); + } + + try { + // Use lstat to check file exists WITHOUT following symlinks (prevents TOCTOU attacks) + const stats = await fs.promises.lstat(resolvedPath).catch(() => null); + if (stats && stats.isFile() && !stats.isSymbolicLink()) { + // Re-validate the real path after confirming it's not a symlink using path.relative() + const realPath = await fs.promises.realpath(resolvedPath); + const realRelativePath = path.relative(baseDirectory, realPath); + if ( + realRelativePath.startsWith('..') || + path.isAbsolute(realRelativePath) + ) { + logger.error( + `Symlink escape attempt detected: ${realPath} is outside ${baseDirectory}`, + ); + throw new BadRequestError('Invalid file path'); + } + const taxonomiesData = await fs.promises.readFile(realPath, 'utf8'); + const taxonomiesObject = JSON.parse(taxonomiesData); + + // Convert object to array with proper structure + const apiTaxonomies = Object.entries(taxonomiesObject).map( + ([uid, data]: [string, any]) => ({ + uid: data.uid || uid, + name: data.name || uid, + description: data.description || '', + source: 'source_cms', + }), + ); + sourceTaxonomies.push(...apiTaxonomies); + } + } catch (fileError: any) { + logger.error( + `Error reading migration-data taxonomies: ${fileError.message}`, + ); + } + } else { + logger.warn( + 'stackId is null or undefined, skipping api/migration-data path. Will try upload-api fallback.', + ); + } + + // Path 2: Fallback to upload-api drupalMigrationData (if api/migration-data not found) + if (sourceTaxonomies.length === 0) { + try { + // Try to find upload-api directory relative to api directory + const uploadApiPath = path.join( + process.cwd(), + '..', + 'upload-api', + 'drupalMigrationData', + 'taxonomySchema', + 'taxonomySchema.json', + ); + const uploadApiResolved = path.resolve(uploadApiPath); + + // Basic safety check - ensure it's within expected directory structure + if ( + uploadApiResolved.includes('upload-api') && + uploadApiResolved.includes('drupalMigrationData') + ) { + const stats = await fs.promises + .lstat(uploadApiResolved) + .catch(() => null); + if (stats && stats.isFile() && !stats.isSymbolicLink()) { + const taxonomyData = await fs.promises.readFile( + uploadApiResolved, + 'utf8', + ); + const taxonomiesArray = JSON.parse(taxonomyData); + + // Convert array to proper structure + const uploadApiTaxonomies = ( + Array.isArray(taxonomiesArray) + ? taxonomiesArray + : Object.values(taxonomiesArray) + ).map((taxonomy: any) => ({ + uid: taxonomy.uid || taxonomy.vid || '', + name: taxonomy.name || taxonomy.uid || taxonomy.vid || '', + description: taxonomy.description || '', + source: 'source_cms', + })); + + sourceTaxonomies.push(...uploadApiTaxonomies); + logger.info( + `✓ Found ${uploadApiTaxonomies.length} taxonomies from upload-api drupalMigrationData`, + ); + } + } + } catch (uploadApiError: any) { + logger.warn( + `Could not read taxonomies from upload-api: ${uploadApiError.message}`, + ); + } + } + } + + // Step 2: Get destination taxonomies from Contentstack (if stack exists and token_payload is available) + let destinationTaxonomies: any[] = []; + + if (token_payload?.region && token_payload?.user_id && stackId) { + try { + const authtoken = await getAuthtoken( + token_payload.region, + token_payload.user_id, + ); + + const baseUrl = `${config.CS_API[ + token_payload?.region as keyof typeof config.CS_API + ]!}/taxonomies`; + + const headers = { + api_key: stackId, + authtoken, + }; + + // Fetch taxonomies from Contentstack + const taxonomies = await fetchAllPaginatedData( + baseUrl, + headers, + 100, + 'getExistingTaxonomies', + 'taxonomies', + ); + + destinationTaxonomies = taxonomies.map((taxonomy: any) => ({ + uid: taxonomy.uid, + name: taxonomy.name, + description: taxonomy.description || '', + source: 'destination_stack', + })); + } catch (apiError: any) { + logger.error( + `Error fetching destination taxonomies: ${apiError.message}`, + ); + } + } + + const response = { + sourceTaxonomies, + destinationTaxonomies, + status: 200, // GET requests should return 200 OK, not 201 Created + }; + + return response; + } catch (error: any) { + logger.error(`Error in getExistingTaxonomies: ${error.message}`); + return { + sourceTaxonomies: [], + destinationTaxonomies: [], + data: error.message, + status: error?.statusCode || error?.status || 500, // Check statusCode first (custom errors use this) + }; + } +}; + export const contentMapperService = { putTestData, getContentTypes, @@ -1326,5 +1598,6 @@ export const contentMapperService = { getSingleContentTypes, updateContentMapper, getExistingGlobalFields, - getSingleGlobalField + getSingleGlobalField, + getExistingTaxonomies, }; diff --git a/api/src/services/drupal.service.ts b/api/src/services/drupal.service.ts new file mode 100644 index 000000000..9cd1d5f2b --- /dev/null +++ b/api/src/services/drupal.service.ts @@ -0,0 +1,70 @@ +// Import modular Drupal services +import { createAssets } from './drupal/assets.service.js'; +import { createEntry } from './drupal/entries.service.js'; +import { createLocale } from './drupal/locales.service.js'; +import { createRefrence } from './drupal/references.service.js'; +import { createTaxonomy } from './drupal/taxonomy.service.js'; +import { createVersionFile } from './drupal/version.service.js'; +import { createQuery, createQueryConfig } from './drupal/query.service.js'; +import { generateContentTypeSchemas } from './drupal/content-types.service.js'; + +/** + * Drupal migration service with SQL-based data extraction. + * + * All functions use direct database connections to extract data from Drupal + * following the original migration patterns. + * + * IMPORTANT: Run in this order for proper dependency resolution: + * 1. createQuery - Generate dynamic queries from database analysis (MUST RUN FIRST) + * 2. generateContentTypeSchemas - Convert upload-api schema to API content types (MUST RUN AFTER upload-api) + * 3. createAssets - Extract assets first (needed by entries) + * 4. createRefrence - Create reference mappings (needed by entries) + * 5. createTaxonomy - Extract taxonomies (needed by entries for taxonomy references) + * 6. createEntry - Process entries (uses assets, references, and taxonomies) + * 7. createLocale - Create locale configurations + * 8. createVersionFile - Create version metadata file + */ +export const drupalService = { + createQuery, // Generate dynamic queries from database analysis (MUST RUN FIRST) + createQueryConfig, // Helper: Create query configuration file for dynamic SQL + generateContentTypeSchemas, // Convert upload-api schema to API content types (MUST RUN AFTER upload-api) + createAssets: ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + isTest = false, + assetsConfig?: any + ) => { + return createAssets( + dbConfig, + destination_stack_id, + projectId, + assetsConfig?.base_url || '', + assetsConfig?.public_path || '', + isTest + ); + }, + createRefrence, // Create reference mappings for relationships (run before entries) + createTaxonomy, // Extract and process Drupal taxonomies (vocabularies and terms) + createEntry: ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + isTest = false, + masterLocale = 'en-us', + contentTypeMapping: any[] = [], + project: any = null + ) => { + return createEntry( + dbConfig, + destination_stack_id, + projectId, + isTest, + masterLocale, + contentTypeMapping, + project + ); + }, + createLocale, // Create locale configurations + createVersionFile, // Create version metadata file +}; diff --git a/api/src/services/drupal/assets.service.ts b/api/src/services/drupal/assets.service.ts new file mode 100644 index 000000000..41f5bca71 --- /dev/null +++ b/api/src/services/drupal/assets.service.ts @@ -0,0 +1,905 @@ +import fs from 'fs'; +import path from 'path'; +import axios from 'axios'; +import pLimit from 'p-limit'; +import mysql from 'mysql2'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; +import { getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { getDbConnection } from '../../helper/index.js'; +import { processBatches } from '../../utils/batch-processor.utils.js'; + +const { + DATA, + ASSETS_DIR_NAME, + ASSETS_FILE_NAME, + ASSETS_SCHEMA_FILE, + ASSETS_FAILED_FILE, +} = MIGRATION_DATA_CONFIG; + +interface AssetMetaData { + uid: string; + url: string; + filename: string; +} + +interface DrupalAsset { + fid: string | number; + uri: string; + filename: string; + filesize: string | number; + filemime?: string; + status?: string | number; + uid?: string | number; + timestamp?: string | number; + id?: string | number; // For file_usage table + count?: string | number; // For file_usage table +} + +/** + * Interface to track asset download URLs and their status + */ +interface AssetUrlTracker { + success: Array<{ + uid: string; + url: string; + filename: string; + }>; + failed: Array<{ + uid: string; + url: string; + filename: string; + reason: string; + }>; +} + +/** + * Writes data to a specified file, ensuring the target directory exists. + */ +async function writeFile(dirPath: string, filename: string, data: any) { + let fileHandle; + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + const filePath = path.join(dirPath, filename); + + // Use file handle for better control over file operations + fileHandle = await fs.promises.open(filePath, 'w'); + await fileHandle.writeFile(JSON.stringify(data), 'utf8'); + } catch (err) { + console.error(`Error writing ${dirPath}/${filename}::`, err); + throw err; // Re-throw to handle upstream + } finally { + // Ensure file handle is always closed + if (fileHandle) { + try { + await fileHandle.close(); + } catch (closeErr) { + console.error( + `Error closing file handle for ${dirPath}/${filename}:`, + closeErr + ); + } + } + } +} + +/** + * Executes SQL query and returns results as Promise + */ +const executeQuery = ( + connection: mysql.Connection, + query: string +): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + } else { + resolve(results as any[]); + } + }); + }); +}; + +const publicPathCache = new Map(); + +// AUTO-DETECT PUBLIC PATH FROM DATABASE +const detectPublicPath = async ( + connection: mysql.Connection, + baseUrl: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'detectPublicPath'; + + try { + // Try to get public file path from Drupal's system table + const configQuery = ` + SELECT value + FROM config + WHERE name = 'system.file' + LIMIT 1 + `; + + try { + const configResults = await executeQuery(connection, configQuery); + if (configResults.length > 0) { + const config = JSON.parse(configResults[0].value); + if (config.path && config.path.public) { + const detectedPath = config.path.public; + return detectedPath.endsWith('/') ? detectedPath : `${detectedPath}/`; + } + } + } catch (configErr) {} + + // Final fallback: Try to detect from an actual file by testing URLs + const sampleFileQuery = ` + SELECT uri, filename + FROM file_managed + WHERE uri LIKE 'public://%' + LIMIT 5 + `; + + const sampleResults = await executeQuery(connection, sampleFileQuery); + if (sampleResults.length > 0) { + // Try common Drupal paths with the user-provided baseUrl + const commonPaths = [ + '/sites/default/files/', + '/sites/all/files/', + '/files/', + ]; + + // Also try to extract path patterns from the database URIs + for (const sampleFile of sampleResults) { + const sampleUri = sampleFile.uri; + + for (const testPath of commonPaths) { + const testUrl = `${baseUrl}${testPath}${sampleUri.replace( + 'public://', + '' + )}`; + try { + const response = await axios.get(testUrl, { + timeout: 5000, + maxContentLength: 1024, // Only download first 1KB to test + headers: { + 'User-Agent': 'Contentstack-Drupal-Migration/1.0', + }, + }); + if (response.status === 200) { + const message = getLogMessage( + srcFunc, + `Auto-detected public path: ${testPath}`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + message + ); + return testPath; + } + } catch (err) { + // Continue to next path + } + } + } + + // If common paths don't work, try to extract from URI patterns + // Look for patterns like /sites/[site]/files/ in the database + const uriPatternQuery = ` + SELECT DISTINCT uri + FROM file_managed + WHERE uri LIKE 'public://%' + LIMIT 10 + `; + + const uriResults = await executeQuery(connection, uriPatternQuery); + const pathPatterns = new Set(); + + uriResults.forEach((row) => { + const uri = row.uri; + // Extract potential path patterns from URIs + const matches = uri.match(/public:\/\/(?:sites\/([^\/]+)\/)?files\//); + if (matches) { + pathPatterns.add(`/sites/${matches[1]}/files/`); + } + }); + + // Test extracted patterns + for (const pattern of pathPatterns) { + const patternStr = pattern as string; + for (const sampleFile of sampleResults.slice(0, 2)) { + // Test with fewer files + const testUrl = `${baseUrl}${patternStr}${sampleFile.uri.replace( + 'public://', + '' + )}`; + try { + const response = await axios.get(testUrl, { + timeout: 5000, + maxContentLength: 1024, // Only download first 1KB to test + headers: { + 'User-Agent': 'Contentstack-Drupal-Migration/1.0', + }, + }); + if (response.status === 200) { + const message = getLogMessage( + srcFunc, + `Auto-detected public path from patterns: ${patternStr}`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + message + ); + return patternStr; + } + } catch (err) { + // Continue to next pattern + } + } + } + } + + // Ultimate fallback + + const message = getLogMessage( + srcFunc, + `Could not auto-detect public path. Using default: /sites/default/files/`, + {} + ); + await customLogger(projectId, destination_stack_id, 'warn', message); + return '/sites/default/files/'; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error detecting public path: ${error.message}. Using default.`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'warn', message); + return '/sites/default/files/'; + } +}; + +// URL VALIDATION AND NORMALIZATION +const normalizeUrlConfig = ( + baseUrl: string, + publicPath: string +): { baseUrl: string; publicPath: string } => { + // Validate inputs - allow empty values for auto-detection + if (!baseUrl && !publicPath) { + throw new Error( + `Invalid URL configuration: Both baseUrl and publicPath are empty. At least one must be provided.` + ); + } + + // Normalize baseUrl (handle empty case) + let normalizedBaseUrl = baseUrl ? baseUrl.trim() : ''; + + if (normalizedBaseUrl) { + // Remove trailing slash from baseUrl + normalizedBaseUrl = normalizedBaseUrl.replace(/\/+$/, ''); + + // Ensure baseUrl has protocol + if ( + !normalizedBaseUrl.startsWith('http://') && + !normalizedBaseUrl.startsWith('https://') + ) { + normalizedBaseUrl = `https://${normalizedBaseUrl}`; + } + + // Validate baseUrl format + try { + new URL(normalizedBaseUrl); + } catch (error) { + throw new Error( + `Invalid baseUrl format: "${baseUrl}" → "${normalizedBaseUrl}". Please provide a valid URL.` + ); + } + } + + // Normalize publicPath (handle empty case) + let normalizedPublicPath = publicPath ? publicPath.trim() : ''; + + if (normalizedPublicPath) { + // Ensure publicPath starts with / + if (!normalizedPublicPath.startsWith('/')) { + normalizedPublicPath = `/${normalizedPublicPath}`; + } + + // Ensure publicPath ends with / + if (!normalizedPublicPath.endsWith('/')) { + normalizedPublicPath = `${normalizedPublicPath}/`; + } + + // Remove duplicate slashes + normalizedPublicPath = normalizedPublicPath.replace(/\/+/g, '/'); + + // Validate publicPath doesn't contain invalid characters + if ( + normalizedPublicPath.includes('..') || + normalizedPublicPath.includes('//') + ) { + throw new Error( + `Invalid publicPath format: "${publicPath}" → "${normalizedPublicPath}". Path contains invalid characters.` + ); + } + } + + return { + baseUrl: normalizedBaseUrl, + publicPath: normalizedPublicPath, + }; +}; + +// DYNAMIC URL CONSTRUCTION - HANDLES MULTIPLE PATH FORMATS +const constructAssetUrl = ( + uri: string, + baseUrl: string, + publicPath: string +): string => { + try { + // Normalize the input URLs first + const { baseUrl: cleanBaseUrl, publicPath: cleanPublicPath } = + normalizeUrlConfig(baseUrl, publicPath); + + // Already a full URL - return as is + if (uri.startsWith('http://') || uri.startsWith('https://')) { + return uri; + } + + // Handle public:// scheme + if (uri.startsWith('public://')) { + const relativePath = uri.replace('public://', ''); + + // Check if we have valid baseUrl and publicPath + if (!cleanBaseUrl || !cleanPublicPath) { + throw new Error( + `Cannot construct URL: baseUrl="${cleanBaseUrl}", publicPath="${cleanPublicPath}". Both are required for public:// URIs.` + ); + } + + const fullUrl = `${cleanBaseUrl}${cleanPublicPath}${relativePath}`; + return fullUrl; + } + + // Handle private:// scheme + if (uri.startsWith('private://')) { + const relativePath = uri.replace('private://', ''); + + if (!cleanBaseUrl) { + throw new Error( + `Cannot construct URL: baseUrl="${cleanBaseUrl}". Base URL is required for private:// URIs.` + ); + } + + return `${cleanBaseUrl}/system/files/${relativePath}`; + } + + // Handle relative paths + const path = uri.startsWith('/') ? uri : `/${uri}`; + + if (!cleanBaseUrl) { + throw new Error( + `Cannot construct URL: baseUrl="${cleanBaseUrl}". Base URL is required for relative paths.` + ); + } + + return `${cleanBaseUrl}${path}`; + } catch (error: any) { + console.error(`❌ URL Construction Error: ${error.message}`); + throw new Error(`Failed to construct asset URL: ${error.message}`); + } +}; + +// IMPROVED SAVE ASSET WITH BETTER ERROR HANDLING +const saveAsset = async ( + assets: DrupalAsset, + failedJSON: any, + assetData: any, + metadata: AssetMetaData[], + projectId: string, + destination_stack_id: string, + baseUrl: string = '', + publicPath: string = '/sites/default/files/', + retryCount = 0, + authHeaders: any = {}, // Support for authentication + urlTracker?: AssetUrlTracker, // Track successful and failed URLs + userProvidedPublicPath?: string // Original user-provided path (for failed URL tracking) +): Promise => { + try { + const srcFunc = 'saveAsset'; + const assetsSave = path.join( + DATA, + destination_stack_id, + ASSETS_DIR_NAME, + 'files' + ); + + const assetId = `assets_${assets.fid}`; + const fileName = assets.filename; + const fileUrl = constructAssetUrl(assets.uri, baseUrl, publicPath); + + // Check if asset already exists + if (fs.existsSync(path.resolve(assetsSave, assetId, fileName))) { + return assetId; + } + + try { + const response = await axios.get(fileUrl, { + responseType: 'arraybuffer', + timeout: 120000, // Increased to 2 minutes + maxContentLength: 500 * 1024 * 1024, // 500MB max + headers: { + 'User-Agent': 'Contentstack-Drupal-Migration/1.0', + ...authHeaders, // Spread any authentication headers + }, + validateStatus: (status) => status === 200, // Only accept 200 + }); + + const assetPath = path.resolve(assetsSave, assetId); + + // Create asset data + assetData[assetId] = { + uid: assetId, + urlPath: `/assets/${assetId}`, + status: true, + content_type: assets.filemime || 'application/octet-stream', + file_size: assets.filesize.toString(), + tag: [], + filename: fileName, + url: fileUrl, + is_dir: false, + parent_uid: null, + _version: 1, + title: fileName, + publish_details: [], + }; + + await fs.promises.mkdir(assetPath, { recursive: true }); + await fs.promises.writeFile( + path.join(assetPath, fileName), + Buffer.from(response.data), + 'binary' + ); + await writeFile( + assetPath, + `_contentstack_${assetId}.json`, + assetData[assetId] + ); + + metadata.push({ uid: assetId, url: fileUrl, filename: fileName }); + + // Track successful download + if (urlTracker) { + urlTracker.success.push({ + uid: assetId, + url: fileUrl, + filename: fileName, + }); + } + + if (failedJSON[assetId]) { + delete failedJSON[assetId]; + } + + const message = getLogMessage( + srcFunc, + `✅ Asset "${fileName}" (${assets.fid}) downloaded successfully.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return assetId; + } catch (err: any) { + // Retry logic with exponential backoff + if (retryCount < 3) { + // Increased to 3 retries + const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return await saveAsset( + assets, + failedJSON, + assetData, + metadata, + projectId, + destination_stack_id, + baseUrl, + publicPath, + retryCount + 1, + authHeaders, + urlTracker, + userProvidedPublicPath + ); + } else { + // After 3 retries failed, try fallback paths (if not already tried) + const commonPaths = [ + '/sites/default/files/', + '/sites/all/files/', + ]; + + // Only try fallback if current path is the user-provided path + const isUserProvidedPath = + publicPath === (userProvidedPublicPath || publicPath); + + if (isUserProvidedPath) { + // Try each common path + for (const fallbackPath of commonPaths) { + // Skip if already tried + if (fallbackPath === publicPath) { + continue; + } + + try { + // Attempt download with fallback path (reset retry count) + const result = await saveAsset( + assets, + failedJSON, + assetData, + metadata, + projectId, + destination_stack_id, + baseUrl, + fallbackPath, + 0, // Reset retry count for fallback attempt + authHeaders, + urlTracker, + userProvidedPublicPath || publicPath // Keep original user path for tracking + ); + + // Check if asset was actually saved (exists in assetData) + if (assetData[assetId]) { + return result; // Successfully downloaded with fallback path + } + } catch (fallbackErr) { + // Continue to next fallback path + continue; + } + } + } + + // All attempts failed - log failure + const errorDetails = { + status: err.response?.status, + statusText: err.response?.statusText, + message: err.message, + url: fileUrl, + }; + + // Use user-provided public path for the failed URL + const failedUrl = constructAssetUrl( + assets.uri, + baseUrl, + userProvidedPublicPath || publicPath + ); + + failedJSON[assetId] = { + failedUid: assets.fid, + name: fileName, + url: failedUrl, + file_size: assets.filesize, + reason_for_error: JSON.stringify(errorDetails), + }; + + // Track failed download with user-provided URL + if (urlTracker) { + urlTracker.failed.push({ + uid: assetId, + url: failedUrl, + filename: fileName, + reason: `${err.response?.status || 'Network error'}: ${ + err.message + }`, + }); + } + + const message = getLogMessage( + srcFunc, + `❌ Failed to download "${fileName}" (${assets.fid}) after all attempts: ${err.message}`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + + return assetId; + } + } + } catch (error) { + console.error('❌ Error in saveAsset:', error); + return `assets_${assets.fid}`; + } +}; + +// ASSETS QUERY - FETCH ALL FILES +const fetchAssetsFromDB = async ( + connection: mysql.Connection, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'fetchAssetsFromDB'; + + // Query to fetch ALL files from Drupal + const assetsQuery = ` + SELECT + fm.fid, + fm.filename, + fm.uri, + fm.filesize, + fm.filemime + FROM file_managed fm + ORDER BY fm.fid ASC + `; + + try { + const results = await executeQuery(connection, assetsQuery); + + const message = getLogMessage( + srcFunc, + `Fetched ${results.length} total assets from database.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return results as DrupalAsset[]; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Failed to fetch assets from database: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Retries failed assets using FID query + */ +const retryFailedAssets = async ( + connection: mysql.Connection, + failedAssetIds: string[], + failedJSON: any, + assetData: any, + metadata: AssetMetaData[], + projectId: string, + destination_stack_id: string, + baseUrl: string = '', + publicPath: string = '/sites/default/files/', + authHeaders: any = {}, + urlTracker?: AssetUrlTracker, + userProvidedPublicPath?: string +): Promise => { + const srcFunc = 'retryFailedAssets'; + + if (failedAssetIds.length === 0) { + return; + } + + try { + const assetsFIDQuery = `SELECT a.fid, a.filename, a.uri, a.filesize, a.filemime, b.id, b.count FROM file_managed a, file_usage b WHERE a.fid IN (${failedAssetIds.join( + ',' + )})`; + const results = await executeQuery(connection, assetsFIDQuery); + + if (results.length > 0) { + const limit = pLimit(1); // Reduce to 1 for large datasets to prevent EMFILE errors + const tasks = results.map((asset: DrupalAsset) => + limit(() => + saveAsset( + asset, + failedJSON, + assetData, + metadata, + projectId, + destination_stack_id, + baseUrl, + publicPath, + 0, + authHeaders, + urlTracker, + userProvidedPublicPath + ) + ) + ); + + await Promise.all(tasks); + + const message = getLogMessage( + srcFunc, + `Retried ${results.length} failed assets.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + } + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error retrying failed assets: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + } +}; + +// UPDATED createAssets WITH AUTO-DETECTION +export const createAssets = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + baseUrl: string = '', + publicPath: string = '', // Now optional - will auto-detect if empty + isTest = false +) => { + const srcFunc = 'createAssets'; + let connection: mysql.Connection | null = null; + + try { + // Create database connection first + connection = await getDbConnection( + dbConfig, + projectId, + destination_stack_id + ); + + // Auto-detect public path if not provided or empty + let detectedPublicPath = publicPath; + if (!publicPath || publicPath.trim() === '') { + detectedPublicPath = await detectPublicPath( + connection, + baseUrl, + projectId, + destination_stack_id + ); + } else { + } + + const assetsSave = path.join(DATA, destination_stack_id, ASSETS_DIR_NAME); + const assetMasterFolderPath = path.join( + DATA, + destination_stack_id, + 'logs', + ASSETS_DIR_NAME + ); + + await fs.promises.mkdir(assetsSave, { recursive: true }); + await fs.promises.mkdir(assetMasterFolderPath, { recursive: true }); + + const failedJSON: any = {}; + const assetData: any = {}; + const metadata: AssetMetaData[] = []; + const fileMeta = { '1': ASSETS_SCHEMA_FILE }; + const failedAssetIds: string[] = []; + + // Initialize URL tracker for assets_url.json + const urlTracker: AssetUrlTracker = { + success: [], + failed: [], + }; + + const message = getLogMessage( + srcFunc, + `Exporting assets using base URL: ${baseUrl} and public path: ${detectedPublicPath}`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Fetch assets from database + const assetsData = await fetchAssetsFromDB( + connection, + projectId, + destination_stack_id + ); + + if (assetsData && assetsData.length > 0) { + let assets = assetsData; + if (isTest) { + assets = assets.slice(0, 10); + } + + const batchSize = assets.length > 10000 ? 100 : 1000; + const results = await processBatches( + assets, + async (asset: DrupalAsset) => { + try { + return await saveAsset( + asset, + failedJSON, + assetData, + metadata, + projectId, + destination_stack_id, + baseUrl, + detectedPublicPath, // Use detected path + 0, + {}, // authHeaders + urlTracker, // Pass URL tracker + publicPath || detectedPublicPath // Use original user-provided path for tracking + ); + } catch (error) { + failedAssetIds.push(asset.fid.toString()); + return `assets_${asset.fid}`; + } + }, + { + batchSize, + concurrency: 5, // Increased from 1 for better performance + delayBetweenBatches: 200, + }, + (batchIndex, totalBatches, batchResults) => { + if (batchIndex % 10 === 0) { + } + } + ); + + // Retry failed assets + if (failedAssetIds.length > 0) { + await retryFailedAssets( + connection, + failedAssetIds, + failedJSON, + assetData, + metadata, + projectId, + destination_stack_id, + baseUrl, + detectedPublicPath, // Use detected path + {}, // authHeaders + urlTracker, // Pass URL tracker + publicPath || detectedPublicPath // User-provided path for tracking + ); + } + + await writeFile(assetsSave, ASSETS_SCHEMA_FILE, assetData); + await writeFile(assetsSave, ASSETS_FILE_NAME, fileMeta); + + if (Object.keys(failedJSON).length > 0) { + await writeFile(assetMasterFolderPath, ASSETS_FAILED_FILE, failedJSON); + } + + // Write assets_url.json with successful and failed URLs + await writeFile(assetsSave, 'assets_url.json', urlTracker); + + const successMessage = getLogMessage( + srcFunc, + `Successfully processed ${ + Object.keys(assetData).length + } assets out of ${assets.length} total assets.`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + successMessage + ); + + return results; + } else { + const message = getLogMessage(srcFunc, `No assets found.`, {}); + await customLogger(projectId, destination_stack_id, 'info', message); + return []; + } + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error encountered while creating assets.`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } finally { + if (connection) { + connection.end(); + } + } +}; diff --git a/api/src/services/drupal/content-types.service.ts b/api/src/services/drupal/content-types.service.ts new file mode 100644 index 000000000..fa11b53be --- /dev/null +++ b/api/src/services/drupal/content-types.service.ts @@ -0,0 +1,416 @@ +/* eslint-disable */ + +import fs from 'fs'; +import path from 'path'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; +import { convertToSchemaFormate } from '../../utils/content-type-creator.utils.js'; +import { getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import FieldMapperModel from '../../models/FieldMapper.js'; +import ContentTypesMapperModelLowdb from '../../models/contentTypesMapper-lowdb.js'; + +const { DATA, CONTENT_TYPES_DIR_NAME, CONTENT_TYPES_SCHEMA_FILE } = + MIGRATION_DATA_CONFIG; + +/** + * Generates API content types from upload-api drupal schema + * This service reads the upload-api generated schema and converts it to final API content types + * following the same pattern as other CMS services (Contentful, WordPress, Sitecore) + */ +export const generateContentTypeSchemas = async ( + destination_stack_id: string, + projectId: string, +): Promise => { + const srcFunc = 'generateContentTypeSchemas'; + + try { + const message = getLogMessage( + srcFunc, + `Generating content type schemas from upload-api drupal schema...`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Path to upload-api generated schema + const uploadApiSchemaPath = path.join( + process.cwd(), + '..', + 'upload-api', + 'drupalMigrationData', + 'drupalSchema', + ); + + // Path to API content types directory + const apiContentTypesPath = path.join( + DATA, + destination_stack_id, + CONTENT_TYPES_DIR_NAME, + ); + + // Ensure API content types directory exists + await fs.promises.mkdir(apiContentTypesPath, { recursive: true }); + + if (!fs.existsSync(uploadApiSchemaPath)) { + throw new Error( + `Upload-API schema not found at: ${uploadApiSchemaPath}. Please run upload-api migration first.`, + ); + } + + // Read all schema files from upload-api + const schemaFiles = fs + .readdirSync(uploadApiSchemaPath) + .filter((file) => file.endsWith('.json')); + + if (schemaFiles.length === 0) { + throw new Error( + `No schema files found in upload-api directory: ${uploadApiSchemaPath}`, + ); + } + + // Load saved field mappings from database to get UI selections + await FieldMapperModel.read(); + await ContentTypesMapperModelLowdb.read(); + + const savedFieldMappings = FieldMapperModel.data.field_mapper.filter( + (field: any) => field && field.projectId === projectId, + ); + + // Log fields with UI changes + const fieldsWithTypeChanges = savedFieldMappings.filter( + (field: any) => + field.contentstackFieldType && + field.backupFieldType !== field.contentstackFieldType, + ); + + if (fieldsWithTypeChanges.length > 0) { + fieldsWithTypeChanges.forEach((field: any) => {}); + } + + // Build complete schema array (NO individual files) + const allApiSchemas = []; + + for (const schemaFile of schemaFiles) { + try { + const uploadApiSchemaFilePath = path.join( + uploadApiSchemaPath, + schemaFile, + ); + const uploadApiSchema = JSON.parse( + fs.readFileSync(uploadApiSchemaFilePath, 'utf8'), + ); + + // Convert upload-api schema to API format WITH saved field mappings from UI + const apiSchema = convertUploadApiSchemaToApiSchema( + uploadApiSchema, + savedFieldMappings, + projectId, + ); + + // Add to combined schema array (NO individual files) + allApiSchemas.push(apiSchema); + + const fieldMessage = getLogMessage( + srcFunc, + `Converted content type ${uploadApiSchema.uid} with ${ + uploadApiSchema.schema?.length || 0 + } fields`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + fieldMessage, + ); + } catch (error: any) { + const errorMessage = getLogMessage( + srcFunc, + `Failed to convert schema file ${schemaFile}: ${error.message}`, + {}, + error, + ); + await customLogger( + projectId, + destination_stack_id, + 'error', + errorMessage, + ); + } + } + + // Write ONLY the combined schema.json file + const combinedSchemaPath = path.join( + apiContentTypesPath, + CONTENT_TYPES_SCHEMA_FILE, + ); + await fs.promises.writeFile( + combinedSchemaPath, + JSON.stringify(allApiSchemas, null, 2), + 'utf8', + ); + + const successMessage = getLogMessage( + srcFunc, + `Successfully generated ${schemaFiles.length} content type schemas from upload-api`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + } catch (error: any) { + const errorMessage = getLogMessage( + srcFunc, + `Failed to generate content type schemas: ${error.message}`, + {}, + error, + ); + await customLogger(projectId, destination_stack_id, 'error', errorMessage); + throw error; + } +}; + +/** + * Converts upload-api drupal schema format to API content type format + * This preserves the original field types and user selections from the upload-api + * AND applies user's UI selections from Content Mapper for reference/taxonomy fields + */ +function convertUploadApiSchemaToApiSchema( + uploadApiSchema: any, + savedFieldMappings: any[] = [], + projectId?: string, +): any { + const apiSchema = { + title: uploadApiSchema.title, + uid: uploadApiSchema.uid, + schema: [] as any[], + }; + + if (!uploadApiSchema.schema || !Array.isArray(uploadApiSchema.schema)) { + return apiSchema; + } + + // Convert each field from upload-api format to API format + for (const uploadField of uploadApiSchema.schema) { + try { + // Find saved field mapping from database FIRST to get user's field type selection + const savedMapping = savedFieldMappings.find( + (mapping: any) => + mapping.contentstackFieldUid === uploadField.contentstackFieldUid || + mapping.contentstackFieldUid === uploadField.uid || + mapping.uid === uploadField.contentstackFieldUid || + mapping.uid === uploadField.uid, + ); + + // Use UI-selected field type if available, otherwise use upload-api type + const fieldType = + savedMapping?.contentstackFieldType || + uploadField.contentstackFieldType; + + // Map upload-api field to API format using convertToSchemaFormate + // PRIORITY: Use savedMapping (UI selections from database) FIRST, then fall back to uploadField (upload-api) + const apiField = convertToSchemaFormate({ + field: { + title: uploadField.contentstackField || uploadField.otherCmsField, + uid: uploadField.contentstackFieldUid, + contentstackFieldType: fieldType, // Use UI selection if available + advanced: { + // Spread upload-api defaults first + ...uploadField.advanced, + // Then override with UI selections from savedMapping (database) + mandatory: + savedMapping?.advanced?.mandatory ?? + uploadField.advanced?.mandatory ?? + false, + multiple: + savedMapping?.advanced?.multiple ?? + uploadField.advanced?.multiple ?? + false, + unique: + savedMapping?.advanced?.unique ?? + uploadField.advanced?.unique ?? + false, + nonLocalizable: + savedMapping?.advanced?.nonLocalizable ?? + uploadField.advanced?.non_localizable ?? + false, + default_value: + savedMapping?.advanced?.default_value ?? + uploadField.advanced?.default_value ?? + '', + validationRegex: + savedMapping?.advanced?.validationRegex ?? + uploadField.advanced?.format ?? + '', + validationErrorMessage: + savedMapping?.advanced?.validationErrorMessage ?? + uploadField.advanced?.error_message ?? + '', + embedObjects: + savedMapping?.advanced?.embedObjects ?? + uploadField.advanced?.embedObjects ?? + [], + embedObject: savedMapping?.advanced?.embedObject ?? false, + }, + // For reference fields, use UI selections first, then fall back to upload-api + refrenceTo: + savedMapping?.refrenceTo || + savedMapping?.referenceTo || + uploadField.advanced?.reference_to || + uploadField.advanced?.embedObjects || + [], + // For taxonomy fields, preserve taxonomies from upload-api + taxonomies: uploadField.advanced?.taxonomies || [], + }, + advanced: true, + }); + + if (apiField) { + // Use UI selections if available, otherwise fall back to upload-api data + // Check against the FINAL field type (which may have been changed in UI) + if (fieldType === 'reference') { + // Check both spellings: refrenceTo (legacy typo used by UI) and referenceTo (correct spelling) + const uiReferences = + savedMapping?.refrenceTo || savedMapping?.referenceTo || []; + const hasUIReferences = + Array.isArray(uiReferences) && uiReferences.length > 0; + + // Also check advanced.referenedItems which is where the UI stores reference selections + const advancedReferences = + savedMapping?.advanced?.referenedItems || []; + const hasAdvancedReferences = + Array.isArray(advancedReferences) && advancedReferences.length > 0; + + if (hasUIReferences || hasAdvancedReferences) { + // MERGE: Combine old upload-api data with new UI selections (no duplicates) + // Check both embedObjects AND reference_to from upload-api + const oldReferences = + uploadField.advanced?.embedObjects || + uploadField.advanced?.reference_to || + []; + // Combine all reference sources + const newReferences = [...uiReferences, ...advancedReferences]; + const mergedReferences = [ + ...new Set([...oldReferences, ...newReferences]), + ].filter((ref) => ref && ref.toLowerCase() !== 'profile'); // Filter out profile + + apiField.reference_to = mergedReferences; + } else { + // Fall back to upload-api data only (check both embedObjects and reference_to) + const fallbackReferences = ( + uploadField.advanced?.embedObjects || + uploadField.advanced?.reference_to || + [] + ).filter((ref: string) => ref && ref.toLowerCase() !== 'profile'); // Filter out profile + + if (fallbackReferences && fallbackReferences.length > 0) { + apiField.reference_to = fallbackReferences; + } + } + } + + if (fieldType === 'taxonomy') { + // Check both spellings: refrenceTo (legacy typo used by UI) and referenceTo (correct spelling) + const uiTaxonomies = + savedMapping?.refrenceTo || savedMapping?.referenceTo || []; + const hasUITaxonomies = + Array.isArray(uiTaxonomies) && uiTaxonomies.length > 0; + + // Also check advanced.referenedItems which is where the UI stores taxonomy selections + const advancedTaxonomies = + savedMapping?.advanced?.referenedItems || []; + const hasAdvancedTaxonomies = + Array.isArray(advancedTaxonomies) && advancedTaxonomies.length > 0; + + if (hasUITaxonomies || hasAdvancedTaxonomies) { + // MERGE: Combine old upload-api taxonomies with new UI selections (no duplicates) + const oldTaxonomyUIDs = ( + uploadField.advanced?.taxonomies || [] + ).map((t: any) => t.taxonomy_uid || t); + // Combine all taxonomy sources + const newTaxonomyUIDs = [...uiTaxonomies, ...advancedTaxonomies]; + const mergedTaxonomyUIDs = [ + ...new Set([...oldTaxonomyUIDs, ...newTaxonomyUIDs]), + ]; + + // Convert UIDs to taxonomy format + apiField.taxonomies = mergedTaxonomyUIDs.map((taxUid: string) => ({ + taxonomy_uid: taxUid, + mandatory: + savedMapping?.advanced?.mandatory ?? + uploadField.advanced?.mandatory ?? + false, + multiple: + savedMapping?.advanced?.multiple ?? + uploadField.advanced?.multiple !== false, // Default to true for taxonomies + non_localizable: + savedMapping?.advanced?.nonLocalizable ?? + uploadField.advanced?.non_localizable ?? + false, + })); + } else if (uploadField.advanced?.taxonomies) { + // Fall back to upload-api data only + apiField.taxonomies = uploadField.advanced.taxonomies; + } + } + + // Preserve field metadata for proper field type conversion + if (uploadField.advanced?.multiline !== undefined) { + apiField.field_metadata = apiField.field_metadata || {}; + apiField.field_metadata.multiline = uploadField.advanced.multiline; + } + + apiSchema.schema.push(apiField); + } + } catch (error: any) { + // Fallback: create basic field structure + apiSchema.schema.push({ + display_name: + uploadField.contentstackField || + uploadField.otherCmsField || + uploadField.uid, + uid: uploadField.contentstackFieldUid || uploadField.uid, + data_type: mapFieldTypeToDataType(uploadField.contentstackFieldType), + mandatory: uploadField.advanced?.mandatory || false, + unique: uploadField.advanced?.unique || false, + field_metadata: { _default: true }, + format: '', + error_messages: { format: '' }, + multiple: uploadField.advanced?.multiple || false, + non_localizable: uploadField.advanced?.non_localizable || false, + }); + } + } + + return apiSchema; +} + +/** + * Maps upload-api field types to API data types + * This ensures proper field type preservation from upload-api to API + */ +function mapFieldTypeToDataType(fieldType: string): string { + const fieldTypeMap: { [key: string]: string } = { + single_line_text: 'text', + multi_line_text: 'text', + text: 'text', + html: 'html', + json: 'json', + markdown: 'text', + number: 'number', + boolean: 'boolean', + isodate: 'isodate', + file: 'file', + reference: 'reference', + taxonomy: 'taxonomy', + link: 'link', + dropdown: 'text', + radio: 'text', + checkbox: 'boolean', + global_field: 'global_field', + group: 'group', + url: 'text', + }; + + return fieldTypeMap[fieldType] || 'text'; +} + +// Removed regenerateCombinedSchemaFromIndividualFiles function +// We now generate ONLY schema.json directly, no individual files diff --git a/api/src/services/drupal/entries.service.ts b/api/src/services/drupal/entries.service.ts new file mode 100644 index 000000000..9fd8c1ae8 --- /dev/null +++ b/api/src/services/drupal/entries.service.ts @@ -0,0 +1,1782 @@ +/* eslint-disable */ + +import fs from 'fs'; +import path from 'path'; +import mysql from 'mysql2'; +import { JSDOM } from 'jsdom'; +import _ from 'lodash'; +import { + htmlToJson, + jsonToHtml, + jsonToMarkdown, +} from '@contentstack/json-rte-serializer'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; +import { getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { getDbConnection } from '../../helper/index.js'; +import { + analyzeFieldTypes, + isTaxonomyField, + isReferenceField, + isAssetField, + type TaxonomyFieldMapping, + type ReferenceFieldMapping, + type AssetFieldMapping, +} from './field-analysis.service.js'; +import FieldFetcherService from './field-fetcher.service.js'; +import { mapDrupalLocales } from './locales.service.js'; +// Dynamic import for phpUnserialize will be used in the function + +// Local utility functions (extracted from entries-field-creator.utils.ts patterns) +// Default prefix fallback if none provided +const DEFAULT_PREFIX = 'cs'; + +function startsWithNumber(str: string) { + return /^\d/.test(str); +} + +const uidCorrector = ({ + uid, + id, + prefix, +}: { + uid?: string; + id?: string; + prefix?: string; +}) => { + const value = uid || id; + if (!value) return ''; + + const effectivePrefix = prefix || DEFAULT_PREFIX; + + if (startsWithNumber(value)) { + return `${effectivePrefix}_${_.replace( + value, + new RegExp('[ -]', 'g'), + '_', + )?.toLowerCase()}`; + } + return _.replace(value, new RegExp('[ -]', 'g'), '_')?.toLowerCase(); +}; + +interface TaxonomyReference { + drupal_term_id: number; + taxonomy_uid: string; + term_uid: string; +} + +interface TaxonomyFieldOutput { + taxonomy_uid: string; + term_uid: string; +} + +const { + DATA, + ENTRIES_DIR_NAME, + ASSETS_DIR_NAME, + REFERENCES_DIR_NAME, + REFERENCES_FILE_NAME, + TAXONOMIES_DIR_NAME, +} = MIGRATION_DATA_CONFIG; + +interface DrupalFieldConfig { + field_name: string; + field_type: string; + settings?: { + handler?: string; + [key: string]: any; + }; + [key: string]: any; +} + +interface DrupalEntry { + nid: number; + title: string; + langcode: string; + created: number; + type: string; + [key: string]: any; +} + +interface QueryConfig { + page: { + [contentType: string]: string; + }; + count: { + [contentTypeCount: string]: string; + }; +} + +const LIMIT = 5; // Pagination limit + +// NOTE: Hardcoded queries have been REMOVED. All queries are now generated dynamically +// by the query.service.ts based on actual database field analysis. + +/** + * Executes SQL query and returns results as Promise + */ +const executeQuery = ( + connection: mysql.Connection, + query: string, +): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + } else { + resolve(results as any[]); + } + }); + }); +}; + +/** + * Load taxonomy reference mappings from taxonomyReference.json + */ +const loadTaxonomyReferences = async ( + referencesPath: string, +): Promise> => { + try { + const taxonomyRefPath = path.join(referencesPath, 'taxonomyReference.json'); + + if (!fs.existsSync(taxonomyRefPath)) { + return {}; + } + + const taxonomyReferences: TaxonomyReference[] = JSON.parse( + fs.readFileSync(taxonomyRefPath, 'utf8'), + ); + + // Create lookup map: drupal_term_id -> {taxonomy_uid, term_uid} + const lookup: Record = {}; + + taxonomyReferences.forEach((ref) => { + lookup[ref.drupal_term_id] = { + taxonomy_uid: ref.taxonomy_uid, + term_uid: ref.term_uid, + }; + }); + + return lookup; + } catch (error) { + console.error('Could not load taxonomy references:', error); + return {}; + } +}; + +/** + * Writes data to a specified file, ensuring the target directory exists. + */ +async function writeFile(dirPath: string, filename: string, data: any) { + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + const filePath = path.join(dirPath, filename); + await fs.promises.writeFile(filePath, JSON.stringify(data), 'utf8'); + } catch (err) { + console.error(`Error writing ${dirPath}/${filename}::`, err); + } +} + +/** + * Reads a file and returns its JSON content. + */ +async function readFile(filePath: string, fileName: string) { + try { + const data = await fs.promises.readFile( + path.join(filePath, fileName), + 'utf8', + ); + return JSON.parse(data); + } catch (err) { + return {}; + } +} + +/** + * Fetches field configurations from Drupal database + */ +const fetchFieldConfigs = async ( + connection: mysql.Connection, + projectId: string, + destination_stack_id: string, +): Promise => { + const srcFunc = 'fetchFieldConfigs'; + const contentTypeQuery = + "SELECT *, CONVERT(data USING utf8) as data FROM config WHERE name LIKE '%field.field.node%'"; + + try { + const results = await executeQuery(connection, contentTypeQuery); + + const fieldConfigs: DrupalFieldConfig[] = []; + for (const row of results) { + try { + const { unserialize } = await import('php-serialize'); + const configData = unserialize(row.data); + if (configData && typeof configData === 'object') { + fieldConfigs.push(configData as DrupalFieldConfig); + } + } catch (parseError) { + console.error( + `Failed to parse field config for ${row.name}:`, + parseError, + ); + } + } + + const message = getLogMessage( + srcFunc, + `Fetched ${fieldConfigs.length} field configurations from database.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return fieldConfigs; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Failed to fetch field configurations: ${error.message}`, + {}, + error, + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Determines the source field type based on the value structure + */ +const determineSourceFieldType = (value: any): string => { + if (typeof value === 'object' && value !== null && value.type === 'doc') { + return 'json_rte'; + } + if (typeof value === 'string' && /<\/?[a-z][\s\S]*>/i.test(value)) { + return 'html_rte'; + } + if (typeof value === 'string') { + // Simple heuristic: if it has line breaks, consider it multi-line + return value.includes('\n') || value.includes('\r') + ? 'multi_line' + : 'single_line'; + } + // Handle numeric values (common for reference IDs, taxonomy term IDs) + if ( + typeof value === 'number' || + (typeof value === 'string' && !isNaN(Number(value))) + ) { + return 'number'; + } + // Handle arrays (could be multi-value fields like taxonomy) + if (Array.isArray(value)) { + return 'array'; + } + return 'unknown'; +}; + +/** + * Checks if conversion is allowed based on the exact rules: + * 1. Single-line text → Single-line/Multi-line/HTML RTE/JSON RTE + * 2. Multi-line text → Multi-line/HTML RTE/JSON RTE (NOT Single-line) + * 3. HTML RTE → HTML RTE/JSON RTE (NOT Single-line or Multi-line) + * 4. JSON RTE → JSON RTE/HTML RTE (NOT Single-line or Multi-line) + */ +const isConversionAllowed = ( + sourceType: string, + targetType: string, +): boolean => { + const conversionRules: { [key: string]: string[] } = { + // ✅ Single line can convert to single_line, multi_line, json_rte, html_rte + single_line: [ + 'single_line_text', + 'single_line', + 'text', + 'multi_line_text', + 'multi_line', + 'html', + 'html_rte', + 'json', + 'json_rte', + ], + // ✅ Multi-line can convert to multi_line, json_rte, html_rte (no downgrade to single_line) + multi_line: [ + 'multi_line_text', + 'multi_line', + 'text', + 'html', + 'html_rte', + 'json', + 'json_rte', + ], + // ✅ HTML RTE can only convert to HTML RTE or JSON RTE (no downgrade to text) + html_rte: ['html', 'json', 'html_rte', 'json_rte'], + // ✅ JSON RTE can only convert to JSON RTE or HTML RTE (no downgrade to text) + json_rte: ['json', 'html', 'json_rte', 'html_rte'], + // ✅ Numbers can convert to taxonomy (term IDs), reference, file, number fields + number: [ + 'taxonomy', + 'reference', + 'file', + 'number', + 'single_line_text', + 'text', + ], + // ✅ Arrays can convert to taxonomy (multiple term IDs), reference (multiple refs) + array: ['taxonomy', 'reference', 'file'], + // ✅ Unknown types - allow conversion to non-text types (fallback) + unknown: ['taxonomy', 'reference', 'file', 'number', 'link'], + }; + + return conversionRules[sourceType]?.includes(targetType) || false; +}; + +/** + * Processes field values based on content type mapping and field type switching + * Follows proper conversion rules for field type compatibility + */ +const processFieldByType = ( + value: any, + fieldMapping: any, + assetId: any, + referenceId: any, +): any => { + if (!fieldMapping || !fieldMapping.contentstackFieldType) { + return value; + } + + // Determine source field type + const sourceType = determineSourceFieldType(value); + const targetType = fieldMapping.contentstackFieldType; + + // Check if conversion is allowed + if (!isConversionAllowed(sourceType, targetType)) { + console.error( + `Conversion not allowed: ${sourceType} → ${targetType}. Keeping original value.`, + ); + return value; + } + + switch (targetType) { + case 'single_line_text': { + // Convert to single line text + if (typeof value === 'object' && value !== null && value.type === 'doc') { + // JSON RTE to plain text (extract text content) + try { + const htmlContent = jsonToHtml(value) || ''; + // Strip HTML tags and convert to single line + const textContent = htmlContent + .replace(/<[^>]*>/g, '') + .replace(/\s+/g, ' ') + .trim(); + return textContent; + } catch (error) { + console.error( + 'Failed to convert JSON RTE to single line text:', + error, + ); + return String(value); + } + } else if (typeof value === 'string') { + if (/<\/?[a-z][\s\S]*>/i.test(value)) { + // HTML to plain text + const textContent = value + .replace(/<[^>]*>/g, '') + .replace(/\s+/g, ' ') + .trim(); + return textContent; + } + // Multi-line to single line + return value.replace(/\s+/g, ' ').trim(); + } + return String(value); + } + + case 'text': + case 'multi_line_text': { + // Convert to multi-line text + if (typeof value === 'object' && value !== null && value.type === 'doc') { + // JSON RTE to HTML (preserving structure) + try { + return ( + jsonToHtml(value, { + customElementTypes: { + 'social-embed': (attrs, child, jsonBlock) => { + return `${child}`; + }, + }, + customTextWrapper: { + color: (child, value) => { + return `${child}`; + }, + }, + }) || '' + ); + } catch (error) { + console.error('Failed to convert JSON RTE to HTML:', error); + return String(value); + } + } + // HTML and plain text can stay as-is for multi-line + return typeof value === 'string' ? value : String(value || ''); + } + + case 'json': { + // Convert to JSON RTE + if (typeof value === 'string' && /<\/?[a-z][\s\S]*>/i.test(value)) { + // HTML to JSON RTE + try { + const dom = new JSDOM(value); + const htmlDoc = dom.window.document.querySelector('body'); + if (htmlDoc) { + htmlDoc.innerHTML = value; + return htmlToJson(htmlDoc); + } + } catch (error) { + console.error('Failed to convert HTML to JSON RTE:', error); + } + } else if (typeof value === 'string') { + // Plain text to JSON RTE + try { + const dom = new JSDOM(`

${value}

`); + const htmlDoc = dom.window.document.querySelector('body'); + if (htmlDoc) { + return htmlToJson(htmlDoc); + } + } catch (error) { + console.error('Failed to convert text to JSON RTE:', error); + } + } + // If already JSON RTE or conversion failed, return as-is + return value; + } + + case 'html': { + // Convert to HTML RTE + if (typeof value === 'object' && value !== null && value.type === 'doc') { + // JSON RTE to HTML + try { + return ( + jsonToHtml(value, { + customElementTypes: { + 'social-embed': (attrs, child, jsonBlock) => { + return `${child}`; + }, + }, + customTextWrapper: { + color: (child, value) => { + return `${child}`; + }, + }, + }) || '

' + ); + } catch (error) { + console.error('Failed to convert JSON RTE to HTML:', error); + return value; + } + } else if (typeof value === 'string') { + // Check if it's already HTML + if (/<\/?[a-z][\s\S]*>/i.test(value)) { + // Already HTML, return as-is + return value; + } else { + // Plain text to HTML - wrap in paragraph tags + return `

${value}

`; + } + } + return typeof value === 'string' ? value : String(value || ''); + } + + case 'markdown': { + // Convert to Markdown + if (typeof value === 'object' && value !== null && value.type === 'doc') { + try { + return jsonToMarkdown(value); + } catch (error) { + console.error('Failed to convert JSON RTE to Markdown:', error); + return value; + } + } + return typeof value === 'string' ? value : String(value || ''); + } + + case 'file': { + // File/Asset processing with proper validation and cleanup + if (fieldMapping.advanced?.multiple) { + // Multiple files + if (Array.isArray(value)) { + const validAssets = value + .map((assetRef) => { + const assetKey = `assets_${assetRef}`; + const assetReference = assetId[assetKey]; + + if (assetReference && typeof assetReference === 'object') { + return assetReference; + } + + console.error( + `Asset ${assetKey} not found or invalid, excluding from array`, + ); + return null; + }) + .filter((asset) => asset !== null); // Remove null entries + + return validAssets.length > 0 ? validAssets : undefined; // Return undefined if no valid assets + } + } else { + // Single file + const assetKey = `assets_${value}`; + const assetReference = assetId[assetKey]; + + if (assetReference && typeof assetReference === 'object') { + return assetReference; + } + + console.error(`Asset ${assetKey} not found or invalid, removing field`); + return undefined; // Return undefined to indicate field should be removed + } + return value; + } + + case 'reference': { + // Reference processing + if (fieldMapping.advanced?.multiple) { + // Multiple references + if (Array.isArray(value)) { + return value.map( + (refId) => + referenceId[`content_type_entries_title_${refId}`] || refId, + ); + } + } else { + // Single reference + return [referenceId[`content_type_entries_title_${value}`] || value]; + } + return value; + } + + case 'number': { + // Number processing + if (typeof value === 'string') { + const parsed = parseInt(value); + return isNaN(parsed) ? 0 : parsed; + } + return typeof value === 'number' ? value : 0; + } + + case 'boolean': { + // Boolean processing + if (typeof value === 'string') { + return value === '1' || value.toLowerCase() === 'true'; + } + return Boolean(value); + } + + case 'isodate': { + // Date processing + if (typeof value === 'number') { + return new Date(value * 1000).toISOString(); + } + return value; + } + + case 'taxonomy': { + // Taxonomy processing - return the value as-is (will be processed by consolidateTaxonomyFields) + // The actual taxonomy lookup and transformation happens in the main entry processing loop + // This case just ensures the value passes through without error + if (Array.isArray(value)) { + return value; + } + if ( + typeof value === 'number' || + (typeof value === 'string' && !isNaN(Number(value))) + ) { + // Single taxonomy term ID - return as array for consistent processing + return [Number(value)]; + } + return value; + } + + case 'link': { + // Link field processing - ensure proper structure + if (typeof value === 'object' && value !== null) { + return value; + } + if (typeof value === 'string') { + return { href: value, title: '' }; + } + return value; + } + + default: { + // Default processing - handle HTML content + if (typeof value === 'string' && /<\/?[a-z][\s\S]*>/i.test(value)) { + try { + const dom = new JSDOM(value); + const htmlDoc = dom.window.document.querySelector('body'); + return htmlToJson(htmlDoc); + } catch (error) { + return value; + } + } + return value; + } + } +}; + +/** + * Consolidates all taxonomy fields into a single 'taxonomies' field with unique term_uid validation + * Uses the same pattern as entries-field-creator.utils.ts + */ +const consolidateTaxonomyFields = ( + processedEntry: any, + contentType: string, + taxonomyFieldMapping: TaxonomyFieldMapping, +): any => { + const consolidatedTaxonomies: Array<{ + taxonomy_uid: string; + term_uid: string; + }> = []; + const fieldsToRemove: string[] = []; + const seenTermUids = new Set(); // Track unique term_uid values + + // Iterate through all fields in the processed entry + for (const [fieldKey, fieldValue] of Object.entries(processedEntry)) { + // Extract field name from key (remove _target_id suffix) + const fieldName = fieldKey.replace(/_target_id$/, ''); + + // Check if this is a taxonomy field using field analysis + if (isTaxonomyField(fieldName, contentType, taxonomyFieldMapping)) { + // Validate that field value is an array with taxonomy structure + if (Array.isArray(fieldValue)) { + for (const taxonomyItem of fieldValue) { + // Validate taxonomy structure + if ( + taxonomyItem && + typeof taxonomyItem === 'object' && + taxonomyItem.taxonomy_uid && + taxonomyItem.term_uid + ) { + // Check for unique term_uid (avoid duplicates) + if (!seenTermUids.has(taxonomyItem.term_uid)) { + consolidatedTaxonomies.push({ + taxonomy_uid: taxonomyItem.taxonomy_uid, + term_uid: taxonomyItem.term_uid, + }); + seenTermUids.add(taxonomyItem.term_uid); + } + } + } + } + + // Mark this field for removal + fieldsToRemove.push(fieldKey); + } + } + + // Create new entry object without the original taxonomy fields + const consolidatedEntry = { ...processedEntry }; + + // Remove original taxonomy fields + for (const fieldKey of fieldsToRemove) { + delete consolidatedEntry[fieldKey]; + } + + // Add consolidated taxonomy field if we have any taxonomies + if (consolidatedTaxonomies.length > 0) { + consolidatedEntry.taxonomies = consolidatedTaxonomies; + } + + return consolidatedEntry; +}; + +/** + * Processes field values based on field configuration - following original Drupal logic + */ +const processFieldData = async ( + entryData: DrupalEntry, + fieldConfigs: DrupalFieldConfig[], + assetId: any, + referenceId: any, + taxonomyId: any, + taxonomyFieldMapping: TaxonomyFieldMapping, + referenceFieldMapping: ReferenceFieldMapping, + assetFieldMapping: any, + taxonomyReferenceLookup: Record, + contentType: string, + prefix: string = DEFAULT_PREFIX, +): Promise => { + const fieldNames = Object.keys(entryData); + const isoDate = new Date(); + const processedData: any = {}; + const skippedFields = new Set(); // Track fields that should be skipped entirely + const processedFields = new Set(); // Track fields that have been processed to avoid duplicates + + // Process each field in the entry data + for (const [dataKey, value] of Object.entries(entryData)) { + // Extract field name from dataKey (remove _target_id suffix) + const fieldName = dataKey + .replace(/_target_id$/, '') + .replace(/_value$/, '') + .replace(/_status$/, '') + .replace(/_uri$/, ''); + + // Handle asset fields using field analysis + if ( + dataKey.endsWith('_target_id') && + isAssetField(fieldName, contentType, assetFieldMapping) + ) { + const assetKey = `assets_${value}`; + if (assetKey in assetId) { + // Transform to proper Contentstack asset reference format + const assetReference = assetId[assetKey]; + if (assetReference && typeof assetReference === 'object') { + processedData[dataKey] = assetReference; + } + // If asset reference is not properly structured, skip the field + } + // If asset not found in assets index, mark field as skipped + skippedFields.add(dataKey); + continue; // Skip further processing for this field + } + + // Handle entity references (taxonomy and node references) using field analysis + // NOTE: value can be a number (single reference) or string (GROUP_CONCAT comma-separated IDs) + if ( + dataKey.endsWith('_target_id') && + (typeof value === 'number' || typeof value === 'string') + ) { + // Check if this is a taxonomy field using our field analysis + if (isTaxonomyField(fieldName, contentType, taxonomyFieldMapping)) { + // Handle both single ID (number) and GROUP_CONCAT result (comma-separated string) + const targetIds = + typeof value === 'string' + ? value + .split(',') + .map((id) => parseInt(id.trim())) + .filter((id) => !isNaN(id)) + : [value]; + + const transformedTaxonomies: Array<{ + taxonomy_uid: string; + term_uid: string; + }> = []; + + for (const tid of targetIds) { + // Look up taxonomy reference using drupal_term_id + const taxonomyRef = taxonomyReferenceLookup[tid]; + + if (taxonomyRef) { + transformedTaxonomies.push({ + taxonomy_uid: taxonomyRef.taxonomy_uid, + term_uid: taxonomyRef.term_uid, + }); + } else { + console.warn( + `⚠️ Taxonomy term ${tid} not found in reference lookup for field ${fieldName}`, + ); + } + } + + if (transformedTaxonomies.length > 0) { + processedData[dataKey] = transformedTaxonomies; + } else { + // Fallback to original value if no lookups succeeded + processedData[dataKey] = value; + } + + // Mark field as processed so it doesn't get overwritten by ctValue loop + processedFields.add(dataKey); + skippedFields.add(dataKey); // Also skip in ctValue loop + + continue; // Skip further processing for this field + } else if ( + isReferenceField(fieldName, contentType, referenceFieldMapping) + ) { + // Handle node reference fields using field analysis + // Handle both single ID (number) and GROUP_CONCAT result (comma-separated string) + const targetIds = + typeof value === 'string' + ? value + .split(',') + .map((id) => parseInt(id.trim())) + .filter((id) => !isNaN(id)) + : [value]; + + const transformedReferences: any[] = []; + + for (const nid of targetIds) { + const referenceKey = `content_type_entries_title_${nid}`; + if (referenceKey in referenceId) { + transformedReferences.push(referenceId[referenceKey]); + } + } + + if (transformedReferences.length > 0) { + processedData[dataKey] = transformedReferences; + } else { + // If no references found, mark field as skipped + skippedFields.add(dataKey); + } + + // Mark field as processed so it doesn't get overwritten by ctValue loop + processedFields.add(dataKey); + skippedFields.add(dataKey); // Also skip in ctValue loop + + continue; // Skip further processing for this field + } + } + + // Handle other field types by checking field configs + const matchingFieldConfig = fieldConfigs.find( + (fc) => + dataKey === `${fc.field_name}_value` || + dataKey === `${fc.field_name}_status` || + dataKey === fc.field_name, + ); + + if (matchingFieldConfig) { + // Handle datetime and timestamps + if ( + matchingFieldConfig.field_type === 'datetime' || + matchingFieldConfig.field_type === 'timestamp' + ) { + if (dataKey === `${matchingFieldConfig.field_name}_value`) { + if (typeof value === 'number') { + processedData[dataKey] = new Date(value * 1000).toISOString(); + } else { + processedData[dataKey] = isoDate.toISOString(); + } + // Mark field as processed to avoid duplicate processing in second loop + processedFields.add(dataKey); + processedFields.add(matchingFieldConfig.field_name); + continue; + } + } + + // Handle boolean fields + if (matchingFieldConfig.field_type === 'boolean') { + if ( + dataKey === `${matchingFieldConfig.field_name}_value` && + typeof value === 'number' + ) { + processedData[dataKey] = value === 1; + // Mark field as processed to avoid duplicate processing in second loop + processedFields.add(dataKey); + processedFields.add(matchingFieldConfig.field_name); + continue; + } + } + + // Handle comment fields + if (matchingFieldConfig.field_type === 'comment') { + if ( + dataKey === `${matchingFieldConfig.field_name}_status` && + typeof value === 'number' + ) { + processedData[dataKey] = `${value}`; + // Mark field as processed to avoid duplicate processing in second loop + processedFields.add(dataKey); + processedFields.add(matchingFieldConfig.field_name); + continue; + } + } + } + + // Remove null, undefined, and empty values + if (value === null || value === undefined || value === '') { + // Skip null, undefined, and empty string values + continue; + } + + // Default case: copy field to processedData if it wasn't handled by special processing above + if (!(dataKey in processedData)) { + processedData[dataKey] = value; + } + } + + // Process standard field transformations + const ctValue: any = {}; + + for (const fieldName of fieldNames) { + // Skip fields that were intentionally excluded in the main processing loop + if (skippedFields.has(fieldName)) { + continue; + } + + const value = entryData[fieldName]; + + if (fieldName === 'created') { + ctValue[fieldName] = new Date(value * 1000).toISOString(); + } else if (fieldName === 'uid_name') { + ctValue[fieldName] = [value]; + } else if (fieldName.endsWith('_tid')) { + ctValue[fieldName] = [value]; + } else if (fieldName === 'nid') { + ctValue.uid = uidCorrector({ + id: `content_type_entries_title_${value}`, + prefix, + }); + } else if (fieldName === 'langcode') { + // Use the actual langcode from the entry for proper multilingual support + ctValue.locale = value || 'en-us'; // fallback to en-us if langcode is empty + } else if (fieldName.endsWith('_uri')) { + // Skip if this field has already been processed + if (processedFields.has(fieldName)) { + continue; + } + + const baseFieldName = fieldName.replace('_uri', ''); + const titleFieldName = `${baseFieldName}_title`; + + // Check if we also have title data + const titleValue = entryData[titleFieldName]; + + if (value) { + ctValue[baseFieldName] = { + title: titleValue || value, // Use title if available, fallback to URI + href: value, + }; + } else { + ctValue[baseFieldName] = { + title: titleValue || '', + href: '', + }; + } + + // Mark title field as processed to avoid duplicate processing + if (titleValue) { + processedFields.add(titleFieldName); + } + } else if (fieldName.endsWith('_title')) { + // Skip _title fields as they're handled with _uri fields + if (processedFields.has(fieldName)) { + continue; + } + + // Check if there's a corresponding _uri field + const baseFieldName = fieldName.replace('_title', ''); + const uriFieldName = `${baseFieldName}_uri`; + + if (entryData[uriFieldName]) { + // URI field will handle this, skip processing here + continue; + } else { + // No URI field found, process title field standalone (rare case) + ctValue[baseFieldName] = { + title: value || '', + href: '', + }; + } + } else if (fieldName.endsWith('_value')) { + // Skip if this field was already processed in the main loop (avoid duplicates) + const baseFieldName = fieldName.replace('_value', ''); + if ( + processedFields.has(fieldName) || + processedFields.has(baseFieldName) + ) { + continue; + } + + // Check if content contains HTML + if (/<\/?[a-z][\s\S]*>/i.test(value)) { + const dom = new JSDOM(value); + const htmlDoc = dom.window.document.querySelector('body'); + const jsonValue = htmlToJson(htmlDoc); + ctValue[baseFieldName] = jsonValue; + } else { + ctValue[baseFieldName] = value; + } + + // Mark both the original and base field as processed to avoid duplicates + processedFields.add(fieldName); + processedFields.add(baseFieldName); + } else if (fieldName.endsWith('_status')) { + // Skip if this field was already processed in the main loop (avoid duplicates) + const baseFieldName = fieldName.replace('_status', ''); + if ( + processedFields.has(fieldName) || + processedFields.has(baseFieldName) + ) { + continue; + } + + ctValue[baseFieldName] = value; + + // Mark both the original and base field as processed to avoid duplicates + processedFields.add(fieldName); + processedFields.add(baseFieldName); + } else { + // Check if content contains HTML + if (typeof value === 'string' && /<\/?[a-z][\s\S]*>/i.test(value)) { + const dom = new JSDOM(value); + const htmlDoc = dom.window.document.querySelector('body'); + const jsonValue = htmlToJson(htmlDoc); + ctValue[fieldName] = jsonValue; + } else { + ctValue[fieldName] = value; + } + } + } + + // Apply processed field data, but prioritize ctValue (processed without suffixes) over processedData (with suffixes) + // This prevents duplicate fields like both 'body' and 'body_value' from appearing + const mergedData = { ...processedData, ...ctValue }; + + // Final cleanup: remove any null, undefined, or empty values from the final result + // Also remove duplicate fields where both suffixed and non-suffixed versions exist + const cleanedEntry: any = {}; + for (const [key, val] of Object.entries(mergedData)) { + if (val !== null && val !== undefined && val !== '') { + // Check if this is a suffixed field (_value, _status, _uri) and if a non-suffixed version exists + const isValueField = key.endsWith('_value'); + const isStatusField = key.endsWith('_status'); + const isUriField = key.endsWith('_uri'); + + if (isValueField) { + const baseFieldName = key.replace('_value', ''); + // Only include the _value field if the base field doesn't exist + if (!mergedData.hasOwnProperty(baseFieldName)) { + cleanedEntry[key] = val; + } + // If base field exists, skip the _value field (base field takes priority) + } else if (isStatusField) { + const baseFieldName = key.replace('_status', ''); + // Only include the _status field if the base field doesn't exist + if (!mergedData.hasOwnProperty(baseFieldName)) { + cleanedEntry[key] = val; + } + // If base field exists, skip the _status field (base field takes priority) + } else if (isUriField) { + const baseFieldName = key.replace('_uri', ''); + // Only include the _uri field if the base field doesn't exist + if (!mergedData.hasOwnProperty(baseFieldName)) { + cleanedEntry[key] = val; + } + // If base field exists, skip the _uri field (base field takes priority) + } else { + // For non-suffixed fields, always include them + cleanedEntry[key] = val; + } + } + } + + return cleanedEntry; +}; + +/** + * Processes entries for a specific content type and pagination offset + */ +const processEntries = async ( + connection: mysql.Connection, + contentType: string, + skip: number, + queryPageConfig: QueryConfig, + fieldConfigs: DrupalFieldConfig[], + assetId: any, + referenceId: any, + taxonomyId: any, + taxonomyFieldMapping: TaxonomyFieldMapping, + referenceFieldMapping: ReferenceFieldMapping, + assetFieldMapping: AssetFieldMapping, + taxonomyReferenceLookup: Record, + projectId: string, + destination_stack_id: string, + masterLocale: string, + contentTypeMapping: any[] = [], + isTest: boolean = false, + project: any = null, +): Promise<{ [key: string]: any } | null> => { + const srcFunc = 'processEntries'; + + try { + // Following original pattern: queryPageConfig['page']['' + pagename + ''] + const baseQuery = queryPageConfig['page'][contentType]; + if (!baseQuery) { + throw new Error(`No query found for content type: ${contentType}`); + } + + // Check if this is an optimized query (content type with many fields) + const isOptimizedQuery = baseQuery.includes('/* OPTIMIZED_NO_JOINS:'); + let entries: any[] = []; + + if (isOptimizedQuery) { + // Handle content types with many fields using optimized approach + const fieldCountMatch = baseQuery.match( + /\/\* OPTIMIZED_NO_JOINS:(\d+) \*\//, + ); + const fieldCount = fieldCountMatch ? parseInt(fieldCountMatch[1]) : 0; + + const optimizedMessage = getLogMessage( + srcFunc, + `Processing ${contentType} with optimized field fetching (${fieldCount} fields)`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + optimizedMessage, + ); + + // Execute base query without field JOINs + const effectiveLimit = isTest ? 1 : LIMIT; + const cleanBaseQuery = baseQuery + .replace(/\/\* OPTIMIZED_NO_JOINS:\d+ \*\//, '') + .trim(); + const query = cleanBaseQuery + ` LIMIT ${skip}, ${effectiveLimit}`; + const baseEntries = await executeQuery(connection, query); + + if (baseEntries.length === 0) { + return null; + } + + // Fetch field data separately using FieldFetcherService + const fieldFetcher = new FieldFetcherService( + connection, + projectId, + destination_stack_id, + ); + const nodeIds = baseEntries.map((entry) => entry.nid); + const fieldsForType = await fieldFetcher.getFieldsForContentType( + contentType, + ); + + if (fieldsForType.length > 0) { + const fieldData = await fieldFetcher.fetchFieldDataForContentType( + contentType, + nodeIds, + fieldsForType, + ); + + // Merge base entries with field data + entries = fieldFetcher.mergeNodeAndFieldData(baseEntries, fieldData); + + const mergeMessage = getLogMessage( + srcFunc, + `Merged ${baseEntries.length} base entries with field data for ${contentType}`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + mergeMessage, + ); + } else { + entries = baseEntries; + } + } else { + // Handle content types with few fields using traditional approach + const effectiveLimit = isTest ? 1 : LIMIT; + const query = baseQuery + ` LIMIT ${skip}, ${effectiveLimit}`; + entries = await executeQuery(connection, query); + + if (entries.length === 0) { + return null; + } + } + + // Group entries by their actual locale (langcode) for proper multilingual support + const entriesByLocale: { [locale: string]: any[] } = {}; + + // Group entries by their langcode + entries.forEach((entry) => { + const entryLocale = entry.langcode || masterLocale; // fallback to masterLocale if no langcode + if (!entriesByLocale[entryLocale]) { + entriesByLocale[entryLocale] = []; + } + entriesByLocale[entryLocale].push(entry); + }); + + // Map source locales to destination locales using user-selected mapping from UI + // This replaces the old hardcoded transformation rules with dynamic user mapping + const transformedEntriesByLocale: { [locale: string]: any[] } = {}; + const allLocales = Object.keys(entriesByLocale); + const hasEn = allLocales.includes('en'); + const hasEnUs = allLocales.includes('en-us'); + + // Get locale mapping configuration from project + const localeMapping = project?.localeMapping || {}; + const localesFromProject = { + masterLocale: project?.master_locale || {}, + ...(project?.locales || {}), + }; + + // Get source master locale from database query + // Use the masterLocale parameter which was fetched from Drupal's system.site config + const sourceMasterLocale = masterLocale || 'en'; + + // Get destination master locale from project configuration + // Priority: localeMapping -> master_locale values -> stackDetails.master_locale -> masterLocale + const masterLocaleKey = `${sourceMasterLocale}-master_locale`; + const destinationMasterLocale = + localeMapping?.[masterLocaleKey] || + Object.values(project?.master_locale || {})?.[0] || // ✅ Use values() not keys()! + project?.stackDetails?.master_locale || + masterLocale || + 'en-us'; + // Apply source locale transformation rules first (und → en-us, etc.) + // Then map the transformed source locale to destination locale using user's selection + Object.entries(entriesByLocale).forEach(([originalLocale, entries]) => { + // Step 1: Apply Drupal-specific transformation rules (same as before) + let transformedSourceLocale = originalLocale; + + if (originalLocale === 'und') { + if (hasEn && hasEnUs) { + // Rule 4: All three "en" + "und" + "en-us" → all three stays + transformedSourceLocale = 'und'; + } else if (hasEnUs && !hasEn) { + // Rule 2: "und" + "en-us" → "und" become "en", "en-us" stays + transformedSourceLocale = 'en'; + } else if (hasEn && !hasEnUs) { + // Rule 3: "en" + "und" → "und" becomes "en-us", "en" stays + transformedSourceLocale = 'en-us'; + } else if (!hasEn && !hasEnUs) { + // Rule 1: "und" alone → "en-us" + transformedSourceLocale = 'en-us'; + } else { + // Keep as is for any other combinations + transformedSourceLocale = 'und'; + } + } else if (originalLocale === 'en-us') { + // "en-us" always stays as "en-us" in all rules + transformedSourceLocale = 'en-us'; + } else if (originalLocale === 'en') { + // "en" always stays as "en" in all rules (never transforms to "und") + transformedSourceLocale = 'en'; + } + + // Step 2: Map transformed source locale to destination locale using user's mapping + const destinationLocale = mapDrupalLocales({ + masterLocale, + locale: transformedSourceLocale, + locales: localesFromProject, + localeMapping, + sourceMasterLocale, + destinationMasterLocale, + }); + + // Merge entries if destination locale already has entries + if (transformedEntriesByLocale[destinationLocale]) { + transformedEntriesByLocale[destinationLocale] = [ + ...transformedEntriesByLocale[destinationLocale], + ...entries, + ]; + } else { + transformedEntriesByLocale[destinationLocale] = entries; + } + }); + + // Find content type mapping for field type switching + const currentContentTypeMapping = contentTypeMapping.find( + (ct) => + ct.otherCmsUid === contentType || ct.contentstackUid === contentType, + ); + + const allProcessedContent: { [key: string]: any } = {}; + + // Process entries for each transformed locale separately + for (const [currentLocale, localeEntries] of Object.entries( + transformedEntriesByLocale, + )) { + // Create folder structure: entries/contentType/locale/ + const contentTypeFolderPath = path.join( + MIGRATION_DATA_CONFIG.DATA, + destination_stack_id, + MIGRATION_DATA_CONFIG.ENTRIES_DIR_NAME, + contentType, + ); + const localeFolderPath = path.join(contentTypeFolderPath, currentLocale); + await fs.promises.mkdir(localeFolderPath, { recursive: true }); + + // Read existing content for this locale or initialize + const localeFileName = `${currentLocale}.json`; + const existingLocaleContent = + (await readFile(localeFolderPath, localeFileName)) || {}; + + // Extract prefix from project for UID correction + const prefix = project?.legacy_cms?.affix || DEFAULT_PREFIX; + + // Process each entry in this locale + for (const entry of localeEntries) { + let processedEntry = await processFieldData( + entry, + fieldConfigs, + assetId, + referenceId, + taxonomyId, + taxonomyFieldMapping, + referenceFieldMapping, + assetFieldMapping, + taxonomyReferenceLookup, + contentType, + prefix, + ); + + // 🏷️ TAXONOMY CONSOLIDATION: Merge all taxonomy fields into single 'taxonomies' field + processedEntry = consolidateTaxonomyFields( + processedEntry, + contentType, + taxonomyFieldMapping, + ); + + // Apply field type switching based on user's UI selections (from content type schema) + const enhancedEntry: any = {}; + + // Process each field with type switching support + for (const [fieldName, fieldValue] of Object.entries(processedEntry)) { + let fieldMapping = null; + + // PRIORITY 1: Read from generated content type schema (has UI-selected field types) + // This is checked FIRST because it contains the final field types after user's UI changes + // Load the content type schema to get user's field type selections + try { + const contentTypeSchemaPath = path.join( + MIGRATION_DATA_CONFIG.DATA, + destination_stack_id, + 'content_types', + `${contentType}.json`, + ); + const contentTypeSchema = JSON.parse( + await fs.promises.readFile(contentTypeSchemaPath, 'utf8'), + ); + + // Find field in schema + const schemaField = contentTypeSchema.schema?.find( + (field: any) => + field.uid === fieldName || + field.uid === fieldName.replace(/_target_id$/, '') || + field.uid === fieldName.replace(/_value$/, '') || + fieldName.includes(field.uid), + ); + + if (schemaField) { + // Determine the proper field type based on schema configuration + let targetFieldType = schemaField.data_type; + + // Handle HTML RTE fields (text with allow_rich_text: true) + if ( + schemaField.data_type === 'text' && + schemaField.field_metadata?.allow_rich_text === true + ) { + targetFieldType = 'html'; // ✅ HTML RTE field + } + // Handle JSON RTE fields + else if (schemaField.data_type === 'json') { + targetFieldType = 'json'; // ✅ JSON RTE field + } + // Handle text fields with multiline metadata + else if ( + schemaField.data_type === 'text' && + schemaField.field_metadata?.multiline + ) { + targetFieldType = 'multi_line_text'; // ✅ Multi-line text field + } + + // Create a mapping from schema field + fieldMapping = { + uid: fieldName, + contentstackFieldType: targetFieldType, + backupFieldType: schemaField.data_type, + advanced: schemaField, + }; + } + } catch (error: any) { + // Schema not found, will try fallback below + } + + // FALLBACK: If schema not found, try UI content type mapping + if ( + !fieldMapping && + currentContentTypeMapping && + currentContentTypeMapping.fieldMapping + ) { + fieldMapping = currentContentTypeMapping.fieldMapping.find( + (fm: any) => + fm.uid === fieldName || + fm.otherCmsField === fieldName || + fieldName.startsWith(fm.uid) || + fieldName.includes(fm.uid), + ); + } + + if (fieldMapping) { + // Apply field type processing based on user's selection + const processedValue = processFieldByType( + fieldValue, + fieldMapping, + assetId, + referenceId, + ); + + // Only add field if processed value is not undefined (undefined means remove field) + if (processedValue !== undefined) { + enhancedEntry[fieldName] = processedValue; + + // Log field type processing + if ( + fieldMapping.contentstackFieldType !== + fieldMapping.backupFieldType + ) { + const message = getLogMessage( + srcFunc, + `Field ${fieldName} processed as ${fieldMapping.contentstackFieldType} (switched from ${fieldMapping.backupFieldType})`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + message, + ); + } + } else { + // Log field removal + const message = getLogMessage( + srcFunc, + `Field ${fieldName} removed due to missing or invalid asset reference`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'warn', + message, + ); + } + } else { + // Keep original value if no mapping found + enhancedEntry[fieldName] = fieldValue; + } + } + + processedEntry = enhancedEntry; + + // Add publish_details as an empty array to the end of entry creation + processedEntry.publish_details = []; + + if (typeof entry.nid === 'number') { + const entryUid = uidCorrector({ + id: `content_type_entries_title_${entry.nid}`, + prefix, + }); + existingLocaleContent[entryUid] = processedEntry; + allProcessedContent[entryUid] = processedEntry; + } + + // Log each entry transformation + const message = getLogMessage( + srcFunc, + `Entry with uid ${entry.nid} (locale: ${currentLocale}) for content type ${contentType} has been successfully transformed.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + } + + // Write processed content for this specific locale + await writeFile(localeFolderPath, localeFileName, existingLocaleContent); + + const localeMessage = getLogMessage( + srcFunc, + `Successfully processed ${localeEntries.length} entries for locale ${currentLocale} in content type ${contentType}`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + localeMessage, + ); + } + + // 📁 Create mandatory index.json files for each transformed locale directory + for (const [currentLocale, localeEntries] of Object.entries( + transformedEntriesByLocale, + )) { + if (localeEntries.length > 0) { + const contentTypeFolderPath = path.join( + MIGRATION_DATA_CONFIG.DATA, + destination_stack_id, + MIGRATION_DATA_CONFIG.ENTRIES_DIR_NAME, + contentType, + ); + const localeFolderPath = path.join( + contentTypeFolderPath, + currentLocale, + ); + const localeFileName = `${currentLocale}.json`; + + // Create mandatory index.json file that maps to the locale file + const indexData = { '1': localeFileName }; + await writeFile(localeFolderPath, 'index.json', indexData); + } + } + + return allProcessedContent; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error processing entries for ${contentType}: ${error.message}`, + {}, + error, + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Gets count and processes all entries for a specific content type + */ +const processContentType = async ( + connection: mysql.Connection, + contentType: string, + queryPageConfig: QueryConfig, + fieldConfigs: DrupalFieldConfig[], + assetId: any, + referenceId: any, + taxonomyId: any, + taxonomyFieldMapping: TaxonomyFieldMapping, + referenceFieldMapping: ReferenceFieldMapping, + assetFieldMapping: AssetFieldMapping, + taxonomyReferenceLookup: Record, + projectId: string, + destination_stack_id: string, + masterLocale: string, + contentTypeMapping: any[] = [], + isTest: boolean = false, + project: any = null, +): Promise => { + const srcFunc = 'processContentType'; + + try { + // Get total count for pagination (if count query exists) + const countKey = `${contentType}Count`; + let totalCount = 1; // Default to process at least one batch + + if (queryPageConfig.count && queryPageConfig.count[countKey]) { + const countQuery = queryPageConfig.count[countKey]; + const countResults = await executeQuery(connection, countQuery); + totalCount = countResults[0]?.countentry || 0; + } + + if (totalCount === 0) { + const message = getLogMessage( + srcFunc, + `No entries found for content type ${contentType}.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + return; + } + + // 🧪 Process entries in batches (test migration: single entry, main migration: all entries) + const effectiveLimit = isTest ? 1 : LIMIT; + + for ( + let i = 0; + i < (isTest ? effectiveLimit : totalCount + LIMIT); + i += effectiveLimit + ) { + const result = await processEntries( + connection, + contentType, + i, + queryPageConfig, + fieldConfigs, + assetId, + referenceId, + taxonomyId, + taxonomyFieldMapping, + referenceFieldMapping, + assetFieldMapping, + taxonomyReferenceLookup, + projectId, + destination_stack_id, + masterLocale, + contentTypeMapping, + isTest, + project, + ); + + // If no entries returned, break the loop + if (!result) { + break; + } + } + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error processing content type ${contentType}: ${error.message}`, + {}, + error, + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Reads dynamic query configuration file generated by query.service.ts + * Following original pattern: helper.readFile(path.join(process.cwd(), config.data, 'query', 'index.json')) + * + * NOTE: No fallback to hardcoded queries - dynamic queries MUST be generated first + */ +async function readQueryConfig( + destination_stack_id: string, +): Promise { + try { + const queryPath = path.join( + DATA, + destination_stack_id, + 'query', + 'index.json', + ); + const data = await fs.promises.readFile(queryPath, 'utf8'); + return JSON.parse(data); + } catch (err) { + // No fallback - dynamic queries must be generated first by createQuery() service + throw new Error( + `❌ No dynamic query configuration found at query/index.json. Dynamic queries must be generated first using createQuery() service. Original error: ${err}`, + ); + } +} + +/** + * Creates and processes entries from Drupal database for migration to Contentstack. + * Based on the original Drupal v8 migration logic with direct SQL queries. + * + * Supports dynamic SQL queries from query/index.json file following original pattern: + * var queryPageConfig = helper.readFile(path.join(process.cwd(), config.data, 'query', 'index.json')); + * var query = queryPageConfig['page']['' + pagename + '']; + */ +export const createEntry = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + isTest = false, + masterLocale = 'en-us', + contentTypeMapping: any[] = [], + project: any = null, +): Promise => { + const srcFunc = 'createEntry'; + let connection: mysql.Connection | null = null; + + try { + const entriesSave = path.join(DATA, destination_stack_id, ENTRIES_DIR_NAME); + const assetsSave = path.join(DATA, destination_stack_id, ASSETS_DIR_NAME); + const referencesSave = path.join( + DATA, + destination_stack_id, + REFERENCES_DIR_NAME, + ); + + // Initialize directories + await fs.promises.mkdir(entriesSave, { recursive: true }); + + const message = getLogMessage(srcFunc, `Exporting entries...`, {}); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Read query configuration (following original pattern) + const queryPageConfig = await readQueryConfig(destination_stack_id); + + // Create database connection + connection = await getDbConnection( + dbConfig, + projectId, + destination_stack_id, + ); + + // Analyze field types to identify taxonomy, reference, and asset fields + const { + taxonomyFields: taxonomyFieldMapping, + referenceFields: referenceFieldMapping, + assetFields: assetFieldMapping, + } = await analyzeFieldTypes(dbConfig, destination_stack_id, projectId); + + // Fetch field configurations + const fieldConfigs = await fetchFieldConfigs( + connection, + projectId, + destination_stack_id, + ); + + // Read supporting data - following original page.js pattern + // Load assets from index.json (your new format) + const assetId = (await readFile(assetsSave, 'index.json')) || {}; + + const referenceId = + (await readFile(referencesSave, REFERENCES_FILE_NAME)) || {}; + const taxonomyId = + (await readFile( + path.join(entriesSave, 'taxonomy'), + `${masterLocale}.json`, + )) || {}; + + // Load taxonomy reference mappings for field transformation + const taxonomyReferenceLookup = await loadTaxonomyReferences( + referencesSave, + ); + + // Process each content type from query config (like original) + const pageQuery = queryPageConfig.page; + const contentTypes = Object.keys(pageQuery); + // 🧪 Test migration: Process ALL content types but with limited data per content type + const typesToProcess = contentTypes; // Always process all content types + + for (const contentType of typesToProcess) { + await processContentType( + connection, + contentType, + queryPageConfig, + fieldConfigs, + assetId, + referenceId, + taxonomyId, + taxonomyFieldMapping, + referenceFieldMapping, + assetFieldMapping, + taxonomyReferenceLookup, + projectId, + destination_stack_id, + masterLocale, + contentTypeMapping, + isTest, + project, + ); + } + + const successMessage = getLogMessage( + srcFunc, + `Successfully processed entries for ${typesToProcess.length} content types with multilingual support.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + + // Log multilingual structure summary + const structureSummary = getLogMessage( + srcFunc, + `Multilingual entries structure created at: ${DATA}/${destination_stack_id}/${ENTRIES_DIR_NAME}/[contentType]/[locale]/[locale].json`, + {}, + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + structureSummary, + ); + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error encountered while creating entries.`, + {}, + err, + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } finally { + // Close database connection + if (connection) { + connection.end(); + } + } +}; diff --git a/api/src/services/drupal/field-analysis.service.ts b/api/src/services/drupal/field-analysis.service.ts new file mode 100644 index 000000000..3d79e1875 --- /dev/null +++ b/api/src/services/drupal/field-analysis.service.ts @@ -0,0 +1,599 @@ +import mysql from 'mysql2'; +import { getDbConnection } from '../../helper/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { getLogMessage } from '../../utils/index.js'; +// Dynamic import for phpUnserialize will be used in the function + +interface FieldInfo { + field_name: string; + content_types: string; + field_type: string; + content_handler?: string; + target_type?: string; + handler_settings?: any; +} + +export interface TaxonomyFieldMapping { + [contentType: string]: { + [fieldName: string]: { + vocabulary?: string; + handler: string; + field_type: string; + }; + }; +} + +export interface ReferenceFieldMapping { + [contentType: string]: { + [fieldName: string]: { + target_type: string; + handler: string; + field_type: string; + }; + }; +} + +export interface AssetFieldMapping { + [contentType: string]: { + [fieldName: string]: { + field_type: string; + file_extensions?: string[]; + upload_location?: string; + max_filesize?: string; + }; + }; +} + +/** + * List of dangerous property names that could lead to prototype pollution + */ +const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']); + +/** + * Validates that a key is safe to use as an object property + * Prevents prototype pollution attacks + */ +const isSafeKey = (key: string): boolean => { + return ( + typeof key === 'string' && + key.length > 0 && + !DANGEROUS_KEYS.has(key) && + !key.includes('__proto__') && + !key.includes('constructor') && + !key.includes('prototype') + ); +}; + +/** + * Creates a null-prototype object to prevent prototype pollution + */ +const createSafeMapping = (): Record => { + return Object.create(null) as Record; +}; + +/** + * Safely sets a value in a mapping object with prototype pollution protection + */ +const safeSetMapping = ( + mapping: Record>, + contentType: string, + fieldName: string, + value: T +): boolean => { + if (!isSafeKey(contentType) || !isSafeKey(fieldName)) { + return false; + } + if (!Object.prototype.hasOwnProperty.call(mapping, contentType)) { + mapping[contentType] = createSafeMapping(); + } + mapping[contentType][fieldName] = value; + return true; +}; + +/** + * Execute SQL query with promise support + */ +const executeQuery = async ( + connection: mysql.Connection, + query: string +): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + return; + } + resolve(results as any[]); + }); + }); +}; + +/** + * Analyze field configuration to identify taxonomy and reference fields + * Based on the original query.js logic that checks content_handler + */ +export const analyzeFieldTypes = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string +): Promise<{ + taxonomyFields: TaxonomyFieldMapping; + referenceFields: ReferenceFieldMapping; + assetFields: AssetFieldMapping; +}> => { + const srcFunc = 'analyzeFieldTypes'; + let connection: mysql.Connection | null = null; + + try { + const message = getLogMessage( + srcFunc, + `Analyzing field types to identify taxonomy fields...`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Create database connection + connection = await getDbConnection( + dbConfig, + projectId, + destination_stack_id + ); + + // Query to get field configurations (same as original ct_mapped query) + const fieldConfigQuery = ` + SELECT *, CONVERT(data USING utf8) as data + FROM config + WHERE name LIKE '%field.field.node%' + `; + + const fieldConfigs = await executeQuery(connection, fieldConfigQuery); + + // Use null-prototype objects to prevent prototype pollution + const taxonomyFieldMapping: TaxonomyFieldMapping = createSafeMapping(); + const referenceFieldMapping: ReferenceFieldMapping = createSafeMapping(); + const assetFieldMapping: AssetFieldMapping = createSafeMapping(); + let taxonomyFieldCount = 0; + let referenceFieldCount = 0; + let assetFieldCount = 0; + let totalFieldCount = 0; + + for (const fieldConfig of fieldConfigs) { + try { + // Unserialize the PHP data to get field details + const { unserialize } = await import('php-serialize'); + const fieldData = unserialize(fieldConfig.data); + + if (fieldData && fieldData.field_name && fieldData.bundle) { + totalFieldCount++; + + const fieldInfo: FieldInfo = { + field_name: fieldData.field_name, + content_types: fieldData.bundle, + field_type: fieldData.field_type || 'unknown', + content_handler: fieldData?.settings?.handler, + target_type: fieldData?.settings?.target_type, + handler_settings: fieldData?.settings?.handler_settings, + }; + + // Validate keys to prevent prototype pollution + if ( + !isSafeKey(fieldInfo.content_types) || + !isSafeKey(fieldInfo.field_name) + ) { + const warnMessage = getLogMessage( + srcFunc, + `Skipping field with unsafe key: ${fieldInfo.content_types}.${fieldInfo.field_name}`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'warn', + warnMessage + ); + continue; + } + + // Initialize content type mappings if not exists (using safe null-prototype objects) + if ( + !Object.prototype.hasOwnProperty.call( + taxonomyFieldMapping, + fieldInfo.content_types + ) + ) { + taxonomyFieldMapping[fieldInfo.content_types] = createSafeMapping(); + } + if ( + !Object.prototype.hasOwnProperty.call( + referenceFieldMapping, + fieldInfo.content_types + ) + ) { + referenceFieldMapping[fieldInfo.content_types] = + createSafeMapping(); + } + if ( + !Object.prototype.hasOwnProperty.call( + assetFieldMapping, + fieldInfo.content_types + ) + ) { + assetFieldMapping[fieldInfo.content_types] = createSafeMapping(); + } + + // Check if this is a taxonomy reference field + const isTaxonomyField = + // Check handler for taxonomy references + (fieldInfo.content_handler && + fieldInfo.content_handler.includes('taxonomy_term')) || + // Check target_type for entity references to taxonomy terms + fieldInfo.target_type === 'taxonomy_term' || + // Check field type for direct taxonomy reference fields + (fieldInfo.field_type === 'entity_reference' && + fieldInfo.target_type === 'taxonomy_term') || + fieldInfo.field_type === 'taxonomy_term_reference' || + // Check handler settings for vocabulary restrictions (taxonomy specific) + (fieldInfo.handler_settings?.target_bundles && + Object.keys(fieldInfo.handler_settings.target_bundles).some( + (bundle) => fieldInfo.target_type === 'taxonomy_term' + )); + + // Check if this is a node reference field (non-taxonomy entity reference) + const isReferenceField = + // Check for entity_reference field type + (fieldInfo.field_type === 'entity_reference' && + // Check handler for node references + fieldInfo.content_handler && + fieldInfo.content_handler.includes('node')) || + // Check target_type for entity references to nodes + (fieldInfo.target_type === 'node' && + // Make sure it's NOT a taxonomy field + !isTaxonomyField); + + if (isTaxonomyField) { + taxonomyFieldCount++; + + // Try to determine the vocabulary from handler settings + let vocabulary = 'unknown'; + if (fieldInfo.handler_settings?.target_bundles) { + const vocabularies = Object.keys( + fieldInfo.handler_settings.target_bundles + ); + vocabulary = + vocabularies.length === 1 + ? vocabularies[0] + : vocabularies.join(','); + } + + // Use safe setter to prevent prototype pollution + safeSetMapping( + taxonomyFieldMapping, + fieldInfo.content_types, + fieldInfo.field_name, + { + vocabulary, + handler: fieldInfo.content_handler || 'default:taxonomy_term', + field_type: fieldInfo.field_type, + } + ); + + const taxonomyMessage = getLogMessage( + srcFunc, + `Found taxonomy field: ${fieldInfo.content_types}.${fieldInfo.field_name} → vocabulary: ${vocabulary}`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + taxonomyMessage + ); + } else if (isReferenceField) { + referenceFieldCount++; + + // Use safe setter to prevent prototype pollution + safeSetMapping( + referenceFieldMapping, + fieldInfo.content_types, + fieldInfo.field_name, + { + target_type: fieldInfo.target_type || 'node', + handler: fieldInfo.content_handler || 'default:node', + field_type: fieldInfo.field_type, + } + ); + + const referenceMessage = getLogMessage( + srcFunc, + `Found reference field: ${fieldInfo.content_types}.${ + fieldInfo.field_name + } → target_type: ${fieldInfo.target_type || 'node'}`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + referenceMessage + ); + } + + // Check if this is an asset/file field + const isAssetField = + // Check for file field type + fieldInfo.field_type === 'file' || + // Check for image field type + fieldInfo.field_type === 'image' || + // Check for managed_file field type + fieldInfo.field_type === 'managed_file' || + // Check for entity_reference to file entities + (fieldInfo.field_type === 'entity_reference' && + fieldInfo.target_type === 'file'); + + if (isAssetField) { + assetFieldCount++; + + // Extract file-related settings + const fileExtensions = fieldData?.settings?.file_extensions + ? fieldData.settings.file_extensions.split(' ') + : []; + const uploadLocation = + fieldData?.settings?.file_directory || + fieldData?.settings?.uri_scheme || + 'public://'; + const maxFilesize = + fieldData?.settings?.max_filesize || + fieldData?.settings?.file_size || + ''; + + // Use safe setter to prevent prototype pollution + safeSetMapping( + assetFieldMapping, + fieldInfo.content_types, + fieldInfo.field_name, + { + field_type: fieldInfo.field_type, + file_extensions: fileExtensions, + upload_location: uploadLocation, + max_filesize: maxFilesize, + } + ); + + const assetMessage = getLogMessage( + srcFunc, + `Found asset field: ${fieldInfo.content_types}.${ + fieldInfo.field_name + } → type: ${ + fieldInfo.field_type + }, extensions: [${fileExtensions.join(', ')}]`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + assetMessage + ); + } + } + } catch (parseError: any) { + // Log parsing error but continue with other fields + const parseMessage = getLogMessage( + srcFunc, + `Could not parse field config: ${parseError.message}`, + {}, + parseError + ); + await customLogger( + projectId, + destination_stack_id, + 'warn', + parseMessage + ); + } + } + + const summaryMessage = getLogMessage( + srcFunc, + `Field analysis complete: ${taxonomyFieldCount} taxonomy fields, ${referenceFieldCount} reference fields, and ${assetFieldCount} asset fields found out of ${totalFieldCount} total fields.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', summaryMessage); + + return { + taxonomyFields: taxonomyFieldMapping, + referenceFields: referenceFieldMapping, + assetFields: assetFieldMapping, + }; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error analyzing field types: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } finally { + if (connection) { + connection.end(); + } + } +}; + +/** + * Check if a specific field is a taxonomy field + */ +export const isTaxonomyField = ( + fieldName: string, + contentType: string, + taxonomyMapping: TaxonomyFieldMapping +): boolean => { + return !!( + taxonomyMapping[contentType] && taxonomyMapping[contentType][fieldName] + ); +}; + +/** + * Check if a specific field is a reference field + */ +export const isReferenceField = ( + fieldName: string, + contentType: string, + referenceMapping: ReferenceFieldMapping +): boolean => { + return !!( + referenceMapping[contentType] && referenceMapping[contentType][fieldName] + ); +}; + +/** + * Check if a specific field is an asset field + */ +export const isAssetField = ( + fieldName: string, + contentType: string, + assetMapping: AssetFieldMapping +): boolean => { + return !!(assetMapping[contentType] && assetMapping[contentType][fieldName]); +}; + +/** + * Get taxonomy field information + */ +export const getTaxonomyFieldInfo = ( + fieldName: string, + contentType: string, + taxonomyMapping: TaxonomyFieldMapping +) => { + return taxonomyMapping[contentType]?.[fieldName] || null; +}; + +/** + * Get reference field information + */ +export const getReferenceFieldInfo = ( + fieldName: string, + contentType: string, + referenceMapping: ReferenceFieldMapping +) => { + return referenceMapping[contentType]?.[fieldName] || null; +}; + +/** + * Get asset field information + */ +export const getAssetFieldInfo = ( + fieldName: string, + contentType: string, + assetMapping: AssetFieldMapping +) => { + return assetMapping[contentType]?.[fieldName] || null; +}; + +/** + * Transform taxonomy field value to Contentstack format + * Converts tid to taxonomy term uid based on our taxonomy data + * + * The taxonomyData should contain individual vocabulary files: + * - taxonomies/list.json + * - taxonomies/news.json + * etc. + */ +export const transformTaxonomyValue = async ( + value: any, + fieldName: string, + contentType: string, + taxonomyMapping: TaxonomyFieldMapping, + taxonomyBasePath: string +): Promise => { + const fieldInfo = getTaxonomyFieldInfo( + fieldName, + contentType, + taxonomyMapping + ); + + if (!fieldInfo || !value) { + return value; + } + + // If it's a taxonomy field with tid value, try to find the corresponding term + if ( + typeof value === 'number' || + (typeof value === 'string' && /^\d+$/.test(value)) + ) { + const tid = parseInt(value.toString()); + + try { + // Try to determine which vocabulary to look in based on field info + const vocabularies = fieldInfo.vocabulary + ? fieldInfo.vocabulary.split(',') + : ['unknown']; + + for (const vocabulary of vocabularies) { + try { + const fs = await import('fs'); + const path = await import('path'); + + const taxonomyFilePath = path.join( + taxonomyBasePath, + `${vocabulary}.json` + ); + + if (fs.existsSync(taxonomyFilePath)) { + const taxonomyContent = JSON.parse( + fs.readFileSync(taxonomyFilePath, 'utf8') + ); + + if (taxonomyContent.terms && Array.isArray(taxonomyContent.terms)) { + for (const term of taxonomyContent.terms) { + if (term.drupal_term_id === tid) { + return term.uid; + } + } + } + } + } catch (vocabError) { + // Continue to next vocabulary if this one fails + continue; + } + } + + // If we couldn't find it in specific vocabularies, try all taxonomy files + const fs = await import('fs'); + const path = await import('path'); + + if (fs.existsSync(taxonomyBasePath)) { + const taxonomyFiles = fs + .readdirSync(taxonomyBasePath) + .filter( + (file) => file.endsWith('.json') && file !== 'taxonomies.json' + ); + + for (const file of taxonomyFiles) { + try { + const taxonomyContent = JSON.parse( + fs.readFileSync(path.join(taxonomyBasePath, file), 'utf8') + ); + + if (taxonomyContent.terms && Array.isArray(taxonomyContent.terms)) { + for (const term of taxonomyContent.terms) { + if (term.drupal_term_id === tid) { + return term.uid; + } + } + } + } catch (fileError) { + // Continue to next file if this one fails + continue; + } + } + } + } catch (error) { + // Return original value if transformation fails + return value; + } + } + + return value; +}; diff --git a/api/src/services/drupal/field-fetcher.service.ts b/api/src/services/drupal/field-fetcher.service.ts new file mode 100644 index 000000000..bb2c6ab6c --- /dev/null +++ b/api/src/services/drupal/field-fetcher.service.ts @@ -0,0 +1,228 @@ +import mysql from 'mysql2'; +import { getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; + +/** + * Field Fetcher Service for Content Types with Many Fields + * Handles field data fetching for content types that exceed MySQL JOIN limits + */ + +interface DrupalFieldData { + field_name: string; + content_types: string; + type: string; + content_handler?: string; +} + +interface FieldDataResult { + [nid: number]: { + [fieldName: string]: any; + }; +} + +export class FieldFetcherService { + private connection: mysql.Connection; + private projectId: string; + private destinationStackId: string; + + constructor(connection: mysql.Connection, projectId: string, destinationStackId: string) { + this.connection = connection; + this.projectId = projectId; + this.destinationStackId = destinationStackId; + } + + /** + * Fetch field data for content types with many fields using individual queries + * This avoids the MySQL 61-table JOIN limit + */ + async fetchFieldDataForContentType( + contentType: string, + nodeIds: number[], + fieldsForType: DrupalFieldData[] + ): Promise { + const srcFunc = 'fetchFieldDataForContentType'; + const fieldData: FieldDataResult = {}; + + if (nodeIds.length === 0) { + return fieldData; + } + + // Initialize field data structure + nodeIds.forEach(nid => { + fieldData[nid] = {}; + }); + + const message = getLogMessage( + srcFunc, + `Fetching field data for ${contentType}: ${fieldsForType.length} fields, ${nodeIds.length} nodes`, + {} + ); + await customLogger(this.projectId, this.destinationStackId, 'info', message); + + // Process fields in batches to avoid overwhelming the database + const batchSize = 10; + for (let i = 0; i < fieldsForType.length; i += batchSize) { + const fieldBatch = fieldsForType.slice(i, i + batchSize); + + await Promise.all( + fieldBatch.map(field => this.fetchSingleFieldData(field, nodeIds, fieldData)) + ); + } + + const successMessage = getLogMessage( + srcFunc, + `Successfully fetched field data for ${contentType}: ${Object.keys(fieldData).length} nodes processed`, + {} + ); + await customLogger(this.projectId, this.destinationStackId, 'info', successMessage); + + return fieldData; + } + + /** + * Fetch data for a single field across multiple nodes + */ + private async fetchSingleFieldData( + field: DrupalFieldData, + nodeIds: number[], + fieldData: FieldDataResult + ): Promise { + const fieldTableName = `node__${field.field_name}`; + + try { + // Check if field table exists + const tableExistsQuery = ` + SELECT 1 FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + LIMIT 1 + `; + + const [tableExists] = await this.connection.promise().query(tableExistsQuery, [fieldTableName]) as any[]; + + if (tableExists.length === 0) { + console.warn(`Field table ${fieldTableName} does not exist`); + return; + } + + // Get field columns dynamically + const columnQuery = ` + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + AND COLUMN_NAME LIKE ? + `; + + const [columns] = await this.connection.promise().query(columnQuery, [fieldTableName, `${field.field_name}_%`]) as any[]; + + if (columns.length === 0) { + console.warn(`No columns found for field ${field.field_name}`); + return; + } + + // Build field query with all relevant columns + const fieldColumns = columns.map((col: any) => col.COLUMN_NAME); + const selectColumns = fieldColumns.join(', '); + + const fieldQuery = ` + SELECT + entity_id, + ${selectColumns} + FROM ${fieldTableName} + WHERE entity_id IN (${nodeIds.map(() => '?').join(',')}) + `; + + const [fieldResults] = await this.connection.promise().query(fieldQuery, nodeIds) as any[]; + + // Merge field results into main data structure + fieldResults.forEach((row: any) => { + const nid = row.entity_id; + if (fieldData[nid]) { + // Add all field columns to the node data + fieldColumns.forEach((columnName: string) => { + if (row[columnName] !== null && row[columnName] !== undefined) { + fieldData[nid][columnName] = row[columnName]; + } + }); + } + }); + + } catch (error: any) { + console.warn(`Error fetching data for field ${field.field_name}:`, error.message); + + const errorMessage = getLogMessage( + 'fetchSingleFieldData', + `Failed to fetch data for field ${field.field_name}: ${error.message}`, + {}, + error + ); + await customLogger(this.projectId, this.destinationStackId, 'warn', errorMessage); + } + } + + /** + * Merge base node data with field data + */ + mergeNodeAndFieldData( + baseNodes: any[], + fieldData: FieldDataResult + ): any[] { + return baseNodes.map(node => { + const nid = node.nid; + const nodeFieldData = fieldData[nid] || {}; + + return { + ...node, + ...nodeFieldData + }; + }); + } + + /** + * Get field configuration for a content type + */ + async getFieldsForContentType(contentType: string): Promise { + const configQuery = ` + SELECT *, CONVERT(data USING utf8) as data + FROM config + WHERE name LIKE '%field.field.node%' + `; + + try { + const [rows] = await this.connection.promise().query(configQuery) as any[]; + const fields: DrupalFieldData[] = []; + + for (const row of rows) { + try { + const { unserialize } = await import('php-serialize'); + const configData = unserialize(row.data); + + if (configData && configData.bundle === contentType) { + fields.push({ + field_name: configData.field_name, + content_types: configData.bundle, + type: configData.field_type, + content_handler: configData?.settings?.handler + }); + } + } catch (parseError) { + console.warn(`Failed to parse field config for ${row.name}:`, parseError); + } + } + + return fields; + } catch (error: any) { + const errorMessage = getLogMessage( + 'getFieldsForContentType', + `Failed to get fields for content type ${contentType}: ${error.message}`, + {}, + error + ); + await customLogger(this.projectId, this.destinationStackId, 'error', errorMessage); + throw error; + } + } +} + +export default FieldFetcherService; diff --git a/api/src/services/drupal/locales.service.ts b/api/src/services/drupal/locales.service.ts new file mode 100644 index 000000000..c10029711 --- /dev/null +++ b/api/src/services/drupal/locales.service.ts @@ -0,0 +1,367 @@ +import fs from 'fs'; +import path from 'path'; +import axios from 'axios'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; +import { Locale } from '../../models/types.js'; +import { getAllLocales, getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { createDbConnection } from '../../helper/index.js'; +import { v4 as uuidv4 } from 'uuid'; + +const { + DATA: MIGRATION_DATA_PATH, + LOCALE_DIR_NAME, + LOCALE_FILE_NAME, + LOCALE_MASTER_LOCALE, + LOCALE_CF_LANGUAGE, +} = MIGRATION_DATA_CONFIG; + +/** + * Writes data to a specified file, ensuring the target directory exists. + */ +async function writeFile(dirPath: string, filename: string, data: any) { + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + const filePath = path.join(dirPath, filename); + await fs.promises.writeFile(filePath, JSON.stringify(data), 'utf8'); + } catch (err) { + console.error(`Error writing ${dirPath}/${filename}::`, err); + } +} + +/** + * Maps source locale to destination locale based on user-selected mapping from UI. + * Similar to WordPress/Contentful/Sitecore mapLocales function. + * + * @param masterLocale - The master locale code from the stack + * @param locale - The source locale code from Drupal + * @param locales - The locale mapping object from project (contains master_locale and locales) + * @param localeMapping - The direct locale mapping from UI (optional, takes precedence) + * @returns The mapped destination locale code + */ +export function mapDrupalLocales({ + masterLocale, + locale, + locales, + localeMapping, + sourceMasterLocale, + destinationMasterLocale, +}: { + masterLocale: string; + locale: string; + locales?: any; + localeMapping?: Record; + sourceMasterLocale?: string; + destinationMasterLocale?: string; +}): string { + // Priority 1: Check direct locale mapping from UI (format: { "en-master_locale": "fr-fr", "es": "es-es" }) + if (localeMapping) { + // Check if this is a master locale mapping + const masterKey = `${locale}-master_locale`; + if (localeMapping[masterKey]) { + return localeMapping[masterKey]; + } + + // Check direct mapping + if (localeMapping[locale]) { + return localeMapping[locale]; + } + } + + // Priority 2: Check if source locale matches master locale in mapping + if (locales?.masterLocale?.[masterLocale] === locale) { + return Object.keys(locales.masterLocale)?.[0] || masterLocale; + } + + // Priority 3: Check regular locales mapping + if (locales) { + for (const [key, value] of Object.entries(locales)) { + if (typeof value !== 'object' && value === locale) { + return key; + } + } + } + + // Priority 4: If this is the source master locale, map to destination master locale + // This handles the case where user selected a custom master locale (e.g., "div-mv") in UI + // but locale mapping wasn't saved in project.master_locale/locales + if ( + sourceMasterLocale && + destinationMasterLocale && + locale === sourceMasterLocale + ) { + return destinationMasterLocale?.toLowerCase?.(); + } + + // Priority 5: Check if locale matches the destination master locale (already in correct format) + if ( + destinationMasterLocale && + locale?.toLowerCase?.() === destinationMasterLocale?.toLowerCase?.() + ) { + return destinationMasterLocale?.toLowerCase?.(); + } + + // Priority 6: Return locale as-is (lowercase) + return locale?.toLowerCase?.() || locale; +} + +/** + * Applies special locale code transformations based on business rules + * - "und" alone → "en-us" + * - "und" + "en-us" → "und" become "en", "en-us" stays + * - "en" + "und" → "und" becomes "en-us", "en" stays + * - All three "en" + "und" + "en-us" → all three stays + * - Apart from these, all other locales stay as is + */ +function applyLocaleTransformations( + locales: string[], + masterLocale: string +): { code: string; name: string; isMaster: boolean }[] { + const hasUnd = locales.includes('und'); + const hasEn = locales.includes('en'); + const hasEnUs = locales.includes('en-us'); + + // First, apply the transformation rules to get the correct locale codes + const transformedCodes: string[] = []; + + // Start with all non-special locales (not und, en, en-us) + const nonSpecialLocales = locales.filter( + (locale) => !['und', 'en', 'en-us'].includes(locale) + ); + transformedCodes.push(...nonSpecialLocales); + + // Apply transformation rules based on combinations + if (hasEn && hasUnd && hasEnUs) { + // Rule 4: All three "en" + "und" + "en-us" → all three stays + transformedCodes.push('en', 'und', 'en-us'); + } else if (hasUnd && hasEnUs && !hasEn) { + // Rule 2: "und" + "en-us" → "und" become "en", "en-us" stays + transformedCodes.push('en', 'en-us'); + } else if (hasEn && hasUnd && !hasEnUs) { + // Rule 3: "en" + "und" → "und" becomes "en-us", "en" stays + transformedCodes.push('en', 'en-us'); + } else if (hasUnd && !hasEn && !hasEnUs) { + // Rule 1: "und" alone → "en-us" + transformedCodes.push('en-us'); + } else { + // For any other combinations, keep locales as they are + if (hasEn) transformedCodes.push('en'); + if (hasUnd) transformedCodes.push('und'); + if (hasEnUs) transformedCodes.push('en-us'); + } + + // Remove duplicates and sort + const uniqueTransformedCodes = Array.from(new Set(transformedCodes)).sort(); + + // Now map each transformed code to the proper format with names + return uniqueTransformedCodes.map((code) => { + let name = ''; + let isMaster = false; + + // Determine if this is the master locale (check against original and transformed) + isMaster = + code === masterLocale || + (masterLocale === 'und' && code === 'en-us') || // Rule 1 transformation + (masterLocale === 'und' && hasEnUs && code === 'en') || // Rule 2 transformation + (masterLocale === 'und' && hasEn && code === 'en-us'); // Rule 3 transformation + + // Set appropriate names + switch (code) { + case 'en': + name = 'English'; + break; + case 'en-us': + name = 'English - United States'; + break; + case 'und': + name = 'Language Neutral'; + break; + default: + name = ''; // Will be filled from Contentstack API later + break; + } + + return { code: code.toLowerCase(), name, isMaster }; + }); +} + +/** + * Processes and creates locale configurations from Drupal database for migration to Contentstack. + * + * This function: + * 1. Fetches master locale from Drupal system.site config + * 2. Fetches all locales from node_field_data + * 3. Uses user-selected locale mapping from UI (project.localeMapping) + * 4. Maps source locales to destination locales based on user selection + * 5. Sets master locale based on user selection + * 6. Creates 3 JSON files: master-locale.json, locales.json, language.json + */ +export const createLocale = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + project: any +) => { + const srcFunc = 'createLocale'; + const localeSave = path.join( + MIGRATION_DATA_PATH, + destination_stack_id, + LOCALE_DIR_NAME + ); + + try { + const msLocale: Record = {}; + const allLocales: Record = {}; + const localeList: Record = {}; + + if (!dbConfig || !dbConfig.host || !dbConfig.user || !dbConfig.database) { + throw new Error( + 'Invalid database configuration provided to createLocale' + ); + } + + const connection = await createDbConnection(dbConfig); + + if (!connection) { + throw new Error('Failed to create database connection'); + } + + // Helper function to execute queries (same pattern as entries.service.ts) + const executeQuery = (query: string): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + } else { + resolve(results as any[]); + } + }); + }); + }; + + // 1. Get master locale from Drupal system.site config + const masterLocaleQuery = ` + SELECT SUBSTRING_INDEX( + SUBSTRING_INDEX(CONVERT(data USING utf8), 'default_langcode";s:2:"', -1), + '"', 1 + ) as master_locale + FROM config + WHERE name = 'system.site' + `; + + const masterRows: any = await executeQuery(masterLocaleQuery); + const sourceMasterLocale = masterRows[0]?.master_locale || 'en'; + + // 2. Get all locales from node_field_data + const allLocalesQuery = ` + SELECT DISTINCT langcode + FROM node_field_data + WHERE langcode IS NOT NULL AND langcode != '' + ORDER BY langcode + `; + + const allLocaleRows: any = await executeQuery(allLocalesQuery); + const sourceLocaleCodes = allLocaleRows.map((row: any) => row.langcode); + + // Close database connection + connection.end(); + + // 3. Get user-selected locale mapping from UI (project.localeMapping or project.locales/master_locale) + // localeMapping format: { "en-master_locale": "fr-fr", "es": "es-es", ... } + const localeMapping = project?.localeMapping || {}; + // ✅ FIX: Use LOCALE_MAPPER as fallback like WordPress does (but stackDetails.master_locale takes precedence) + const masterLocaleFromProject = project?.master_locale || {}; + const localesFromProject = project?.locales || {}; + + // 4. Fetch locale names from Contentstack API + const [localesApiResponse] = await getAllLocales(); + const contentstackLocales = localesApiResponse || {}; // ✅ FIX: getAllLocales already returns the locales object + + // 5. Map source locales to destination locales using user selection + // Find the destination master locale based on source master locale + const masterLocaleKey = `${sourceMasterLocale}-master_locale`; + let destinationMasterLocale = + localeMapping[masterLocaleKey] || + Object.values(masterLocaleFromProject)?.[0] || // ✅ FIX: Use VALUES not KEYS! + project?.stackDetails?.master_locale || + 'en-us'; + + // Process transformed locales first (handle und, en, en-us) + const transformedLocales = applyLocaleTransformations( + sourceLocaleCodes, + sourceMasterLocale + ); + + // Map each transformed source locale to destination locale + transformedLocales.forEach((localeInfo) => { + const { code: sourceCode, isMaster } = localeInfo; + + // Find destination locale from mapping + let destinationCode: string; + + if (isMaster) { + // For master locale, use the mapped master locale + destinationCode = destinationMasterLocale; + } else { + // For non-master locales, check mapping or use as-is + destinationCode = + localeMapping[sourceCode] || + localesFromProject[sourceCode] || + sourceCode; + } + + // Create UID + const uid = uuidv4(); + + // Get name from Contentstack API + const localeName = + contentstackLocales[destinationCode] || + contentstackLocales[destinationCode.toLowerCase()] || + contentstackLocales[sourceCode] || + 'Unknown Language'; + + const newLocale: Locale = { + code: destinationCode.toLowerCase(), + name: localeName, + fallback_locale: isMaster + ? null + : destinationMasterLocale.toLowerCase(), + uid: uid, + }; + + // Add to appropriate collections using UID as key + if (isMaster) { + msLocale[uid] = newLocale; + } else { + allLocales[uid] = newLocale; + } + + localeList[uid] = newLocale; + }); + + // Handle case where no non-master locales exist + const finalAllLocales = + Object.keys(allLocales).length > 0 ? allLocales : {}; + + // 6. Write locale files (same structure as Contentful/WordPress) + await writeFile(localeSave, LOCALE_FILE_NAME, finalAllLocales); // locales.json (non-master only) + await writeFile(localeSave, LOCALE_MASTER_LOCALE, msLocale); // master-locale.json (master only) + await writeFile(localeSave, LOCALE_CF_LANGUAGE, localeList); // language.json (all locales) + + const message = getLogMessage( + srcFunc, + `Drupal locales have been successfully transformed. Source Master: ${sourceMasterLocale}, Destination Master: ${destinationMasterLocale}, Total: ${sourceLocaleCodes.length}`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error while creating Drupal locales.`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } +}; diff --git a/api/src/services/drupal/query.service.ts b/api/src/services/drupal/query.service.ts new file mode 100644 index 000000000..734ad1692 --- /dev/null +++ b/api/src/services/drupal/query.service.ts @@ -0,0 +1,646 @@ +import fs from 'fs'; +import path from 'path'; +import mysql from 'mysql2'; +import { getDbConnection } from '../../helper/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; + +const { DATA } = MIGRATION_DATA_CONFIG; + +// PHP unserialize functionality (simplified for Node.js) +// Dynamic import for phpUnserialize will be used in the function + +/** + * Interface for field data extracted from Drupal config + */ +interface DrupalFieldData { + field_name: string; + content_types: string; + type: string; + content_handler?: string; + target_bundles?: string[]; // Content types this reference field targets +} + +/** + * Helper function to check if a reference field should be skipped + * Returns true if the field only references 'profile' or has no valid targets + */ +function shouldSkipReferenceField( + targetBundles: string[] | undefined, +): boolean { + if (!targetBundles || targetBundles.length === 0) { + return false; // No specific targets, will use fallback - don't skip + } + // Filter out profile + const validTargets = targetBundles.filter( + (bundle) => bundle && bundle.toLowerCase() !== 'profile', + ); + // Skip if empty after filtering (meaning it only had profile) + return validTargets.length === 0; +} + +/** + * Interface for query configuration + */ +interface QueryConfig { + page: { [contentType: string]: string }; + count: { [contentType: string]: string }; +} + +/** + * Gets taxonomy reference fields for a content type by scanning actual entry data + * Similar to upload-api's getActualTaxonomyUsage but returns field info for query generation + * + * @param connection - MySQL connection + * @param contentType - Content type bundle name + * @returns Array of field table/column info that reference taxonomies + */ +async function getTaxonomyFieldsForContentType( + connection: mysql.Connection, + contentType: string, +): Promise< + Array<{ tableName: string; columnName: string; fieldName: string }> +> { + console.info( + `\n🏷️🏷️🏷️ [query.service] getTaxonomyFieldsForContentType called for: ${contentType}`, + ); + + const taxonomyFields: Array<{ + tableName: string; + columnName: string; + fieldName: string; + }> = []; + + try { + // Find ALL field tables for this content type that have taxonomy references + const [fieldTables] = (await connection.promise().query(` + SELECT DISTINCT t.TABLE_NAME, c.COLUMN_NAME + FROM information_schema.TABLES t + INNER JOIN information_schema.COLUMNS c + ON c.TABLE_NAME = t.TABLE_NAME AND c.TABLE_SCHEMA = t.TABLE_SCHEMA + WHERE t.TABLE_SCHEMA = DATABASE() + AND t.TABLE_NAME LIKE 'node__field_%' + AND t.TABLE_NAME NOT LIKE '%revision%' + AND c.COLUMN_NAME LIKE '%_target_id' + `)) as any[]; + + console.info( + `🏷️ [query.service] Found ${fieldTables.length} potential field tables for ${contentType}`, + ); + + // For each field table, check if target_ids reference taxonomy terms for this content type + for (const fieldTable of fieldTables) { + const tableName = fieldTable.TABLE_NAME; + const targetIdColumn = fieldTable.COLUMN_NAME; + const fieldName = tableName.replace('node__', ''); + + // Skip known image/file fields by name pattern + if ( + fieldName.includes('image') || + fieldName.includes('file') || + fieldName.includes('photo') || + fieldName.includes('media') || + fieldName.includes('hero_image') + ) { + console.info( + `🏷️ [query.service] Skipping '${fieldName}' - appears to be an image/file field`, + ); + continue; + } + + try { + // NOTE: Removed file_managed check as it caused false positives due to ID collisions + // (taxonomy term IDs can coincidentally match file IDs) + // We now rely on: 1) field name patterns, 2) taxonomy_term_field_data check + + // Check if this field has taxonomy term references for this content type + const [vocabs] = (await connection.promise().query( + ` + SELECT DISTINCT ttfd.vid as vocabulary_uid + FROM \`${tableName}\` ft + INNER JOIN taxonomy_term_field_data ttfd ON ft.${targetIdColumn} = ttfd.tid + WHERE ft.bundle = ? + AND ft.${targetIdColumn} IS NOT NULL + LIMIT 1 + `, + [contentType], + )) as any[]; + + if (vocabs && vocabs.length > 0) { + taxonomyFields.push({ + tableName, + columnName: targetIdColumn, + fieldName, + }); + console.info( + `🏷️ [query.service] Found taxonomy field '${fieldName}' for content type '${contentType}' -> vocabulary: ${vocabs[0].vocabulary_uid}`, + ); + } + } catch (tableError) { + // Skip tables that don't exist or have incompatible structure + } + } + + // Step 3: Also check taxonomy_index as a backup (for indexed taxonomy relationships) + // This catches taxonomy relationships that might not be in explicit field tables + try { + const [indexVocabs] = (await connection.promise().query( + ` + SELECT DISTINCT ttfd.vid as vocabulary_uid + FROM taxonomy_index ti + INNER JOIN node_field_data nfd ON ti.nid = nfd.nid + INNER JOIN taxonomy_term_field_data ttfd ON ti.tid = ttfd.tid + WHERE nfd.type = ? + `, + [contentType], + )) as any[]; + + if (indexVocabs && indexVocabs.length > 0) { + // Check if we already have a taxonomy field - if not, add from taxonomy_index + const existingVocabs = new Set(); + // We need to check existing taxonomy fields for this content type + // If taxonomy_index has vocabs that aren't covered by explicit fields, add it + const hasExplicitTaxonomyField = taxonomyFields.length > 0; + + if (!hasExplicitTaxonomyField && indexVocabs.length > 0) { + // Add taxonomy_index as a pseudo-field + taxonomyFields.push({ + tableName: 'taxonomy_index', + columnName: 'tid', + fieldName: 'taxonomy_index', + }); + console.info( + `🏷️ [query.service] Found taxonomy via taxonomy_index for '${contentType}' -> vocabularies: ${indexVocabs + .map((v: any) => v.vocabulary_uid) + .join(', ')}`, + ); + } + } + } catch (indexError) { + // taxonomy_index might not exist in some Drupal installations + console.info( + `🏷️ [query.service] taxonomy_index not available for ${contentType}`, + ); + } + + console.info( + `🏷️ [query.service] Returning ${taxonomyFields.length} taxonomy fields for ${contentType}:`, + taxonomyFields.map((f) => f.fieldName), + ); + return taxonomyFields; + } catch (error: any) { + console.error( + `⚠️ Could not query taxonomy fields for ${contentType}:`, + error.message, + ); + return taxonomyFields; + } +} + +/** + * Get field information by querying the database for a specific field + * Enhanced to handle link fields with both URI and TITLE columns + */ +const getQuery = ( + connection: mysql.Connection, + data: DrupalFieldData, +): Promise => { + return new Promise((resolve, reject) => { + try { + const tableName = `node__${data.field_name}`; + + // Check if this is a link field first + if (data.type !== 'link') { + // For non-link fields, use existing logic + const value = data.field_name; + const handlerType = + data.content_handler === undefined ? 'invalid' : data.content_handler; + const query = `SELECT *, '${handlerType}' as handler, '${data.type}' as fieldType FROM ${tableName}`; + + connection.query(query, (error: any, rows: any, fields: any) => { + if (!error && fields) { + // Look for field patterns in the database columns + for (const field of fields) { + const fieldName = field.name; + + // Check for various Drupal field suffixes + if ( + fieldName === `${value}_value` || + fieldName === `${value}_fid` || + fieldName === `${value}_tid` || + fieldName === `${value}_status` || + fieldName === `${value}_target_id` || + fieldName === `${value}_uri` + ) { + const fieldTable = `node__${data.field_name}.${fieldName}`; + resolve(fieldTable); + return; + } + } + // If no matching field was found + resolve(''); + } else { + console.error(`Error executing query for field ${value}:`, error); + resolve(''); // Resolve with empty string on error to continue process + } + }); + return; + } + + // For LINK fields only - get both URI and TITLE columns + connection.query( + `SHOW COLUMNS FROM ${tableName}`, + (error: any, columns: any) => { + if (error) { + console.error( + `Error querying columns for link field ${data.field_name}:`, + error, + ); + resolve(''); + return; + } + + // Filter for link-specific columns only + const linkColumns = columns + .map((col: any) => col.Field) + .filter( + (field: string) => + (field === `${data.field_name}_uri` || + field === `${data.field_name}_title`) && + field.startsWith(data.field_name), + ); + + if (linkColumns.length > 0) { + // Return both columns as MAX aggregations for link fields + const maxColumns = linkColumns.map( + (col: string) => `MAX(${tableName}.${col}) as ${col}`, + ); + resolve(maxColumns.join(',')); + } else { + // Fallback to just URI if title doesn't exist + const uriColumn = `${data.field_name}_uri`; + resolve(`MAX(${tableName}.${uriColumn}) as ${uriColumn}`); + } + }, + ); + } catch (error) { + console.error('Error in getQuery', error); + resolve(''); // Resolve with empty string on error to continue process + } + }); +}; + +/** + * Process field data and generate SQL queries for each content type + */ +const generateQueriesForFields = async ( + connection: mysql.Connection, + fieldData: DrupalFieldData[], + projectId: string, + destination_stack_id: string, +): Promise => { + try { + const select: { [contentType: string]: string } = {}; + const countQuery: { [contentType: string]: string } = {}; + + // Group fields by content type and filter out profile + const contentTypes = [ + ...new Set(fieldData.map((field) => field.content_types)), + ].filter((contentType) => contentType !== 'profile'); + + const message = `Processing ${contentTypes.length} content types for query generation...`; + await customLogger(projectId, destination_stack_id, 'info', message); + + // Process each content type + for (const contentType of contentTypes) { + const fieldsForType = fieldData.filter( + (field) => field.content_types === contentType, + ); + const fieldCount = fieldsForType.length; + const maxJoinLimit = 50; // Conservative limit to avoid MySQL's 61-table limit + + // Check if content type has too many fields for single query + if (fieldCount > maxJoinLimit) { + const warningMessage = `Content type '${contentType}' has ${fieldCount} fields (>${maxJoinLimit} limit). Using optimized base query only.`; + await customLogger( + projectId, + destination_stack_id, + 'warn', + warningMessage, + ); + + // Generate simple base query without field JOINs to avoid MySQL limit + const baseQuery = ` + SELECT + node.nid, + node.title, + node.langcode, + node.type, + users.name as author_name + FROM node_field_data node + LEFT JOIN users ON users.uid = node.uid + WHERE node.type = '${contentType}' + GROUP BY node.nid + ` + .replace(/\s+/g, ' ') + .trim(); + + const baseCountQuery = ` + SELECT COUNT(DISTINCT node.nid) as countentry + FROM node_field_data node + WHERE node.type = '${contentType}' + ` + .replace(/\s+/g, ' ') + .trim(); + + select[contentType] = + baseQuery + ` /* OPTIMIZED_NO_JOINS:${fieldCount} */`; + countQuery[`${contentType}Count`] = baseCountQuery; + + const optimizedMessage = `Generated optimized base query for ${contentType} (avoiding ${fieldCount} JOINs)`; + await customLogger( + projectId, + destination_stack_id, + 'info', + optimizedMessage, + ); + + continue; // Skip to next content type + } + + const tableJoins: string[] = []; + const queries: Promise[] = []; + + // Collect all field queries (only for content types with manageable field count) + fieldsForType.forEach((fieldData) => { + tableJoins.push(`node__${fieldData.field_name}`); + queries.push(getQuery(connection, fieldData)); + }); + + // 🏷️ TAXONOMY FIX: Check for taxonomy fields that might not be in field config + // but have actual usage in entry data (similar to upload-api's approach) + const taxonomyFields = await getTaxonomyFieldsForContentType( + connection, + contentType, + ); + + // Add taxonomy fields that aren't already in the query + for (const taxField of taxonomyFields) { + if (!tableJoins.includes(taxField.tableName)) { + tableJoins.push(taxField.tableName); + // Add the column directly to queries as a resolved promise + const taxColumnQuery = `${taxField.tableName}.${taxField.columnName}`; + queries.push(Promise.resolve(taxColumnQuery)); + console.info( + `🏷️ [query.service] Added taxonomy field '${taxField.fieldName}' to query for '${contentType}'`, + ); + } + } + + try { + // Wait for all field queries to complete + const results = await Promise.all(queries); + + // Filter out empty results + const validResults = results.filter((item) => item); + + if (validResults.length === 0) { + continue; + } + + // Build the SELECT clause with proper handling for link fields + const modifiedResults = validResults.map((item) => { + // Check if this is already a MAX aggregation (link fields) + if (item.includes('MAX(') && item.includes(' as ')) { + return item; // Link fields are already properly formatted + } + // For other fields, apply MAX aggregation + return `MAX(${item}) as ${item.split('.').pop()}`; + }); + + // Build LEFT JOIN clauses + const leftJoins = tableJoins.map((table) => { + // Handle taxonomy_index specially - it joins on nid, not entity_id + if (table === 'taxonomy_index') { + return `LEFT JOIN ${table} ON ${table}.nid = node.nid`; + } + return `LEFT JOIN ${table} ON ${table}.entity_id = node.nid`; + }); + leftJoins.push('LEFT JOIN users ON users.uid = node.uid'); + + // Construct the complete query + const selectClause = [ + 'SELECT node.nid, MAX(node.title) AS title, MAX(node.langcode) AS langcode, MAX(node.type) as type', + ...modifiedResults, + ].join(','); + + const fromClause = 'FROM node_field_data node'; + const joinClause = leftJoins.join(' '); + const whereClause = `WHERE node.type = '${contentType}'`; + const groupClause = 'GROUP BY node.nid'; + + // Final query construction + const finalQuery = `${selectClause} ${fromClause} ${joinClause} ${whereClause} ${groupClause}`; + + // Clean up any double commas + select[contentType] = finalQuery + .replace(/,,/g, ',') + .replace(/, ,/g, ','); + + // Build count query + const countQueryStr = `SELECT count(distinct(node.nid)) as countentry ${fromClause} ${joinClause} ${whereClause}`; + countQuery[`${contentType}Count`] = countQueryStr; + + const fieldMessage = `Generated queries for content type: ${contentType} with ${validResults.length} fields`; + await customLogger( + projectId, + destination_stack_id, + 'info', + fieldMessage, + ); + } catch (error) { + const errorMessage = `Error processing queries for content type: ${contentType}`; + await customLogger( + projectId, + destination_stack_id, + 'error', + errorMessage, + ); + console.error( + 'Error processing queries for content type:', + contentType, + error, + ); + } + } + + return { + page: select, + count: countQuery, + }; + } catch (error: any) { + const errorMessage = `Error in generateQueriesForFields: ${error.message}`; + await customLogger(projectId, destination_stack_id, 'error', errorMessage); + throw error; + } +}; + +/** + * Extract field configuration from Drupal database and generate dynamic queries + * Based on upload-api/migration-drupal/libs/extractQueries.js + */ +/** + * Validates that query configuration file exists (legacy compatibility) + * + * NOTE: This function is for backward compatibility. + * The new dynamic query system uses createQuery() which generates queries + * based on actual database field analysis. + */ +export const createQueryConfig = async ( + destination_stack_id: string, + customQueries?: any, +): Promise => { + const queryDir = path.join(DATA, destination_stack_id, 'query'); + const queryPath = path.join(queryDir, 'index.json'); + + try { + // Check if dynamic query file exists (should be created by createQuery service) + await fs.promises.access(queryPath); + } catch (error) { + // If no dynamic queries exist, this is an error since we removed hardcoded fallbacks + throw new Error( + `❌ No query configuration found at ${queryPath}. Dynamic queries must be generated first using createQuery() service.`, + ); + } +}; + +export const createQuery = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string, +): Promise => { + console.info(`\n🔧🔧🔧 [query.service] createQuery CALLED 🔧🔧🔧`); + console.info( + `🔧 [query.service] destination_stack_id: ${destination_stack_id}`, + ); + console.info(`🔧 [query.service] projectId: ${projectId}`); + + let connection: mysql.Connection | null = null; + + try { + const queryDir = path.join(DATA, destination_stack_id, 'query'); + const queryPath = path.join(queryDir, 'index.json'); + + console.info(`🔧 [query.service] Query path: ${queryPath}`); + + // Create query directory + await fs.promises.mkdir(queryDir, { recursive: true }); + + const message = `Generating dynamic queries from Drupal database...`; + await customLogger(projectId, destination_stack_id, 'info', message); + + connection = await getDbConnection( + dbConfig, + projectId, + destination_stack_id, + ); + + // SQL query to extract field configuration from Drupal + const configQuery = + "SELECT *, CONVERT(data USING utf8) as data FROM config WHERE name LIKE '%field.field.node%'"; + + // Execute query using promise-based approach + const [rows] = (await connection.promise().query(configQuery)) as any[]; + + let fieldData: DrupalFieldData[] = []; + let skippedProfileFields = 0; + + // Process results and extract field information + for (let i = 0; i < rows.length; i++) { + try { + const { unserialize } = await import('php-serialize'); + const convDetails = unserialize(rows[i].data); + if ( + convDetails && + typeof convDetails === 'object' && + 'field_name' in convDetails && + convDetails.bundle !== 'profile' // Filter out profile content type fields + ) { + // Extract target_bundles for entity_reference fields + const targetBundles = convDetails?.settings?.handler_settings + ?.target_bundles + ? Object.keys(convDetails.settings.handler_settings.target_bundles) + : undefined; + + // Skip entity_reference fields that ONLY reference 'profile' + const isEntityReference = + convDetails.field_type === 'entity_reference'; + if (isEntityReference && shouldSkipReferenceField(targetBundles)) { + console.info( + `🚫 [query.service] Skipping field '${convDetails.field_name}' - only references 'profile'`, + ); + skippedProfileFields++; + continue; + } + + fieldData.push({ + field_name: convDetails.field_name, + content_types: convDetails.bundle, + type: convDetails.field_type, + content_handler: convDetails?.settings?.handler, + target_bundles: targetBundles, + }); + } + } catch (err: any) { + console.error(`Couldn't parse row ${i}:`, err.message); + } + } + + if (skippedProfileFields > 0) { + const skipMessage = `Skipped ${skippedProfileFields} reference field(s) that only reference 'profile'`; + await customLogger(projectId, destination_stack_id, 'info', skipMessage); + } + + if (fieldData.length === 0) { + throw new Error('No field configuration found in Drupal database'); + } + + const fieldMessage = `Found ${fieldData.length} field configurations in database (profile fields filtered out)`; + await customLogger(projectId, destination_stack_id, 'info', fieldMessage); + + // Generate queries based on field data + const queryConfig = await generateQueriesForFields( + connection, + fieldData, + projectId, + destination_stack_id, + ); + + // Write query configuration to file + await fs.promises.writeFile( + queryPath, + JSON.stringify(queryConfig, null, 4), + 'utf8', + ); + + const successMessage = `Successfully generated and saved dynamic queries to: ${queryPath}`; + await customLogger(projectId, destination_stack_id, 'info', successMessage); + } catch (error: any) { + const errorMessage = `Failed to generate dynamic queries: ${error.message}`; + await customLogger(projectId, destination_stack_id, 'error', errorMessage); + + console.error('❌ Error generating dynamic queries:', error); + throw new Error( + `Failed to connect to database or generate queries: ${error.message}`, + ); + } finally { + // Always close the connection when done + if (connection) { + try { + connection.end(); + } catch (err: any) { + console.warn('Connection was already closed:', err.message); + } + } + } +}; diff --git a/api/src/services/drupal/references.service.ts b/api/src/services/drupal/references.service.ts new file mode 100644 index 000000000..9f70e16ff --- /dev/null +++ b/api/src/services/drupal/references.service.ts @@ -0,0 +1,421 @@ +import fs from "fs"; +import path from "path"; +import mysql from 'mysql2'; +import { MIGRATION_DATA_CONFIG } from "../../constants/index.js"; +import { getLogMessage } from "../../utils/index.js"; +import customLogger from "../../utils/custom-logger.utils.js"; +import { getDbConnection } from "../../helper/index.js"; + +const { + DATA, + REFERENCES_DIR_NAME, + REFERENCES_FILE_NAME, +} = MIGRATION_DATA_CONFIG; + +interface QueryConfig { + page: { + [contentType: string]: string; + }; + count: { + [contentTypeCount: string]: string; + }; +} + +interface DrupalEntry { + nid: number; + title: string; + langcode: string; + created: number; + type: string; + [key: string]: any; +} + +interface TaxonomyReference { + drupal_term_id: number; + taxonomy_uid: string; + term_uid: string; +} + +interface DrupalTaxonomyTerm { + taxonomy_uid: string; // vid (vocabulary id) + drupal_term_id: number; // term id + term_name: string; // term name + term_description: string | null; // term description +} + +const LIMIT = 100; // Pagination limit for references + +// NOTE: Hardcoded queries have been REMOVED. All queries are now generated dynamically +// by the query.service.ts based on actual database field analysis. + +/** + * Executes SQL query and returns results as Promise + */ +const executeQuery = (connection: mysql.Connection, query: string): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + } else { + resolve(results as any[]); + } + }); + }); +}; + +/** + * Writes data to a specified file, ensuring the target directory exists. + */ +async function writeFile(dirPath: string, filename: string, data: any) { + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + const filePath = path.join(dirPath, filename); + await fs.promises.writeFile(filePath, JSON.stringify(data, null, 4), 'utf8'); + } catch (err) { + console.error(`Error writing ${dirPath}/${filename}::`, err); + } +} + +/** + * Reads existing references file or returns empty object + */ +async function readReferencesFile(referencesPath: string): Promise { + try { + const data = await fs.promises.readFile(referencesPath, "utf8"); + return JSON.parse(data); + } catch (err) { + return {}; + } +} + +/** + * Processes entries for a specific content type and creates reference mappings + * Following the original putPosts logic from references.js + */ +const putPosts = async ( + entries: DrupalEntry[], + contentType: string, + referencesPath: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'putPosts'; + + try { + // Read existing references data + const referenceData = await readReferencesFile(referencesPath); + + // Process each entry and create reference mapping + entries.forEach((entry) => { + const referenceKey = `content_type_entries_title_${entry.nid}`; + referenceData[referenceKey] = { + uid: referenceKey, + _content_type_uid: contentType, + }; + }); + + // Write updated references back to file + await fs.promises.writeFile(referencesPath, JSON.stringify(referenceData, null, 4), 'utf8'); + + const message = getLogMessage( + srcFunc, + `Created ${entries.length} reference mappings for content type ${contentType}.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error creating references for ${contentType}: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Processes entries for a specific content type with pagination + * Following the original getQuery logic from references.js + */ +const getQuery = async ( + connection: mysql.Connection, + contentType: string, + skip: number, + queryPageConfig: QueryConfig, + referencesPath: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'getQuery'; + + try { + // Following original pattern: queryPageConfig['page']['' + pagename + ''] + const baseQuery = queryPageConfig['page'][contentType]; + if (!baseQuery) { + throw new Error(`No query found for content type: ${contentType}`); + } + + const query = baseQuery + ` LIMIT ${skip}, ${LIMIT}`; + const entries = await executeQuery(connection, query); + + if (entries.length === 0) { + return false; // No more entries + } + + await putPosts(entries, contentType, referencesPath, projectId, destination_stack_id); + return true; // More entries might exist + + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error querying references for ${contentType}: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Processes all entries for a specific content type + * Following the original getPageCount logic from references.js + */ +const getPageCount = async ( + connection: mysql.Connection, + contentType: string, + queryPageConfig: QueryConfig, + referencesPath: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'getPageCount'; + + try { + // Process entries in batches + let skip = 0; + let hasMoreEntries = true; + + while (hasMoreEntries) { + hasMoreEntries = await getQuery( + connection, + contentType, + skip, + queryPageConfig, + referencesPath, + projectId, + destination_stack_id + ); + skip += LIMIT; + } + + const message = getLogMessage( + srcFunc, + `Completed reference extraction for content type ${contentType}.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error processing content type ${contentType}: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Reads dynamic query configuration file generated by query.service.ts + * Following original pattern: helper.readFile(path.join(process.cwd(), config.data, 'query', 'index.json')) + * + * NOTE: No fallback to hardcoded queries - dynamic queries MUST be generated first + */ +async function readQueryConfig(destination_stack_id: string): Promise { + try { + const queryPath = path.join(DATA, destination_stack_id, 'query', 'index.json'); + const data = await fs.promises.readFile(queryPath, "utf8"); + return JSON.parse(data); + } catch (err) { + // No fallback - dynamic queries must be generated first by createQuery() service + throw new Error(`❌ No dynamic query configuration found at query/index.json. Dynamic queries must be generated first using createQuery() service. Original error: ${err}`); + } +} + +/** + * Creates taxonomy reference mappings from Drupal database + * Using the taxonomy query to create a flat mapping file: taxonomyReference.json + */ +const createTaxonomyReferences = async ( + connection: mysql.Connection, + referencesSave: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'createTaxonomyReferences'; + + try { + const message = getLogMessage( + srcFunc, + `Creating taxonomy reference mappings...`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Use the same SQL query as taxonomy.service.ts + const taxonomyQuery = ` + SELECT + f.vid AS taxonomy_uid, + f.tid AS drupal_term_id, + f.name AS term_name, + f.description__value AS term_description + FROM taxonomy_term_field_data f + ORDER BY f.vid, f.tid + `; + + const taxonomyTerms = await executeQuery(connection, taxonomyQuery); + + if (taxonomyTerms.length === 0) { + const noDataMessage = getLogMessage( + srcFunc, + `No taxonomy terms found in database.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', noDataMessage); + return; + } + + // Transform to taxonomy reference format + const taxonomyReferences: TaxonomyReference[] = []; + + for (const term of taxonomyTerms as DrupalTaxonomyTerm[]) { + const termUid = `${term.taxonomy_uid}_${term.drupal_term_id}`; + + taxonomyReferences.push({ + drupal_term_id: term.drupal_term_id, + taxonomy_uid: term.taxonomy_uid, + term_uid: termUid + }); + } + + // Save taxonomy references to taxonomyReference.json + await writeFile(referencesSave, 'taxonomyReference.json', taxonomyReferences); + + const successMessage = getLogMessage( + srcFunc, + `Created ${taxonomyReferences.length} taxonomy reference mappings.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error creating taxonomy references: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Creates reference mappings from Drupal database for migration to Contentstack. + * Based on the original Drupal v8 references.js logic with direct SQL queries. + * + * This creates a references.json file that maps node IDs to content types, + * which is then used by the entries service to resolve entity references. + * + * Supports dynamic SQL queries from query/index.json file following original pattern: + * var queryPageConfig = helper.readFile(path.join(process.cwd(), config.data, 'query', 'index.json')); + * var query = queryPageConfig['page']['' + pagename + '']; + */ +export const createRefrence = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string, + isTest = false +): Promise => { + const srcFunc = 'createRefrence'; + let connection: mysql.Connection | null = null; + + try { + const referencesSave = path.join(DATA, destination_stack_id, REFERENCES_DIR_NAME); + const referencesPath = path.join(referencesSave, REFERENCES_FILE_NAME); + + // Initialize directories and files + await fs.promises.mkdir(referencesSave, { recursive: true }); + + // Initialize empty references file if it doesn't exist + if (!await fs.promises.access(referencesPath).then(() => true).catch(() => false)) { + await fs.promises.writeFile(referencesPath, JSON.stringify({}, null, 4), 'utf8'); + } + + const message = getLogMessage( + srcFunc, + `Exporting references...`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Read query configuration (following original pattern) + const queryPageConfig = await readQueryConfig(destination_stack_id); + + // Create database connection + connection = await getDbConnection(dbConfig, projectId, destination_stack_id); + + // Process each content type from query config (like original) + const pageQuery = queryPageConfig.page; + const contentTypes = Object.keys(pageQuery); + const typesToProcess = isTest ? contentTypes.slice(0, 2) : contentTypes; + + // Process content types sequentially (like original sequence logic) + for (const contentType of typesToProcess) { + await getPageCount( + connection, + contentType, + queryPageConfig, + referencesPath, + projectId, + destination_stack_id + ); + } + + // Create taxonomy reference mappings + await createTaxonomyReferences( + connection, + referencesSave, + projectId, + destination_stack_id + ); + + const successMessage = getLogMessage( + srcFunc, + `Successfully created reference mappings for ${typesToProcess.length} content types and taxonomy references.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error encountered while creating references.`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } finally { + // Close database connection + if (connection) { + connection.end(); + } + } +}; \ No newline at end of file diff --git a/api/src/services/drupal/taxonomy.service.ts b/api/src/services/drupal/taxonomy.service.ts new file mode 100644 index 000000000..58321b99d --- /dev/null +++ b/api/src/services/drupal/taxonomy.service.ts @@ -0,0 +1,465 @@ +import fs from 'fs'; +import path from 'path'; +import mysql from 'mysql2'; +import { getDbConnection } from '../../helper/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { getLogMessage } from '../../utils/index.js'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; + +const { DATA, TAXONOMIES_DIR_NAME } = MIGRATION_DATA_CONFIG; + +interface DrupalTaxonomyTerm { + taxonomy_uid: string; // vid (vocabulary id) + term_tid: number; // term id + term_name: string; // term name + term_description: string | null; // term description +} + +interface TaxonomyTerm { + uid: string; + name: string; + parent_uid: string | null; + description?: string; +} + +interface TaxonomyStructure { + taxonomy: { + uid: string; + name: string; + description: string; + }; + terms: TaxonomyTerm[]; +} + +/** + * Execute SQL query with promise support + */ +const executeQuery = async ( + connection: mysql.Connection, + query: string +): Promise => { + return new Promise((resolve, reject) => { + connection.query(query, (error, results) => { + if (error) { + reject(error); + return; + } + resolve(results as any[]); + }); + }); +}; + +/** + * Get vocabulary names from Drupal database + * Note: In Drupal 8+, vocabulary names are in the config table + */ +const getVocabularyNames = async ( + connection: mysql.Connection, + projectId: string, + destination_stack_id: string +): Promise> => { + const srcFunc = 'getVocabularyNames'; + + try { + // Try to get vocabulary names from config table (Drupal 8+) + const configQuery = ` + SELECT + SUBSTRING_INDEX(SUBSTRING_INDEX(name, '.', 3), '.', -1) as vid, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.name')) as name + FROM config + WHERE name LIKE 'taxonomy.vocabulary.%' + AND data IS NOT NULL + `; + + const vocabularies = await executeQuery(connection, configQuery); + + const vocabNames: Record = {}; + + for (const vocab of vocabularies) { + if (vocab.vid && vocab.name) { + vocabNames[vocab.vid] = vocab.name; + } + } + + const message = getLogMessage( + srcFunc, + `Found ${Object.keys(vocabNames).length} vocabularies in config.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return vocabNames; + } catch (error: any) { + // Fallback: use vid as name if config method fails + const message = getLogMessage( + srcFunc, + `Could not fetch vocabulary names from config, will use vid as name: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'warn', message); + + return {}; + } +}; + +/** + * Fetch taxonomy hierarchy information + * Note: Drupal uses taxonomy_term__parent or taxonomy_term_hierarchy table for hierarchy + */ +const getTermHierarchy = async ( + connection: mysql.Connection, + projectId: string, + destination_stack_id: string +): Promise> => { + const srcFunc = 'getTermHierarchy'; + + try { + // Try different possible hierarchy table structures + const hierarchyQueries = [ + // Drupal 8+ field-based hierarchy + `SELECT entity_id as tid, parent_target_id as parent_tid + FROM taxonomy_term__parent + WHERE parent_target_id IS NOT NULL AND parent_target_id != 0`, + + // Drupal 7 style hierarchy + `SELECT tid, parent + FROM taxonomy_term_hierarchy + WHERE parent IS NOT NULL AND parent != 0`, + ]; + + let hierarchyData: any[] = []; + + for (const query of hierarchyQueries) { + try { + hierarchyData = await executeQuery(connection, query); + if (hierarchyData.length > 0) { + break; // Use the first successful query + } + } catch (queryError) { + // Continue to next query if this one fails + continue; + } + } + + const hierarchy: Record = {}; + + for (const item of hierarchyData) { + const childTid = item.tid || item.entity_id; + const parentTid = item.parent || item.parent_tid || item.parent_target_id; + + if (childTid && parentTid) { + if (!hierarchy[parentTid]) { + hierarchy[parentTid] = []; + } + hierarchy[parentTid].push(childTid); + } + } + + const message = getLogMessage( + srcFunc, + `Found ${Object.keys(hierarchy).length} parent-child relationships.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return hierarchy; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Could not fetch term hierarchy: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'warn', message); + + return {}; + } +}; + +/** + * Process taxonomy terms and organize by vocabulary + */ +const processTaxonomyData = async ( + terms: DrupalTaxonomyTerm[], + vocabularyNames: Record, + hierarchy: Record, + projectId: string, + destination_stack_id: string +): Promise> => { + const srcFunc = 'processTaxonomyData'; + + try { + const taxonomies: Record = {}; + + // Group terms by vocabulary + const termsByVocabulary: Record = {}; + + for (const term of terms) { + if (!termsByVocabulary[term.taxonomy_uid]) { + termsByVocabulary[term.taxonomy_uid] = []; + } + termsByVocabulary[term.taxonomy_uid].push(term); + } + + // Create taxonomy structure for each vocabulary + for (const [vid, vocabTerms] of Object.entries(termsByVocabulary)) { + const vocabularyName = vocabularyNames[vid] || vid; + + const taxonomyStructure: TaxonomyStructure = { + taxonomy: { + uid: vid, + name: vocabularyName, + description: '', + }, + terms: [], + }; + + // Convert terms to Contentstack format + for (const term of vocabTerms) { + // 🏷️ Generate term UID using vocabulary prefix + term ID format + const vocabularyPrefix = vid.toLowerCase(); + const termUid = `${vocabularyPrefix}_${term.term_tid}`; + + // Find parent if exists + let parentUid: string | null = null; + for (const [parentTid, childTids] of Object.entries(hierarchy)) { + if (childTids.includes(term.term_tid)) { + // Find parent term in the same vocabulary + const parentTerm = vocabTerms.find( + (t) => t.term_tid === parseInt(parentTid) + ); + if (parentTerm) { + // 🏷️ Generate parent UID using same vocabulary prefix + term ID format + parentUid = `${vocabularyPrefix}_${parentTerm.term_tid}`; + } + break; + } + } + + taxonomyStructure.terms.push({ + uid: termUid, + name: term.term_name, + parent_uid: parentUid, + description: term.term_description || '', + }); + } + + taxonomies[vid] = taxonomyStructure; + } + + const message = getLogMessage( + srcFunc, + `Processed ${Object.keys(taxonomies).length} vocabularies with ${ + terms.length + } total terms.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + return taxonomies; + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error processing taxonomy data: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Save taxonomy files to disk + */ +const saveTaxonomyFiles = async ( + taxonomies: Record, + taxonomiesPath: string, + projectId: string, + destination_stack_id: string +): Promise => { + const srcFunc = 'saveTaxonomyFiles'; + + try { + let filesSaved = 0; + + // Save individual taxonomy files (existing functionality) + for (const [vid, taxonomy] of Object.entries(taxonomies)) { + const filePath = path.join(taxonomiesPath, `${vid}.json`); + await fs.promises.writeFile( + filePath, + JSON.stringify(taxonomy, null, 2), + 'utf8' + ); + filesSaved++; + + const message = getLogMessage( + srcFunc, + `Saved taxonomy file: ${vid}.json with ${taxonomy.terms.length} terms.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + } + + // Create consolidated taxonomies.json file with just vocabulary metadata + const taxonomiesDataObject: Record = {}; + + for (const [vid, taxonomy] of Object.entries(taxonomies)) { + taxonomiesDataObject[vid] = { + uid: taxonomy.taxonomy.uid, + name: taxonomy.taxonomy.name, + description: taxonomy.taxonomy.description, + }; + } + + const taxonomiesFilePath = path.join(taxonomiesPath, 'taxonomies.json'); + await fs.promises.writeFile( + taxonomiesFilePath, + JSON.stringify(taxonomiesDataObject, null, 2), + 'utf8' + ); + + const consolidatedMessage = getLogMessage( + srcFunc, + `Saved consolidated taxonomies.json with ${ + Object.keys(taxonomiesDataObject).length + } vocabularies.`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + consolidatedMessage + ); + + const summaryMessage = getLogMessage( + srcFunc, + `Successfully saved ${filesSaved} individual taxonomy files + 1 consolidated taxonomies.json file.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', summaryMessage); + } catch (error: any) { + const message = getLogMessage( + srcFunc, + `Error saving taxonomy files: ${error.message}`, + {}, + error + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw error; + } +}; + +/** + * Creates taxonomy files from Drupal database for migration to Contentstack. + * + * Extracts taxonomy vocabularies and terms from Drupal database, + * organizes them by vocabulary, and saves individual JSON files + * for each vocabulary in the format expected by Contentstack. + */ +export const createTaxonomy = async ( + dbConfig: any, + destination_stack_id: string, + projectId: string +): Promise => { + const srcFunc = 'createTaxonomy'; + let connection: mysql.Connection | null = null; + + try { + const taxonomiesPath = path.join( + DATA, + destination_stack_id, + TAXONOMIES_DIR_NAME + ); + + // Create taxonomies directory + await fs.promises.mkdir(taxonomiesPath, { recursive: true }); + + const message = getLogMessage(srcFunc, `Exporting taxonomies...`, {}); + await customLogger(projectId, destination_stack_id, 'info', message); + + // Create database connection + connection = await getDbConnection( + dbConfig, + projectId, + destination_stack_id + ); + + // Main SQL query to fetch taxonomy terms + const taxonomyQuery = ` + SELECT + f.vid AS taxonomy_uid, + f.tid AS term_tid, + f.name AS term_name, + f.description__value AS term_description + FROM taxonomy_term_field_data f + ORDER BY f.vid, f.tid + `; + + // Fetch taxonomy data + const taxonomyTerms = await executeQuery(connection, taxonomyQuery); + + if (taxonomyTerms.length === 0) { + const noDataMessage = getLogMessage( + srcFunc, + `No taxonomy terms found in database.`, + {} + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + noDataMessage + ); + return; + } + + // Get vocabulary names and hierarchy + const [vocabularyNames, hierarchy] = await Promise.all([ + getVocabularyNames(connection, projectId, destination_stack_id), + getTermHierarchy(connection, projectId, destination_stack_id), + ]); + + // Process taxonomy data + const taxonomies = await processTaxonomyData( + taxonomyTerms, + vocabularyNames, + hierarchy, + projectId, + destination_stack_id + ); + + // Save taxonomy files + await saveTaxonomyFiles( + taxonomies, + taxonomiesPath, + projectId, + destination_stack_id + ); + + const successMessage = getLogMessage( + srcFunc, + `Successfully exported ${ + Object.keys(taxonomies).length + } taxonomies with ${taxonomyTerms.length} total terms.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error encountered while creating taxonomies.`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } finally { + // Close database connection + if (connection) { + connection.end(); + } + } +}; diff --git a/api/src/services/drupal/version.service.ts b/api/src/services/drupal/version.service.ts new file mode 100644 index 000000000..20e6ff6be --- /dev/null +++ b/api/src/services/drupal/version.service.ts @@ -0,0 +1,61 @@ +import fs from "fs"; +import path from "path"; +import { MIGRATION_DATA_CONFIG } from "../../constants/index.js"; +import { getLogMessage } from "../../utils/index.js"; +import customLogger from "../../utils/custom-logger.utils.js"; + +const { DATA, EXPORT_INFO_FILE } = MIGRATION_DATA_CONFIG; + +/** + * Writes data to a specified file, ensuring the target directory exists. + */ +async function writeFile(dirPath: string, filename: string, data: any) { + try { + await fs.promises.mkdir(dirPath, { recursive: true }); + const filePath = path.join(dirPath, filename); + await fs.promises.writeFile(filePath, JSON.stringify(data), 'utf8'); + } catch (err) { + console.error(`Error writing ${dirPath}/${filename}::`, err); + } +} + +/** + * Creates a version file for the given destination stack for Drupal migration. + */ +export const createVersionFile = async ( + destination_stack_id: string, + projectId: string +): Promise => { + const srcFunc = 'createVersionFile'; + + try { + const versionData = { + contentVersion: 2, + logsPath: "", + migrationSource: "drupal", + migrationTimestamp: new Date().toISOString(), + }; + + await writeFile( + path.join(DATA, destination_stack_id), + EXPORT_INFO_FILE, + versionData + ); + + const message = getLogMessage( + srcFunc, + `Version file has been successfully created.`, + {} + ); + await customLogger(projectId, destination_stack_id, 'info', message); + + } catch (err) { + const message = getLogMessage( + srcFunc, + `Error writing version file: ${err}`, + {}, + err + ); + await customLogger(projectId, destination_stack_id, 'error', message); + } +}; diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index dc4348bb7..3e176c9d7 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -1,4 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-unused-expressions +/* eslint-disable */ import { Request } from 'express'; import path from 'path'; @@ -24,6 +25,7 @@ import { import { fieldAttacher } from '../utils/field-attacher.utils.js'; import { siteCoreService } from './sitecore.service.js'; import { wordpressService } from './wordpress.service.js'; +import { drupalService } from './drupal.service.js'; import { testFolderCreator } from '../utils/test-folder-creator.utils.js'; import { utilsCli } from './runCli.service.js'; import customLogger from '../utils/custom-logger.utils.js'; @@ -57,7 +59,7 @@ const createTestStack = async (req: Request): Promise => { try { const authtoken = await getAuthtoken( token_payload?.region, - token_payload?.user_id + token_payload?.user_id, ); await ProjectModelLowdb.read(); @@ -88,7 +90,7 @@ const createTestStack = async (req: Request): Promise => { master_locale, }, }, - }) + }), ); if (err) { @@ -97,8 +99,8 @@ const createTestStack = async (req: Request): Promise => { srcFun, HTTP_TEXTS.CS_ERROR, token_payload, - err.response.data - ) + err.response.data, + ), ); return { @@ -112,6 +114,73 @@ const createTestStack = async (req: Request): Promise => { .findIndex({ id: projectId }) .value(); if (index > -1) { + // ✅ Generate queries for new test stack (Drupal only) + const project = ProjectModelLowdb.data.projects[index]; + if (project?.legacy_cms?.cms === CMS.DRUPAL) { + try { + const startMessage = getLogMessage( + srcFun, + `Generating dynamic queries for new test stack (${res?.data?.stack?.api_key})...`, + token_payload, + ); + await customLogger( + projectId, + res?.data?.stack?.api_key, + 'info', + startMessage, + ); + + // Get database configuration from project + const legacyCms = project?.legacy_cms as unknown as Record< + string, + unknown + >; + const mySQLDetails = legacyCms?.mySQLDetails as + | Record + | undefined; + const dbConfig = { + host: mySQLDetails?.host as string | undefined, + user: mySQLDetails?.user as string | undefined, + password: (mySQLDetails?.password as string) || '', + database: mySQLDetails?.database as string | undefined, + port: (mySQLDetails?.port as number) || 3306, + }; + + // Generate dynamic queries for the new test stack + await drupalService.createQuery( + dbConfig, + res?.data?.stack?.api_key, + projectId, + ); + + const successMessage = getLogMessage( + srcFun, + `Successfully generated queries for test stack (${res?.data?.stack?.api_key})`, + token_payload, + ); + await customLogger( + projectId, + res?.data?.stack?.api_key, + 'info', + successMessage, + ); + } catch (error: any) { + const errorMessage = getLogMessage( + srcFun, + `Failed to generate queries for test stack: ${error.message}. Test migration may fail.`, + token_payload, + error, + ); + await customLogger( + projectId, + res?.data?.stack?.api_key, + 'error', + errorMessage, + ); + // Don't throw error - let test stack creation succeed even if query generation fails + } + } + ProjectModelLowdb.update((data: any) => { data.projects[index].current_step = STEPPER_STEPS['TESTING']; data.projects[index].current_test_stack_id = res?.data?.stack?.api_key; @@ -137,13 +206,13 @@ const createTestStack = async (req: Request): Promise => { srcFun, 'Error while creating a stack', token_payload, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -161,7 +230,7 @@ const deleteTestStack = async (req: Request): Promise => { try { const authtoken = await getAuthtoken( token_payload?.region, - token_payload?.user_id + token_payload?.user_id, ); const [err, res] = await safePromise( @@ -174,7 +243,7 @@ const deleteTestStack = async (req: Request): Promise => { api_key: stack_key, authtoken, }, - }) + }), ); if (err) { @@ -183,8 +252,8 @@ const deleteTestStack = async (req: Request): Promise => { srcFun, HTTP_TEXTS.CS_ERROR, token_payload, - err.response.data - ) + err.response.data, + ), ); return { @@ -217,13 +286,13 @@ const deleteTestStack = async (req: Request): Promise => { srcFun, 'Error while creating a stack', token_payload, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -250,23 +319,23 @@ const startTestMigration = async (req: Request): Promise => { process.cwd(), 'logs', projectId, - `${project?.current_test_stack_id}.log` + `${project?.current_test_stack_id}.log`, ); const message = getLogMessage( 'startTestMigration', 'Starting Test Migration...', - {} + {}, ); await customLogger( projectId, project?.current_test_stack_id, 'info', - message + message, ); await setLogFilePath(loggerPath); const copyLogsToTestStack = async ( stackUid: string, - projectLogPath: string + projectLogPath: string, ) => { try { // Sanitize stackUid using dedicated sanitization function to prevent path traversal @@ -288,14 +357,14 @@ const startTestMigration = async (req: Request): Promise => { sanitizedStackUid, 'logs', 'import', - 'error.log' + 'error.log', ); const successLogPath = path.join( resolvedBaseDir, sanitizedStackUid, 'logs', 'import', - 'success.log' + 'success.log', ); // Final validation to ensure paths are within the expected base directory @@ -304,7 +373,7 @@ const startTestMigration = async (req: Request): Promise => { !path.resolve(successLogPath).startsWith(resolvedBaseDir + path.sep) ) { console.error( - 'Invalid path detected, potential path traversal attempt' + 'Invalid path detected, potential path traversal attempt', ); return; } @@ -320,7 +389,7 @@ const startTestMigration = async (req: Request): Promise => { // path containment check, and realpath canonicalization before reading const errorLogs = await fsPromises.readFile( canonicalErrorPath, - 'utf8' + 'utf8', ); combinedLogs += errorLogs + '\n'; } @@ -331,7 +400,7 @@ const startTestMigration = async (req: Request): Promise => { // Read and combine success logs - use realpath to canonicalize and validate path try { const canonicalSuccessPath = await fsPromises.realpath( - successLogPath + successLogPath, ); // Verify canonical path is still within base directory if (canonicalSuccessPath.startsWith(resolvedBaseDir + path.sep)) { @@ -339,7 +408,7 @@ const startTestMigration = async (req: Request): Promise => { // path containment check, and realpath canonicalization before reading const successLogs = await fsPromises.readFile( canonicalSuccessPath, - 'utf8' + 'utf8', ); combinedLogs += successLogs; } @@ -406,13 +475,13 @@ const startTestMigration = async (req: Request): Promise => { req, project?.current_test_stack_id, projectId, - project + project, ); await siteCoreService?.createEnvironment( - project?.current_test_stack_id + project?.current_test_stack_id, ); await siteCoreService?.createVersionFile( - project?.current_test_stack_id + project?.current_test_stack_id, ); } break; @@ -423,30 +492,30 @@ const startTestMigration = async (req: Request): Promise => { req, project?.current_test_stack_id, projectId, - project + project, ); await wordpressService?.getAllAssets( file_path, packagePath, project?.current_test_stack_id, - projectId + projectId, ); await wordpressService?.createAssetFolderFile( file_path, project?.current_test_stack_id, - projectId + projectId, ); await wordpressService?.getAllreference( file_path, packagePath, project?.current_test_stack_id, - projectId + projectId, ); await wordpressService?.extractChunks( file_path, packagePath, project?.current_test_stack_id, - projectId + projectId, ); await wordpressService?.getAllAuthors( file_path, @@ -456,7 +525,7 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); //await wordpressService?.extractContentTypes(projectId, project?.current_test_stack_id, contentTypes) await wordpressService?.getAllTerms( @@ -467,7 +536,7 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.getAllTags( file_path, @@ -477,7 +546,7 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.getAllCategories( file_path, @@ -487,7 +556,7 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractPosts( packagePath, @@ -496,7 +565,7 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractPages( packagePath, @@ -505,15 +574,15 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractGlobalFields( project?.current_test_stack_id, - projectId + projectId, ); await wordpressService?.createVersionFile( project?.current_test_stack_id, - projectId + projectId, ); } break; @@ -524,28 +593,28 @@ const startTestMigration = async (req: Request): Promise => { cleanLocalPath, project?.current_test_stack_id, projectId, - project + project, ); await contentfulService?.createRefrence( cleanLocalPath, project?.current_test_stack_id, - projectId + projectId, ); await contentfulService?.createWebhooks( cleanLocalPath, project?.current_test_stack_id, - projectId + projectId, ); await contentfulService?.createEnvironment( cleanLocalPath, project?.current_test_stack_id, - projectId + projectId, ); await contentfulService?.createAssets( cleanLocalPath, project?.current_test_stack_id, projectId, - true + true, ); await contentfulService?.createEntry( cleanLocalPath, @@ -554,11 +623,11 @@ const startTestMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await contentfulService?.createVersionFile( project?.current_test_stack_id, - projectId + projectId, ); break; } @@ -582,12 +651,102 @@ const startTestMigration = async (req: Request): Promise => { req, project?.current_test_stack_id, projectId, - project + project, ); await aemService?.createVersionFile(project?.current_test_stack_id); break; } + case CMS.DRUPAL: { + // Get database configuration from project + const dbConfig = { + host: project?.legacy_cms?.mySQLDetails?.host, + user: project?.legacy_cms?.mySQLDetails?.user, + password: project?.legacy_cms?.mySQLDetails?.password || '', + database: project?.legacy_cms?.mySQLDetails?.database, + port: project?.legacy_cms?.mySQLDetails?.port || 3306, + }; + + // Get Drupal assets URL configuration from project, request body, or environment variables + // Priority: project config > request body > environment variables > empty (auto-detection) + const drupalAssetsConfig = { + base_url: + project?.legacy_cms?.assetsConfig?.base_url || + req.body?.assetsConfig?.base_url || + process.env.DRUPAL_ASSETS_BASE_URL || + '', + public_path: + project?.legacy_cms?.assetsConfig?.public_path || + req.body?.assetsConfig?.public_path || + process.env.DRUPAL_ASSETS_PUBLIC_PATH || + '', + }; + + // Run Drupal migration services in proper order (following test-drupal-services sequence) + // Step 1: Generate dynamic queries from database analysis (MUST RUN FIRST) + await drupalService?.createQuery( + dbConfig, + project?.current_test_stack_id, + projectId, + ); + + // Step 2: Generate content type schemas from upload-api (CRITICAL: Must run after upload-api generates schema) + await drupalService?.generateContentTypeSchemas( + project?.current_test_stack_id, + projectId, + ); + + // Step 3: Create assets from Drupal database + await drupalService?.createAssets( + dbConfig, + project?.current_test_stack_id, + projectId, + true, + drupalAssetsConfig, + ); + + // Step 4: Create references + await drupalService?.createRefrence( + dbConfig, + project?.current_test_stack_id, + projectId, + true, + ); + + // Step 5: Create taxonomy + await drupalService?.createTaxonomy( + dbConfig, + project?.current_test_stack_id, + projectId, + ); + + // Step 6: Create entries + await drupalService?.createEntry( + dbConfig, + project?.current_test_stack_id, + projectId, + true, + project?.stackDetails?.master_locale, + project?.content_mapper || [], + project, + ); + + // Step 7: Create locale + await drupalService?.createLocale( + dbConfig, + project?.current_test_stack_id, + projectId, + project, + ); + + // Step 8: Create version file + await drupalService?.createVersionFile( + project?.current_test_stack_id, + projectId, + ); + break; + } + default: break; } @@ -602,7 +761,7 @@ const startTestMigration = async (req: Request): Promise => { project?.current_test_stack_id, projectId, true, - loggerPath + loggerPath, ); } }; @@ -640,24 +799,24 @@ const startMigration = async (req: Request): Promise => { process.cwd(), 'logs', projectId, - `${project?.destination_stack_id}.log` + `${project?.destination_stack_id}.log`, ); const message = getLogMessage( 'start Migration', 'Starting Migration...', - {} + {}, ); await customLogger( projectId, project?.destination_stack_id, 'info', - message + message, ); await setLogFilePath(loggerPath); const copyLogsToStack = async ( stackUid: string, - projectLogPath: string + projectLogPath: string, ) => { try { // Sanitize stackUid using dedicated sanitization function to prevent path traversal @@ -679,14 +838,14 @@ const startMigration = async (req: Request): Promise => { sanitizedStackUid, 'logs', 'import', - 'error.log' + 'error.log', ); const successLogPath = path.join( resolvedBaseDir, sanitizedStackUid, 'logs', 'import', - 'success.log' + 'success.log', ); // Final validation to ensure paths are within the expected base directory @@ -695,7 +854,7 @@ const startMigration = async (req: Request): Promise => { !path.resolve(successLogPath).startsWith(resolvedBaseDir + path.sep) ) { console.error( - 'Invalid path detected, potential path traversal attempt' + 'Invalid path detected, potential path traversal attempt', ); return; } @@ -711,7 +870,7 @@ const startMigration = async (req: Request): Promise => { // path containment check, and realpath canonicalization before reading const errorLogs = await fsPromises.readFile( canonicalErrorPath, - 'utf8' + 'utf8', ); combinedLogs += errorLogs + '\n'; } @@ -722,7 +881,7 @@ const startMigration = async (req: Request): Promise => { // Read and combine success logs - use realpath to canonicalize and validate path try { const canonicalSuccessPath = await fsPromises.realpath( - successLogPath + successLogPath, ); // Verify canonical path is still within base directory if (canonicalSuccessPath.startsWith(resolvedBaseDir + path.sep)) { @@ -730,7 +889,7 @@ const startMigration = async (req: Request): Promise => { // path containment check, and realpath canonicalization before reading const successLogs = await fsPromises.readFile( canonicalSuccessPath, - 'utf8' + 'utf8', ); combinedLogs += successLogs; } @@ -797,10 +956,10 @@ const startMigration = async (req: Request): Promise => { req, project?.destination_stack_id, projectId, - project + project, ); await siteCoreService?.createVersionFile( - project?.destination_stack_id + project?.destination_stack_id, ); } break; @@ -811,30 +970,30 @@ const startMigration = async (req: Request): Promise => { req, project?.current_test_stack_id, projectId, - project + project, ); await wordpressService?.getAllAssets( file_path, packagePath, project?.destination_stack_id, - projectId + projectId, ); await wordpressService?.createAssetFolderFile( file_path, project?.destination_stack_id, - projectId + projectId, ); await wordpressService?.getAllreference( file_path, packagePath, project?.destination_stack_id, - projectId + projectId, ); await wordpressService?.extractChunks( file_path, packagePath, project?.destination_stack_id, - projectId + projectId, ); await wordpressService?.getAllAuthors( file_path, @@ -844,7 +1003,7 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); //await wordpressService?.extractContentTypes(projectId, project?.destination_stack_id) await wordpressService?.getAllTerms( @@ -855,7 +1014,7 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.getAllTags( file_path, @@ -865,7 +1024,7 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.getAllCategories( file_path, @@ -875,7 +1034,7 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractPosts( packagePath, @@ -884,7 +1043,7 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractPages( packagePath, @@ -893,15 +1052,15 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await wordpressService?.extractGlobalFields( project?.destination_stack_id, - projectId + projectId, ); await wordpressService?.createVersionFile( project?.destination_stack_id, - projectId + projectId, ); } break; @@ -912,27 +1071,27 @@ const startMigration = async (req: Request): Promise => { cleanLocalPath, project?.destination_stack_id, projectId, - project + project, ); await contentfulService?.createRefrence( cleanLocalPath, project?.destination_stack_id, - projectId + projectId, ); await contentfulService?.createWebhooks( cleanLocalPath, project?.destination_stack_id, - projectId + projectId, ); await contentfulService?.createEnvironment( cleanLocalPath, project?.destination_stack_id, - projectId + projectId, ); await contentfulService?.createAssets( cleanLocalPath, project?.destination_stack_id, - projectId + projectId, ); await contentfulService?.createEntry( cleanLocalPath, @@ -941,11 +1100,11 @@ const startMigration = async (req: Request): Promise => { contentTypes, project?.mapperKeys, project?.stackDetails?.master_locale, - project + project, ); await contentfulService?.createVersionFile( project?.destination_stack_id, - projectId + projectId, ); break; } @@ -968,12 +1127,101 @@ const startMigration = async (req: Request): Promise => { req, project?.destination_stack_id, projectId, - project + project, ); await aemService?.createVersionFile(project?.destination_stack_id); break; } + case CMS.DRUPAL: { + // Get database configuration from project + const dbConfig = { + host: project?.legacy_cms?.mySQLDetails?.host, + user: project?.legacy_cms?.mySQLDetails?.user, + password: project?.legacy_cms?.mySQLDetails?.password || '', + database: project?.legacy_cms?.mySQLDetails?.database, + port: project?.legacy_cms?.mySQLDetails?.port || 3306, + }; + + // Get Drupal assets URL configuration from project, request body, or environment variables + const drupalAssetsConfig = { + base_url: + project?.legacy_cms?.assetsConfig?.base_url || + req.body?.assetsConfig?.base_url || + process.env.DRUPAL_ASSETS_BASE_URL || + '', + public_path: + project?.legacy_cms?.assetsConfig?.public_path || + req.body?.assetsConfig?.public_path || + process.env.DRUPAL_ASSETS_PUBLIC_PATH || + '', + }; + + // Run Drupal migration services in proper order + // Step 1: Generate dynamic queries from database analysis + await drupalService?.createQuery( + dbConfig, + project?.destination_stack_id, + projectId, + ); + + // Step 2: Generate content type schemas from upload-api + await drupalService?.generateContentTypeSchemas( + project?.destination_stack_id, + projectId, + ); + + // Step 3: Create assets from Drupal database + await drupalService?.createAssets( + dbConfig, + project?.destination_stack_id, + projectId, + false, // Not a test migration + drupalAssetsConfig, + ); + + // Step 4: Create references + await drupalService?.createRefrence( + dbConfig, + project?.destination_stack_id, + projectId, + false, // Not a test migration + ); + + // Step 5: Create taxonomy + await drupalService?.createTaxonomy( + dbConfig, + project?.destination_stack_id, + projectId, + ); + + // Step 6: Create entries + await drupalService?.createEntry( + dbConfig, + project?.destination_stack_id, + projectId, + false, // Not a test migration + project?.stackDetails?.master_locale, + project?.content_mapper || [], + project, + ); + + // Step 7: Create locale + await drupalService?.createLocale( + dbConfig, + project?.destination_stack_id, + projectId, + project, + ); + + // Step 8: Create version file + await drupalService?.createVersionFile( + project?.destination_stack_id, + projectId, + ); + break; + } + default: break; } @@ -983,7 +1231,7 @@ const startMigration = async (req: Request): Promise => { project?.destination_stack_id, projectId, false, - loggerPath + loggerPath, ); } }; @@ -1012,7 +1260,7 @@ const getAuditData = async (req: Request): Promise => { const stackFolders = fs.readdirSync(logsDir); const stackFolder = stackFolders?.find((folder) => - folder?.startsWith?.(stackId) + folder?.startsWith?.(stackId), ); if (!stackFolder) { throw new BadRequestError('Migration data not found for this stack'); @@ -1022,7 +1270,7 @@ const getAuditData = async (req: Request): Promise => { stackFolder, GET_AUDIT_DATA?.LOGS_DIR, GET_AUDIT_DATA?.AUDIT_DIR, - GET_AUDIT_DATA?.AUDIT_REPORT + GET_AUDIT_DATA?.AUDIT_REPORT, ); if (!fs.existsSync(auditLogPath)) { throw new BadRequestError('Audit log path not found'); @@ -1057,7 +1305,7 @@ const getAuditData = async (req: Request): Promise => { const fileContent = await fsPromises?.readFile( safeEntriesSelectFieldPath, - 'utf8' + 'utf8', ); try { if (typeof fileContent === 'string') { @@ -1067,7 +1315,7 @@ const getAuditData = async (req: Request): Promise => { } catch (error) { logger.error( `Error parsing JSON from file ${entriesSelectFieldPath}:`, - error + error, ); throw new BadRequestError('Invalid JSON format in audit file'); } @@ -1103,7 +1351,7 @@ const getAuditData = async (req: Request): Promise => { !safeFilePath.startsWith(auditLogPath) ) { throw new BadRequestError( - 'Path traversal detected or access to this file is not allowed.' + 'Path traversal detected or access to this file is not allowed.', ); } const fileContent = await fsPromises?.readFile(safeFilePath, 'utf8'); @@ -1120,7 +1368,7 @@ const getAuditData = async (req: Request): Promise => { if (!fileData) { throw new BadRequestError( - `No audit data found for module: ${moduleName}` + `No audit data found for module: ${moduleName}`, ); } let transformedData = transformAndFlattenData(fileData); @@ -1144,7 +1392,7 @@ const getAuditData = async (req: Request): Promise => { (value) => value && typeof value === 'string' && - value?.toLowerCase?.()?.includes(searchText?.toLowerCase()) + value?.toLowerCase?.()?.includes(searchText?.toLowerCase()), ); }); } @@ -1169,7 +1417,7 @@ const getAuditData = async (req: Request): Promise => { (value) => value && typeof value === 'string' && - value?.toLowerCase?.()?.includes(searchText?.toLowerCase()) + value?.toLowerCase?.()?.includes(searchText?.toLowerCase()), ); }); } @@ -1185,12 +1433,12 @@ const getAuditData = async (req: Request): Promise => { getLogMessage( srcFunc, `Error getting audit log data for module: ${moduleName}`, - error - ) + error, + ), ); throw new ExceptionFunction( error?.message || HTTP_TEXTS?.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES?.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES?.SERVER_ERROR, ); } }; @@ -1199,7 +1447,7 @@ const getAuditData = async (req: Request): Promise => { * with sequential tuid values */ const transformAndFlattenData = ( - data: any + data: any, ): Array<{ [key: string]: any; id: number }> => { try { const flattenedItems: Array<{ [key: string]: any }> = []; @@ -1299,13 +1547,13 @@ const getLogs = async (req: Request): Promise => { }; } const filterOptions = Array?.from( - new Set(logEntries?.map((log) => log?.level)) + new Set(logEntries?.map((log) => log?.level)), ); logEntries?.findIndex?.((log) => - log?.message?.includes('Starting audit process') + log?.message?.includes('Starting audit process'), ); logEntries?.findIndex?.((log) => - log?.message?.includes('Audit process completed') + log?.message?.includes('Audit process completed'), ); logEntries = logEntries?.slice?.(1, logEntries?.length - 2); if (filter !== 'all') { @@ -1320,7 +1568,7 @@ const getLogs = async (req: Request): Promise => { } if (searchText && searchText !== 'null') { logEntries = logEntries?.filter?.((log) => - matchesSearchText(log, searchText) + matchesSearchText(log, searchText), ); } const paginatedLogs = logEntries?.slice?.(startIndex, stopIndex) ?? []; @@ -1338,7 +1586,7 @@ const getLogs = async (req: Request): Promise => { logger.error(getLogMessage(srcFunc, HTTP_TEXTS?.LOGS_NOT_FOUND, error)); throw new ExceptionFunction( error?.message || HTTP_TEXTS?.INTERNAL_ERROR, - error?.statusCode || error?.status || HTTP_CODES?.SERVER_ERROR + error?.statusCode || error?.status || HTTP_CODES?.SERVER_ERROR, ); } }; @@ -1354,6 +1602,15 @@ export const createSourceLocales = async (req: Request) => { const projectId = req?.params?.projectId; const locales = req?.body?.locale; + console.info('🌐 createSourceLocales: Received request'); + console.info('🌐 createSourceLocales: projectId:', projectId); + console.info('🌐 createSourceLocales: locales:', locales); + console.info('🌐 createSourceLocales: locales type:', typeof locales); + console.info( + '🌐 createSourceLocales: locales length:', + Array.isArray(locales) ? locales.length : 'not an array', + ); + try { // Find the project with the specified projectId await ProjectModelLowdb?.read?.(); @@ -1365,7 +1622,12 @@ export const createSourceLocales = async (req: Request) => { ProjectModelLowdb?.update?.((data: any) => { data.projects[index].source_locales = locales; }); + console.info( + '🌐 createSourceLocales: Successfully stored locales for project:', + projectId, + ); } else { + console.info('🌐 createSourceLocales: Project not found:', projectId); logger.error(`Project with ID: ${projectId} not found`, { status: HTTP_CODES?.NOT_FOUND, message: HTTP_TEXTS?.INVALID_ID, @@ -1375,7 +1637,7 @@ export const createSourceLocales = async (req: Request) => { console.error( '🚀 ~ createSourceLocales ~ err:', err?.response?.data ?? err, - err + err, ); logger.warn('Bad Request', { status: HTTP_CODES?.BAD_REQUEST, @@ -1383,7 +1645,7 @@ export const createSourceLocales = async (req: Request) => { }); throw new ExceptionFunction( err?.message || HTTP_TEXTS.INTERNAL_ERROR, - err?.statusCode || err?.status || HTTP_CODES.SERVER_ERROR + err?.statusCode || err?.status || HTTP_CODES.SERVER_ERROR, ); } }; @@ -1422,7 +1684,7 @@ export const updateLocaleMapper = async (req: Request) => { console.error( '🚀 ~ updateLocaleMapper ~ err:', err?.response?.data ?? err, - err + err, ); logger.warn('Bad Request', { status: HTTP_CODES?.BAD_REQUEST, @@ -1430,7 +1692,7 @@ export const updateLocaleMapper = async (req: Request) => { }); throw new ExceptionFunction( err?.message || HTTP_TEXTS.INTERNAL_ERROR, - err?.statusCode || err?.status || HTTP_CODES.SERVER_ERROR + err?.statusCode || err?.status || HTTP_CODES.SERVER_ERROR, ); } }; diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index f2e6f7de1..8e306f55c 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -4,6 +4,7 @@ import { Request } from 'express'; import ProjectModelLowdb from '../models/project-lowdb.js'; import ContentTypesMapperModelLowdb from '../models/contentTypesMapper-lowdb.js'; import FieldMapperModel from '../models/FieldMapper.js'; +import { drupalService } from './drupal.service.js'; import { BadRequestError, diff --git a/api/src/utils/batch-processor.utils.ts b/api/src/utils/batch-processor.utils.ts new file mode 100644 index 000000000..26fb88a95 --- /dev/null +++ b/api/src/utils/batch-processor.utils.ts @@ -0,0 +1,119 @@ +/** + * Batch Processor Utility + * Handles large datasets by processing them in smaller batches to prevent resource exhaustion + */ + +export interface BatchProcessorOptions { + batchSize: number; + concurrency: number; + delayBetweenBatches?: number; +} + +export class BatchProcessor { + private options: BatchProcessorOptions; + + constructor(options: BatchProcessorOptions) { + this.options = { + delayBetweenBatches: 100, // Default 100ms delay + ...options, + }; + } + + /** + * Process large array in batches to prevent resource exhaustion + */ + async processBatches( + items: T[], + processor: (item: T) => Promise, + onBatchComplete?: ( + batchIndex: number, + totalBatches: number, + results: R[] + ) => void + ): Promise { + const { batchSize, concurrency, delayBetweenBatches } = this.options; + const totalBatches = Math.ceil(items.length / batchSize); + const allResults: R[] = []; + + for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) { + const startIndex = batchIndex * batchSize; + const endIndex = Math.min(startIndex + batchSize, items.length); + const batch = items.slice(startIndex, endIndex); + + // Process batch with controlled concurrency + const batchResults = await this.processBatchWithConcurrency( + batch, + processor, + concurrency + ); + allResults.push(...batchResults); + + // Callback for batch completion + if (onBatchComplete) { + onBatchComplete(batchIndex + 1, totalBatches, batchResults); + } + + // Delay between batches to allow file handles to close + if ( + batchIndex < totalBatches - 1 && + delayBetweenBatches && + delayBetweenBatches > 0 + ) { + await this.delay(delayBetweenBatches); + } + + // Force garbage collection if available + if (global.gc) { + global.gc(); + } + } + + return allResults; + } + + /** + * Process a single batch with controlled concurrency + */ + private async processBatchWithConcurrency( + batch: T[], + processor: (item: T) => Promise, + concurrency: number + ): Promise { + const results: R[] = []; + + for (let i = 0; i < batch.length; i += concurrency) { + const chunk = batch.slice(i, i + concurrency); + const chunkPromises = chunk.map(processor); + const chunkResults = await Promise.all(chunkPromises); + results.push(...chunkResults); + } + + return results; + } + + /** + * Delay utility + */ + private delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} + +/** + * Quick utility function for batch processing + */ +export async function processBatches( + items: T[], + processor: (item: T) => Promise, + options: BatchProcessorOptions, + onBatchComplete?: ( + batchIndex: number, + totalBatches: number, + results: R[] + ) => void +): Promise { + const batchProcessor = new BatchProcessor(options); + return batchProcessor.processBatches(items, processor, onBatchComplete); +} + +export default BatchProcessor; diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index 4d3f9c0c0..2d124f084 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -373,7 +373,7 @@ const saveAppMapper = async ({ marketPlacePath, data, fileName }: any) => { } } -const convertToSchemaFormate = ({ field, advanced = false, marketPlacePath, keyMapper }: any) => { +export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePath, keyMapper }: any) => { // Clean up field UID by removing ALL leading underscores const rawUid = field?.uid; const cleanedUid = sanitizeUid(rawUid); @@ -755,6 +755,38 @@ const convertToSchemaFormate = ({ field, advanced = false, marketPlacePath, keyM }; } + case 'taxonomy': { + // Build taxonomies array from field.taxonomies or field.advanced.taxonomies + const taxonomiesData = field?.taxonomies || field?.advanced?.taxonomies || []; + const taxonomiesArray = Array.isArray(taxonomiesData) + ? taxonomiesData.map((tax: any) => ({ + taxonomy_uid: typeof tax === 'string' ? tax : (tax?.taxonomy_uid || tax), + mandatory: field?.advanced?.mandatory ?? false, + multiple: field?.advanced?.multiple !== false, // Default true for taxonomies + non_localizable: field?.advanced?.nonLocalizable ?? false + })) + : []; + + return { + data_type: "taxonomy", + display_name: field?.title, + uid: cleanedUid, + taxonomies: taxonomiesArray, + field_metadata: { + description: field?.advanced?.description ?? '', + default_value: field?.advanced?.default_value ?? '' + }, + format: field?.advanced?.validationRegex ?? '', + error_messages: { + format: field?.advanced?.validationErrorMessage ?? '' + }, + mandatory: field?.advanced?.mandatory ?? false, + multiple: field?.advanced?.multiple !== false, // Default true for taxonomies + non_localizable: field?.advanced?.nonLocalizable ?? false, + unique: field?.advanced?.unique ?? false + }; + } + case 'html': { const htmlField: any = { "data_type": "text", diff --git a/api/src/utils/optimized-query-builder.utils.ts b/api/src/utils/optimized-query-builder.utils.ts new file mode 100644 index 000000000..bbc682084 --- /dev/null +++ b/api/src/utils/optimized-query-builder.utils.ts @@ -0,0 +1,479 @@ +import mysql from 'mysql2'; +import { getLogMessage } from './index.js'; +import customLogger from './custom-logger.utils.js'; + +/** + * Optimized Query Builder for Drupal Field Data + * Eliminates the 61-table JOIN limit by using sequential queries + */ + +interface DrupalFieldData { + field_name: string; + content_types: string; + type: string; + content_handler?: string; +} + +interface OptimizedQueryResult { + baseQuery: string; + countQuery: string; + fieldQueries: string[]; +} + +interface ColumnInfo { + COLUMN_NAME: string; +} + +interface TableExistsResult { + '1': number; +} + +interface QueryRow { + [key: string]: string | number | null | undefined; + nid?: number; + entity_id?: number; + field_name?: string; + field_value?: string | number | null; +} + +export class OptimizedQueryBuilder { + private connection: mysql.Connection; + private projectId: string; + private destinationStackId: string; + + constructor(connection: mysql.Connection, projectId: string, destinationStackId: string) { + this.connection = connection; + this.projectId = projectId; + this.destinationStackId = destinationStackId; + } + + /** + * Strategy 1: Sequential Field Queries (No JOINs) + * Fetch base node data first, then field data separately + */ + async generateSequentialQueries( + contentType: string, + fieldsForType: DrupalFieldData[] + ): Promise { + const srcFunc = 'generateSequentialQueries'; + + // 1. Base query for node data (no JOINs) + const baseQuery = ` + SELECT + node.nid, + node.title, + node.langcode, + node.created, + node.type, + users.name as author_name + FROM node_field_data node + LEFT JOIN users ON users.uid = node.uid + WHERE node.type = '${contentType}' + ORDER BY node.nid + `; + + // 2. Count query (simple, no JOINs) + const countQuery = ` + SELECT COUNT(DISTINCT node.nid) as countentry + FROM node_field_data node + WHERE node.type = '${contentType}' + `; + + // 3. Individual field queries (one per field table) + const fieldQueries: string[] = []; + + for (const field of fieldsForType) { + // Check if field table exists and get column structure + const fieldTableName = `node__${field.field_name}`; + + try { + // Get field columns dynamically + const columnQuery = ` + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = '${fieldTableName}' + AND COLUMN_NAME LIKE '${field.field_name}_%' + `; + + const [columns] = await this.connection.promise().query(columnQuery) as [ColumnInfo[], unknown]; + + if (columns.length > 0) { + // Build field-specific query + const fieldColumns = columns.map((col: ColumnInfo) => col.COLUMN_NAME).join(', '); + + const fieldQuery = ` + SELECT + entity_id, + ${fieldColumns} + FROM ${fieldTableName} + WHERE entity_id IN ( + SELECT nid FROM node_field_data WHERE type = '${contentType}' + ) + `; + + fieldQueries.push(fieldQuery); + } + } catch (error) { + console.warn(`Field table ${fieldTableName} not found or inaccessible:`, error); + } + } + + const message = getLogMessage( + srcFunc, + `Generated optimized queries for ${contentType}: 1 base + ${fieldQueries.length} field queries (0 JOINs)`, + {} + ); + await customLogger(this.projectId, this.destinationStackId, 'info', message); + + return { + baseQuery, + countQuery, + fieldQueries + }; + } + + /** + * Strategy 2: Batch Field Queries (Limited JOINs) + * Group fields into batches with max 15 JOINs each + */ + async generateBatchedQueries( + contentType: string, + fieldsForType: DrupalFieldData[], + batchSize: number = 15 + ): Promise<{ baseQuery: string; batchQueries: string[]; countQuery: string }> { + const srcFunc = 'generateBatchedQueries'; + + // Base query (always the same) + const baseQuery = ` + SELECT + node.nid, + node.title, + node.langcode, + node.created, + node.type + FROM node_field_data node + WHERE node.type = '${contentType}' + ORDER BY node.nid + `; + + // Count query + const countQuery = ` + SELECT COUNT(DISTINCT node.nid) as countentry + FROM node_field_data node + WHERE node.type = '${contentType}' + `; + + // Create batches of fields + const fieldBatches = this.createFieldBatches(fieldsForType, batchSize); + const batchQueries: string[] = []; + + for (let i = 0; i < fieldBatches.length; i++) { + const batch = fieldBatches[i]; + const validFields: string[] = []; + const joinClauses: string[] = []; + + // Validate each field in the batch + for (const field of batch) { + try { + const fieldTableName = `node__${field.field_name}`; + + // Check if table exists + const tableExistsQuery = ` + SELECT 1 FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = '${fieldTableName}' + `; + + const [tableExists] = await this.connection.promise().query(tableExistsQuery) as [TableExistsResult[], unknown]; + + if (tableExists.length > 0) { + // Get field columns + const columnQuery = ` + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = '${fieldTableName}' + AND COLUMN_NAME LIKE '${field.field_name}_%' + LIMIT 1 + `; + + const [columns] = await this.connection.promise().query(columnQuery) as [ColumnInfo[], unknown]; + + if (columns.length > 0) { + const columnName = columns[0].COLUMN_NAME; + validFields.push(`MAX(${fieldTableName}.${columnName}) as ${columnName}`); + joinClauses.push(`LEFT JOIN ${fieldTableName} ON ${fieldTableName}.entity_id = node.nid`); + } + } + } catch (error) { + console.warn(`Skipping field ${field.field_name}:`, error); + } + } + + if (validFields.length > 0) { + const batchQuery = ` + SELECT + node.nid, + ${validFields.join(',\n ')} + FROM node_field_data node + ${joinClauses.join('\n ')} + WHERE node.type = '${contentType}' + GROUP BY node.nid + ORDER BY node.nid + `; + + batchQueries.push(batchQuery); + } + } + + const message = getLogMessage( + srcFunc, + `Generated ${batchQueries.length} batched queries for ${contentType} (max ${batchSize} JOINs each)`, + {} + ); + await customLogger(this.projectId, this.destinationStackId, 'info', message); + + return { + baseQuery, + batchQueries, + countQuery + }; + } + + /** + * Strategy 3: Union-Based Field Queries + * Use UNION to combine field data without JOINs + */ + async generateUnionQueries( + contentType: string, + fieldsForType: DrupalFieldData[] + ): Promise<{ baseQuery: string; unionQuery: string; countQuery: string }> { + const srcFunc = 'generateUnionQueries'; + + // Base query + const baseQuery = ` + SELECT + node.nid, + node.title, + node.langcode, + node.created, + node.type + FROM node_field_data node + WHERE node.type = '${contentType}' + ORDER BY node.nid + `; + + // Count query + const countQuery = ` + SELECT COUNT(DISTINCT node.nid) as countentry + FROM node_field_data node + WHERE node.type = '${contentType}' + `; + + // Union query for all field data + const unionParts: string[] = []; + + for (const field of fieldsForType) { + const fieldTableName = `node__${field.field_name}`; + + try { + // Get field columns + const columnQuery = ` + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = '${fieldTableName}' + AND COLUMN_NAME LIKE '${field.field_name}_%' + LIMIT 1 + `; + + const [columns] = await this.connection.promise().query(columnQuery) as [ColumnInfo[], unknown]; + + if (columns.length > 0) { + const columnName = columns[0].COLUMN_NAME; + + unionParts.push(` + SELECT + entity_id as nid, + '${field.field_name}' as field_name, + ${columnName} as field_value + FROM ${fieldTableName} + WHERE entity_id IN ( + SELECT nid FROM node_field_data WHERE type = '${contentType}' + ) + `); + } + } catch (error) { + console.warn(`Skipping field ${field.field_name} in union:`, error); + } + } + + const unionQuery = unionParts.length > 0 ? unionParts.join('\nUNION ALL\n') : ''; + + const message = getLogMessage( + srcFunc, + `Generated union query for ${contentType} with ${unionParts.length} field parts`, + {} + ); + await customLogger(this.projectId, this.destinationStackId, 'info', message); + + return { + baseQuery, + unionQuery, + countQuery + }; + } + + /** + * Execute optimized queries and merge results + */ + async executeOptimizedQueries( + strategy: 'sequential' | 'batched' | 'union', + contentType: string, + fieldsForType: DrupalFieldData[], + batchSize: number = 15 + ): Promise { + const srcFunc = 'executeOptimizedQueries'; + + try { + switch (strategy) { + case 'sequential': + return await this.executeSequentialQueries(contentType, fieldsForType); + + case 'batched': + return await this.executeBatchedQueries(contentType, fieldsForType, batchSize); + + case 'union': + return await this.executeUnionQueries(contentType, fieldsForType); + + default: + throw new Error(`Unknown strategy: ${strategy}`); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + const message = getLogMessage( + srcFunc, + `Failed to execute optimized queries for ${contentType}: ${errorMessage}`, + {}, + error + ); + await customLogger(this.projectId, this.destinationStackId, 'error', message); + throw error; + } + } + + private async executeSequentialQueries(contentType: string, fieldsForType: DrupalFieldData[]): Promise { + const { baseQuery, fieldQueries } = await this.generateSequentialQueries(contentType, fieldsForType); + + // Execute base query + const [baseResults] = await this.connection.promise().query(baseQuery) as [QueryRow[], unknown]; + + // Create result map + const resultMap = new Map(); + baseResults.forEach((row: QueryRow) => { + if (row.nid) { + resultMap.set(row.nid, { ...row }); + } + }); + + // Execute field queries and merge results + for (const fieldQuery of fieldQueries) { + const [fieldResults] = await this.connection.promise().query(fieldQuery) as [QueryRow[], unknown]; + + fieldResults.forEach((fieldRow: QueryRow) => { + const nid = fieldRow.entity_id; + if (nid && resultMap.has(nid)) { + const existingRow = resultMap.get(nid); + if (existingRow) { + // Merge field data (exclude entity_id) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { entity_id: _entityId, ...fieldData } = fieldRow; + Object.assign(existingRow, fieldData); + } + } + }); + } + + return Array.from(resultMap.values()); + } + + private async executeBatchedQueries(contentType: string, fieldsForType: DrupalFieldData[], batchSize: number): Promise { + const { baseQuery, batchQueries } = await this.generateBatchedQueries(contentType, fieldsForType, batchSize); + + // Execute base query + const [baseResults] = await this.connection.promise().query(baseQuery) as [QueryRow[], unknown]; + + // Create result map + const resultMap = new Map(); + baseResults.forEach((row: QueryRow) => { + if (row.nid) { + resultMap.set(row.nid, { ...row }); + } + }); + + // Execute batch queries and merge results + for (const batchQuery of batchQueries) { + const [batchResults] = await this.connection.promise().query(batchQuery) as [QueryRow[], unknown]; + + batchResults.forEach((batchRow: QueryRow) => { + const nid = batchRow.nid; + if (nid && resultMap.has(nid)) { + const existingRow = resultMap.get(nid); + if (existingRow) { + // Merge batch data (exclude nid) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { nid: _nid, ...batchData } = batchRow; + Object.assign(existingRow, batchData); + } + } + }); + } + + return Array.from(resultMap.values()); + } + + private async executeUnionQueries(contentType: string, fieldsForType: DrupalFieldData[]): Promise { + const { baseQuery, unionQuery } = await this.generateUnionQueries(contentType, fieldsForType); + + // Execute base query + const [baseResults] = await this.connection.promise().query(baseQuery) as [QueryRow[], unknown]; + + // Create result map + const resultMap = new Map(); + baseResults.forEach((row: QueryRow) => { + if (row.nid) { + resultMap.set(row.nid, { ...row }); + } + }); + + // Execute union query if it exists + if (unionQuery) { + const [unionResults] = await this.connection.promise().query(unionQuery) as [QueryRow[], unknown]; + + // Group union results by nid + unionResults.forEach((unionRow: QueryRow) => { + const nid = unionRow.nid; + const fieldName = unionRow.field_name; + if (nid && fieldName && resultMap.has(nid)) { + const existingRow = resultMap.get(nid); + if (existingRow) { + existingRow[fieldName] = unionRow.field_value; + } + } + }); + } + + return Array.from(resultMap.values()); + } + + private createFieldBatches(fields: DrupalFieldData[], batchSize: number): DrupalFieldData[][] { + const batches: DrupalFieldData[][] = []; + for (let i = 0; i < fields.length; i += batchSize) { + batches.push(fields.slice(i, i + batchSize)); + } + return batches; + } +} + +export default OptimizedQueryBuilder; diff --git a/ui/package-lock.json b/ui/package-lock.json index ce5a1d495..73f1f6e4e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,8 +8,8 @@ "name": "migration-v2-ui", "version": "0.1.0", "dependencies": { - "@contentstack/json-rte-serializer": "^3.0.4", - "@contentstack/venus-components": "^3.0.2", + "@contentstack/json-rte-serializer": "^3.0.5", + "@contentstack/venus-components": "^3.0.3", "@reduxjs/toolkit": "^2.8.2", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^13.4.0", @@ -1937,12 +1937,13 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, "node_modules/@contentstack/json-rte-serializer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-3.0.4.tgz", - "integrity": "sha512-dvJYLApd7O/aw2tufA3810LwDRk0LHO53V4tBY5kYRoJkpiuZZOEmP/vUD7gh8lP4mT5RBoL4f6s9gBa6uvWzQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-3.0.5.tgz", + "integrity": "sha512-WWntS1vO8Hzacf5YU14w1my7oivcwFs9l0gqaq2r5tPJ/5bWtgIW8zOVO5teMX63pMHWCUy+69FmD/xMoGFIsw==", + "license": "MIT", "dependencies": { "array-flat-polyfill": "^1.0.1", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lodash.clonedeep": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isempty": "^4.4.0", @@ -1956,9 +1957,10 @@ } }, "node_modules/@contentstack/venus-components": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@contentstack/venus-components/-/venus-components-3.0.2.tgz", - "integrity": "sha512-8FBw066+mmzQE3YZInrjMKEWmf7z2xMg3DxznlsHORQQbQlSJW2AZhDQhUabug95bF4uzrkkUrWfrelBCzOK6A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@contentstack/venus-components/-/venus-components-3.0.3.tgz", + "integrity": "sha512-M7E66+U7lKxKq8Kuks4zY+KPLpqP8sge1xhDnd7S8cI/F56NEqqLhcD/tVdyFf1Ox7EFlfs9VjwbZg59xuPCaQ==", + "license": "MIT", "dependencies": { "@emotion/css": "^11.1.3", "@mdx-js/react": "^3.1.0", @@ -1987,7 +1989,7 @@ "react-ace": "^9.1.4", "react-beautiful-dnd": "^13.0.0", "react-color": "^2.19.3", - "react-datepicker": "npm:@contentstack/react-datepicker@^4.25.5", + "react-datepicker": "npm:@contentstack/react-datepicker@^4.25.7", "react-dnd-11": "npm:react-dnd@^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dropzone": "^11.2.4", @@ -1996,7 +1998,8 @@ "react-mentions": "^4.4.7", "react-modal": "^3.11.2", "react-redux": "^7.2.1", - "react-select": "npm:@contentstack/react-select@3.2.5", + "react-responsive": "^10.0.1", + "react-select": "npm:@contentstack/react-select@^3.2.8", "react-select-async-paginate": "^0.4.0", "react-simple-code-editor": "^0.11.0", "react-sortable-hoc": "^1.11.0", @@ -2004,7 +2007,7 @@ "react-table": "^7.5.0", "react-test-renderer": "^18.3.1", "react-tiktok": "^1.0.0", - "react-toastify": "npm:@contentstack/react-toastify@^6.1.5", + "react-toastify": "npm:@contentstack/react-toastify@6.1.5", "react-treebeard": "^3.2.4", "react-virtualized": "^9.22.5", "react-virtualized-auto-sizer": "1.0.5", @@ -2230,11 +2233,12 @@ }, "node_modules/@contentstack/venus-components/node_modules/react-select": { "name": "@contentstack/react-select", - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@contentstack/react-select/-/react-select-3.2.5.tgz", - "integrity": "sha512-Q/IE5+BGh4X1/1vFqEaNXdB42JYpewdTs2OyFKeEZ9FemgUWMe/O783cyhrDbXfor4s6J41o9/SLmkzfDD7MjQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@contentstack/react-select/-/react-select-3.2.8.tgz", + "integrity": "sha512-ffc5PKxXgOS0oc1UDUIhZNFZ+XNymw9sk9USRaemzFlCEI5ywl68uJRICiRySO1YMxZIN96aG0cUXTXKfXE4qg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "7.4.4", + "@babel/runtime": "7.26.10", "@emotion/cache": "10.0.9", "@emotion/core": "10.0.9", "@emotion/css": "10.0.9", @@ -7492,6 +7496,12 @@ "node": ">=10" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", + "license": "BSD" + }, "node_modules/css-minimizer-webpack-plugin": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", @@ -10934,6 +10944,12 @@ "node": ">=10.17.0" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -13115,9 +13131,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.17.22", @@ -13275,6 +13292,15 @@ "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", "peer": true }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "license": "MIT", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", @@ -16161,6 +16187,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", + "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-router": { "version": "7.12.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", @@ -17513,6 +17557,12 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", diff --git a/ui/package.json b/ui/package.json index 1d59aaa89..0cc31f786 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { - "@contentstack/json-rte-serializer": "^3.0.4", - "@contentstack/venus-components": "^3.0.2", + "@contentstack/json-rte-serializer": "^3.0.5", + "@contentstack/venus-components": "^3.0.3", "@reduxjs/toolkit": "^2.8.2", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^13.4.0", @@ -77,4 +77,4 @@ "serialize-javascript": ">=6.0.2", "@babel/runtime": ">=7.26.10" } -} \ No newline at end of file +} diff --git a/ui/src/cmsData/legacyCms.json b/ui/src/cmsData/legacyCms.json index c5c4c327e..88db540e4 100644 --- a/ui/src/cmsData/legacyCms.json +++ b/ui/src/cmsData/legacyCms.json @@ -94,9 +94,6 @@ }, { "cms_id": "drupal", - "_metadata": { - "uid": "csb96887a2cfee9e8c" - }, "title": "Drupal", "description": "", "group_name": "lightning", @@ -106,61 +103,10 @@ }, "parent": "Drupal", "isactive": true, - "allowed_file_formats": [ - { - "fileformat_id": "zip", - "_metadata": { - "uid": "cs5d9c8914dc21ea80" - }, - "title": "Zip", - "description": "", - "group_name": "zip", - "isactive": true - } - ] - }, - { - "cms_id": "drupal v7", - "title": "Drupal v7", - "description": "", - "group_name": "lightning", - "doc_url": { - "title": "https://www.drupal.org/", - "href": "https://www.drupal.org/" - }, - "parent": "Drupal", - "isactive": true, - "allowed_file_formats": [ - { - "fileformat_id": "sql", - "title": "Sql", - "description": "", - "group_name": "sql", - "isactive": true, - "_metadata": { - "uid": "csceba83e388748bf1" - } - } - ], - "_metadata": { - "uid": "cs88cc83ef30625782" - } - }, - { - "cms_id": "drupal v8+", - "title": "Drupal v8+", - "description": "", - "group_name": "lightning", - "doc_url": { - "title": "https://www.drupal.org/", - "href": "https://www.drupal.org/" - }, - "parent": "Drupal", - "isactive": true, "allowed_file_formats": [ { "fileformat_id": "sql", - "title": "Sql", + "title": "ApiTokens", "description": "", "group_name": "sql", "isactive": true, @@ -377,4 +323,4 @@ "restricted_keyword_checkbox_text": "Please acknowledge that you have referred to the Contentstack restricted keywords", "affix_cta": "Continue", "file_format_cta": "Continue" -} \ No newline at end of file +} diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 3df5f08cc..4d0f7cc5f 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -11,11 +11,12 @@ import { Icon, Select, Radio, - Button + Button, + InstructionText, } from '@contentstack/venus-components'; // Service -import { getContentTypes } from '../../services/api/migration.service'; +import { getContentTypes, getExistingTaxonomies } from '../../services/api/migration.service'; // Utilities import { validateArray } from '../../utilities/functions'; @@ -32,6 +33,13 @@ interface ContentTypeOption { value: string; } +interface Taxonomy { + uid: string; + name: string; + description?: string; + source?: string; +} + /** * Component for displaying advanced properties. * @param props - The schema properties. @@ -67,7 +75,12 @@ const AdvancePropertise = (props: SchemaProps) => { value: item })); - const referencedItems = props?.data?.refrenceTo?.map?.((item: string) => ({ + const referenceToUids = + (Array.isArray(props?.data?.referenceTo) && props?.data?.referenceTo) || + (Array.isArray(props?.data?.refrenceTo) && props?.data?.refrenceTo) || + []; + + const referencedItems = referenceToUids?.map?.((item: string) => ({ label: item, value: item })); @@ -81,6 +94,11 @@ const AdvancePropertise = (props: SchemaProps) => { const [referencedCT, setReferencedCT] = useState( referencedItems || null ); + const [sourceTaxonomies, setSourceTaxonomies] = useState([]); + const [destinationTaxonomies, setDestinationTaxonomies] = useState([]); + const [referencedTaxonomies, setReferencedTaxonomies] = + useState(referencedItems || null); + const [isTaxonomiesLoading, setIsTaxonomiesLoading] = useState(false); const [showOptions, setShowOptions] = useState>({}); const [showIcon, setShowIcon] = useState(); const filterRef = useRef(null); @@ -90,19 +108,33 @@ const AdvancePropertise = (props: SchemaProps) => { const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); + const getMappedReferenceUids = (): string[] => { + const correct = props?.data?.referenceTo; + const legacy = props?.data?.refrenceTo; + return (Array.isArray(correct) ? correct : Array.isArray(legacy) ? legacy : []) as string[]; + }; + + const getMappedTaxonomyUids = (): string[] => { + // Prefer currently saved UI selections (stored into advanced settings) + const fromAdvanced = props?.value?.referenedItems; + if (Array.isArray(fromAdvanced) && fromAdvanced.length > 0) return fromAdvanced; + return getMappedReferenceUids(); + }; + useEffect(() => { - if (props?.data?.refrenceTo && Array.isArray(props?.data?.refrenceTo)) { - const updatedReferencedItems = props?.data?.refrenceTo.map((item: string) => ({ + const mapped = getMappedReferenceUids(); + if (mapped.length > 0) { + const updatedReferencedItems = mapped.map((item: string) => ({ label: item, value: item })); setReferencedCT(updatedReferencedItems); setToggleStates((prevStates) => ({ ...prevStates, - referenedItems: props?.data?.refrenceTo + referenedItems: mapped })); } - }, [props?.data?.refrenceTo]); + }, [props?.data?.referenceTo, props?.data?.refrenceTo]); useEffect(() => { const defaultIndex = toggleStates?.option?.findIndex( (item: optionsType) => toggleStates?.default_value === item?.key @@ -113,22 +145,169 @@ const AdvancePropertise = (props: SchemaProps) => { } }, []); useEffect(() => { + // IMPORTANT: When opening Advanced Settings, `projectId` may be empty on first render. + // If we call APIs with an empty projectId, we’ll get empty lists and the taxonomy dropdown will show no options. + if (!props?.projectId) { + console.warn('⚠️ [AdvancePropertise] projectId is empty, skipping fetch'); + return; + } + fetchContentTypes(''); - }, []); + + // Only fetch taxonomies if this is a Taxonomy field + if (props?.fieldtype === 'Taxonomy') { + console.info('🏷️ Taxonomy field detected, fetching taxonomies...'); + fetchTaxonomies(); + + // Initialize referencedTaxonomies from existing data if available + const oldTaxonomies = props?.data?.advanced?.taxonomies || []; + const newTaxonomies = getMappedTaxonomyUids(); + const allTaxonomyUIDs = Array.from( + new Set([ + ...oldTaxonomies.map((t: { taxonomy_uid?: string } | string) => + typeof t === 'string' ? t : t.taxonomy_uid || '' + ), + ...newTaxonomies, + ]) + ).filter(Boolean); + + if (allTaxonomyUIDs.length > 0) { + setReferencedTaxonomies( + allTaxonomyUIDs.map((uid: string) => ({ label: uid, value: uid })) + ); + } + } + }, [props?.projectId, props?.fieldtype]); + + // Update referenced CT when content types are fetched (only for Reference fields) + useEffect(() => { + if (props?.fieldtype === 'Reference' && contentTypes.length > 0) { + // Merge old (upload-api) and new (UI) selections + // Reference fields can use embedObjects OR reference_to + const oldReferences = props?.data?.advanced?.embedObjects || props?.data?.advanced?.reference_to || []; + const newReferences = getMappedReferenceUids(); + const allReferenceUIDs = Array.from(new Set([...oldReferences, ...newReferences])); + + if (allReferenceUIDs.length > 0) { + const matchedCTs = allReferenceUIDs + .map((uid: string) => { + const ct = contentTypes.find((c: ContentType) => c.contentstackUid === uid); + return ct ? { label: ct.contentstackTitle, value: ct.contentstackUid } : null; + }) + .filter(Boolean) as ContentTypeOption[]; + + if (matchedCTs.length > 0) { + setReferencedCT(matchedCTs); + } + } + } + }, [contentTypes, props?.data?.referenceTo, props?.data?.refrenceTo, props?.data?.advanced, props?.fieldtype]); + + // Update referenced taxonomies when taxonomies are fetched (only for Taxonomy fields) + useEffect(() => { + if (props?.fieldtype === 'Taxonomy') { + const allTaxonomies = [...sourceTaxonomies, ...destinationTaxonomies]; + + // Only proceed if we have taxonomies loaded OR if we have existing taxonomy data to match + if (allTaxonomies.length > 0 || props?.data?.advanced?.taxonomies || getMappedTaxonomyUids().length > 0) { + // Merge old (upload-api) and new (UI) selections + const oldTaxonomies = (props?.data?.advanced?.taxonomies || []).map((t: { taxonomy_uid?: string } | string) => (typeof t === 'string' ? t : t.taxonomy_uid || '')); + const newTaxonomies = getMappedTaxonomyUids(); + const allTaxonomyUIDs = Array.from(new Set([...oldTaxonomies, ...newTaxonomies])); + + if (allTaxonomyUIDs.length > 0 && allTaxonomies.length > 0) { + // Match UIDs with loaded taxonomies + const matchedTaxonomies = allTaxonomyUIDs + .map((uid: string) => { + const taxonomy = allTaxonomies.find((t: Taxonomy) => t.uid === uid); + return taxonomy ? { label: taxonomy.name || taxonomy.uid, value: taxonomy.uid } : null; + }) + .filter(Boolean) as ContentTypeOption[]; + + if (matchedTaxonomies.length > 0) { + setReferencedTaxonomies(matchedTaxonomies); + } else { + // If no matches found but we have UIDs, create options from UIDs (fallback) + const fallbackOptions = allTaxonomyUIDs.map((uid: string) => ({ + label: uid, + value: uid + })); + setReferencedTaxonomies(fallbackOptions); + } + } else if (allTaxonomyUIDs.length > 0 && allTaxonomies.length === 0) { + // Taxonomies not loaded yet, but we have UIDs - create fallback options + const fallbackOptions = allTaxonomyUIDs.map((uid: string) => ({ + label: uid, + value: uid + })); + setReferencedTaxonomies(fallbackOptions); + } else { + // No existing taxonomies, clear the selection + setReferencedTaxonomies(null); + } + } + } + }, [sourceTaxonomies, destinationTaxonomies, props?.data?.referenceTo, props?.data?.refrenceTo, props?.data?.advanced, props?.fieldtype]); + /** * Fetches the content types list. * @param searchText - The search text. */ const fetchContentTypes = async (searchText: string) => { try { - const { data } = await getContentTypes(props?.projectId ?? '', 0, 5000, searchText || ''); //org id will always present - - setContentTypes(data?.contentTypes); + console.info('📦 [AdvancePropertise] Fetching content types for projectId:', props?.projectId); + const response = await getContentTypes(props?.projectId ?? '', 0, 5000, searchText || ''); //org id will always present + const data = response?.data; + + if (data?.contentTypes && data.contentTypes.length > 0) { + setContentTypes(data.contentTypes); + } else { + console.warn('⚠️ [AdvancePropertise] No content types in response'); + } } catch (error) { + console.error('❌ [AdvancePropertise] Error fetching content types:', error); return error; } }; + /** + * Fetches taxonomies from both source CMS and destination stack. + */ + const fetchTaxonomies = async () => { + setIsTaxonomiesLoading(true); + try { + const response = await getExistingTaxonomies(props?.projectId ?? ''); + + // Handle both success and error responses from axios + const responseData = response?.data || response; + + // Check if response has error status + if (response?.status && response.status >= 400) { + console.warn('⚠️ Taxonomy API returned error status:', response.status); + console.warn('⚠️ Error message:', responseData?.data || responseData?.message); + // Still try to set empty arrays so UI doesn't break + setSourceTaxonomies([]); + setDestinationTaxonomies([]); + } else { + // Success case + setSourceTaxonomies(responseData?.sourceTaxonomies || []); + setDestinationTaxonomies(responseData?.destinationTaxonomies || []); + + } + } catch (error: any) { + console.error('❌ Error fetching taxonomies:', error); + console.error('❌ Error details:', { + message: error?.message, + response: error?.response, + data: error?.response?.data + }); + setSourceTaxonomies([]); + setDestinationTaxonomies([]); + } finally { + setIsTaxonomiesLoading(false); + } + }; + /** * Handles the change event for input fields. * @param field - The field name. @@ -209,7 +388,6 @@ const AdvancePropertise = (props: SchemaProps) => { ...toggleStates, [field]: value }; - props?.updateFieldSettings( props?.rowId, { @@ -374,13 +552,14 @@ const AdvancePropertise = (props: SchemaProps) => { // Option for content types const contentTypesList = contentTypes?.filter((ct: ContentType) => ct?.type === 'content_type'); + // Generate options for the Reference content type dropdown const option = validateArray(contentTypesList) - ? contentTypesList?.map((option: ContentType) => ({ - label: option?.contentstackTitle, - value: option?.contentstackUid + ? contentTypesList?.map((ct: ContentType) => ({ + label: ct?.contentstackTitle, + value: ct?.contentstackUid })) - : [{ label: contentTypesList, value: contentTypesList }]; - + : []; // Return empty array instead of invalid option when no content types + return ( <> { )} + {props?.fieldtype === 'Taxonomy' && ( + + + Referenced Taxonomies + +