From 07653734618397a66f9284a7892f0f687a57832f Mon Sep 17 00:00:00 2001 From: shobhit-cstk Date: Tue, 21 Apr 2026 13:00:58 +0530 Subject: [PATCH 1/3] feat(contentful): taxonomy migration, locale resolution, tests and config - Extract and map Contentful taxonomies from export; upload-api and mapper integration - Contentful service: field/widget helpers, taxonomy metadata locale resolution (mapper + sys.locale) - API: Vitest thresholds; migration and user unit tests (SSO, createTaxonomy mocks) - app.json placeholder updates and related fixes --- api/package-lock.json | 1059 +++++++++-------- api/package.json | 8 +- api/src/services/contentMapper.service.ts | 48 + api/src/services/contentful.service.ts | 212 +++- .../services/contentful/taxonomy.service.ts | 237 ++++ api/src/services/migration.service.ts | 10 + api/src/services/wordpress.service.ts | 6 +- api/src/utils/content-type-creator.utils.ts | 169 ++- .../unit/services/migration.service.test.ts | 1 + api/tests/unit/services/user.service.test.ts | 129 +- api/vitest.config.ts | 6 +- app.json | 28 +- ui/package-lock.json | 138 +-- ui/package.json | 6 +- ui/src/components/AdvancePropertise/index.tsx | 74 +- .../__tests__/groupSchema.utils.test.ts | 44 + .../ContentMapper/groupSchema.utils.ts | 22 + ui/src/components/ContentMapper/index.scss | 6 + ui/src/components/ContentMapper/index.tsx | 455 +++---- upload-api/migration-aem/package-lock.json | 19 + upload-api/migration-contentful/index.js | 4 +- .../libs/createInitialMapper.js | 62 +- .../libs/extractTaxonomy.js | 59 + .../migration-wordpress/libs/extractItems.ts | 22 +- .../libs/extractTaxonomy.ts | 2 + .../migration-wordpress/libs/schemaMapper.ts | 39 +- upload-api/src/config/index.ts | 2 +- upload-api/src/services/contentful/index.ts | 33 +- .../migration-wordpress/schemaMapper.test.ts | 6 +- 29 files changed, 1961 insertions(+), 945 deletions(-) create mode 100644 api/src/services/contentful/taxonomy.service.ts create mode 100644 upload-api/migration-contentful/libs/extractTaxonomy.js diff --git a/api/package-lock.json b/api/package-lock.json index 943e44a8c..e219bca0d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -336,29 +336,29 @@ } }, "node_modules/@contentstack/cli": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli/-/cli-1.60.0.tgz", - "integrity": "sha512-Zr5Dl93NO4CpAtpX+pudMETg4r7TePUCi2wZWaukgaxQ8WfigjewM04yAy07nV/RFKKcstI3EBOpWkXee/X1jA==", + "version": "1.60.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli/-/cli-1.60.1.tgz", + "integrity": "sha512-tH8WJ1khzaHjLuvtGAAeAoqPOzT5EkTM20dLx3sOKvm/4Izj+gKffcbsEwtlVtI45UO5dJMiGDHLYnxGij9Rgw==", "license": "MIT", "dependencies": { - "@contentstack/cli-audit": "~1.19.0", + "@contentstack/cli-audit": "~1.19.1", "@contentstack/cli-auth": "~1.8.0", "@contentstack/cli-cm-bootstrap": "~1.19.0", - "@contentstack/cli-cm-branches": "~1.7.0", - "@contentstack/cli-cm-bulk-publish": "~1.11.0", - "@contentstack/cli-cm-clone": "~1.21.0", + "@contentstack/cli-cm-branches": "~1.7.1", + "@contentstack/cli-cm-bulk-publish": "~1.11.1", + "@contentstack/cli-cm-clone": "~1.21.1", "@contentstack/cli-cm-export": "~1.24.0", "@contentstack/cli-cm-export-to-csv": "~1.12.0", "@contentstack/cli-cm-import": "~1.32.0", - "@contentstack/cli-cm-import-setup": "~1.8.0", + "@contentstack/cli-cm-import-setup": "~1.8.1", "@contentstack/cli-cm-migrate-rte": "~1.6.4", "@contentstack/cli-cm-seed": "~1.15.0", "@contentstack/cli-command": "~1.8.0", - "@contentstack/cli-config": "~1.20.0", - "@contentstack/cli-launch": "^1.9.6", + "@contentstack/cli-config": "~1.20.1", + "@contentstack/cli-launch": "^1.9.7", "@contentstack/cli-migration": "~1.12.0", - "@contentstack/cli-utilities": "~1.18.0", - "@contentstack/cli-variants": "~1.4.0", + "@contentstack/cli-utilities": "~1.18.1", + "@contentstack/cli-variants": "~1.4.1", "@contentstack/management": "~1.27.5", "@oclif/core": "^4.8.3", "@oclif/plugin-help": "^6.2.28", @@ -383,9 +383,9 @@ } }, "node_modules/@contentstack/cli-audit": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-audit/-/cli-audit-1.19.0.tgz", - "integrity": "sha512-NwET9cBeCmM5tM3faJ8wG4YMX9JrGYo7lYE7WQhmVUOhC/7bbYKPpWb6Jmy3JMvzP9YSwmSrztEUdwotB4F6WQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-audit/-/cli-audit-1.19.1.tgz", + "integrity": "sha512-k3bu/NLXGu7/ntMtWh/kd4smytQ44Z4wlixCajoPxKR2k1A/4OaZD6n1WQ7TcJ7biosNU9Pl83PO4oKMdxrEfA==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -396,7 +396,7 @@ "chalk": "^4.1.2", "fast-csv": "^4.3.6", "fs-extra": "^11.3.0", - "lodash": "^4.17.23", + "lodash": "4.18.1", "uuid": "^9.0.1", "winston": "^3.17.0" }, @@ -456,9 +456,9 @@ } }, "node_modules/@contentstack/cli-cm-branches": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-branches/-/cli-cm-branches-1.7.0.tgz", - "integrity": "sha512-5uCqqzB1mGlHX8Ac3+3bteUuQ0CNBHmAZ6mRj9D6/DPTW7spbOHkKWksMyKuSrbkOTj57otjZUpcWvZobuW5kg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-branches/-/cli-cm-branches-1.7.1.tgz", + "integrity": "sha512-IUU/Hs7/LXH/vGRkqGf+CQhDSMFLLa0KqYLOi+LneBU/irrQSC6ue+/oaGVJw4i59Wy/rV5U3buCreAKlSzd2Q==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -467,16 +467,16 @@ "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", "just-diff": "^6.0.2", - "lodash": "^4.17.23" + "lodash": "4.18.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@contentstack/cli-cm-bulk-publish": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bulk-publish/-/cli-cm-bulk-publish-1.11.0.tgz", - "integrity": "sha512-oQd1se/3qa18exmuIFvBL+zH3wuSWHuS4eV1nAyYA6pVl8HU4hqcCj1ygHdMdUx7ZLckESgZjeOfeglbUJg3zQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-bulk-publish/-/cli-cm-bulk-publish-1.11.1.tgz", + "integrity": "sha512-0mjpOfzSMX/vJFXiLhQwgefCuVuiqBH2e/8BR2ks4a6/8ISquIcACkJ7Zosh+8LjiAxSOSJtCtykn6nJIaV2EA==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -487,7 +487,7 @@ "chalk": "^4.1.2", "dotenv": "^16.5.0", "inquirer": "8.2.7", - "lodash": "^4.17.23", + "lodash": "4.18.1", "winston": "^3.17.0" }, "engines": { @@ -495,9 +495,9 @@ } }, "node_modules/@contentstack/cli-cm-clone": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-clone/-/cli-cm-clone-1.21.0.tgz", - "integrity": "sha512-StImHRgX+9iGjdnFOM3AAdU7+ccOrMk3mr1p0oaIlqPLu5QDrMqNZialhuV0BjZ0BXPhLm93bpXuEKg0cCK+5w==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-clone/-/cli-cm-clone-1.21.1.tgz", + "integrity": "sha512-s/UJhEtYqjPKhLbys0eVoDz+yYgESSki5Z+4jQr/PaGcoW3GM4hDro1d+c/rG3/KhKy4VdhnZZcPVDdezRSsvQ==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -509,7 +509,7 @@ "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", "inquirer": "8.2.7", - "lodash": "^4.17.23", + "lodash": "4.18.1", "merge": "^2.1.1", "ora": "^5.4.1", "prompt": "^1.3.0", @@ -520,9 +520,9 @@ } }, "node_modules/@contentstack/cli-cm-export": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export/-/cli-cm-export-1.24.0.tgz", - "integrity": "sha512-eRdSt3z08W7MNH1CnXhZSwJ4o/jodYIBGv+f1G3Zs3ZrthNToDQBR7018gmVjjoZYXIRMvaHo8Nw8eZgbYtchA==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-export/-/cli-cm-export-1.24.1.tgz", + "integrity": "sha512-zTaun28JcIjT88NCi+p1gZabZVkJO6bQLOBs+QGHPOUxRBn5t++AYOuomm7iA1ZJAzsfMN5FF1lWRuRZ2qPf2A==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -533,7 +533,7 @@ "big-json": "^3.2.0", "bluebird": "^3.7.2", "chalk": "^4.1.2", - "lodash": "^4.17.23", + "lodash": "4.18.1", "merge": "^2.1.1", "mkdirp": "^1.0.4", "progress-stream": "^2.0.0", @@ -576,9 +576,9 @@ } }, "node_modules/@contentstack/cli-cm-import": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import/-/cli-cm-import-1.32.0.tgz", - "integrity": "sha512-S+WVN+b5kMa+VaaZSM0oaaYsR3SGTOjlJlvzeFe0Kykkxau6DKbEMvDE2esmsFn21HpUxjLb58//7KS5BhedhQ==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import/-/cli-cm-import-1.32.1.tgz", + "integrity": "sha512-WPdKpFq2iYAoCXrMp9ZlahgzhuHbovQqmQSTxvxCBoQZPt8YX2jr8fu6wgzGEhvI8ra4Tpx/br0n2vEMBbSQSg==", "license": "MIT", "dependencies": { "@contentstack/cli-audit": "~1.19.0", @@ -591,7 +591,7 @@ "chalk": "^4.1.2", "debug": "^4.4.3", "fs-extra": "^11.3.3", - "lodash": "^4.17.23", + "lodash": "4.18.1", "marked": "^4.3.0", "merge": "^2.1.1", "mkdirp": "^1.0.4", @@ -604,9 +604,9 @@ } }, "node_modules/@contentstack/cli-cm-import-setup": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import-setup/-/cli-cm-import-setup-1.8.0.tgz", - "integrity": "sha512-h4vTeFNfKF09NTbJmki1p4EYEh9KxMJn3G7U3CJ7IurcxI6ccNxJuEFhsCHYGwX+UwmZ8TcJMqmbsHwAMQjP0Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-cm-import-setup/-/cli-cm-import-setup-1.8.1.tgz", + "integrity": "sha512-xffwa0MXGH8dk+FGOefETnv2LOOyAwKPwG9+QLoLqLxoCRadiMKsTcaw8ejZcB7i1NXbpEp4aWtOkBLOwEc9KA==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -615,7 +615,7 @@ "big-json": "^3.2.0", "chalk": "^4.1.2", "fs-extra": "^11.3.0", - "lodash": "^4.17.23", + "lodash": "4.18.1", "merge": "^2.1.1", "mkdirp": "^1.0.4", "winston": "^3.17.0" @@ -1019,9 +1019,9 @@ } }, "node_modules/@contentstack/cli-config": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-config/-/cli-config-1.20.0.tgz", - "integrity": "sha512-WURtexv9+lQWNPriWvaakHS+9SmGoO3Aq/zLu5SNt2k2Mj+awJwUehYcuZIVflTVzXlUQvxtU0Bn/mCpX2jkmQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-config/-/cli-config-1.20.1.tgz", + "integrity": "sha512-V7t2Nk5BaP1RnTn9gcd3sOAG/r0dagRD1mEIUd9qgxzQuA2f7Uwap09C4sKLP7IKLtAx8tBlFfrzuOoqr7u8sg==", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.8.0", @@ -1029,7 +1029,7 @@ "@contentstack/utils": "~1.7.0", "@oclif/core": "^4.8.3", "@oclif/plugin-help": "^6.2.28", - "lodash": "^4.17.23" + "lodash": "^4.18.1" }, "engines": { "node": ">=14.0.0" @@ -1095,9 +1095,9 @@ } }, "node_modules/@contentstack/cli-utilities": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.18.0.tgz", - "integrity": "sha512-JEm6ElIegkcibHUEjRF+Id9529bAXBqkf0Givs9GL5CZE7d8eiLzFCUnlb51VZynk1g5+SmjY5nSeghrmcVSPg==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-utilities/-/cli-utilities-1.18.1.tgz", + "integrity": "sha512-1ymPu5HbOXFdDJHJFiwtT1yVNpmDOgMH8qqCeP3kjS7ED1+rz7Q3cWPnJC9FlUfvFeOAyJaJPPQCiYd0lgujtw==", "license": "MIT", "dependencies": { "@contentstack/management": "~1.27.5", @@ -1116,7 +1116,7 @@ "inquirer-search-list": "^1.2.6", "js-yaml": "^4.1.1", "klona": "^2.0.6", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "mkdirp": "^1.0.4", "open": "^8.4.2", "ora": "^5.4.1", @@ -1144,15 +1144,15 @@ } }, "node_modules/@contentstack/cli-variants": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@contentstack/cli-variants/-/cli-variants-1.4.0.tgz", - "integrity": "sha512-avYWCteVVfChz2m/r6VzLAeRKboJjwZVZuQUEONJb0wOeSlFfUC/koYbUaoAtN8v+0vbVx4Z/EkQAaTJIMDbMg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@contentstack/cli-variants/-/cli-variants-1.4.1.tgz", + "integrity": "sha512-iLl1QFeVLIxJGRSbBoTXp3OyfujBj74zj47yzQKo6eSUMBF4Eelb75zFrQlx2gI3UQY9hRX1KnAtqcfRk7jGmg==", "license": "MIT", "dependencies": { "@contentstack/cli-utilities": "~1.18.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", - "lodash": "^4.17.23", + "lodash": "4.18.1", "mkdirp": "^1.0.4", "winston": "^3.17.0" } @@ -1581,9 +1581,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -1598,9 +1598,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -1615,9 +1615,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -1632,9 +1632,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -1649,9 +1649,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -1666,9 +1666,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -1683,9 +1683,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -1700,9 +1700,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -1717,9 +1717,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -1734,9 +1734,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -1751,9 +1751,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -1768,9 +1768,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -1785,9 +1785,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -1802,9 +1802,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -1819,9 +1819,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -1836,9 +1836,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -1853,9 +1853,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -1870,9 +1870,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -1887,9 +1887,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -1904,9 +1904,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -1921,9 +1921,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -1938,9 +1938,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -1955,9 +1955,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -1972,9 +1972,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -1989,9 +1989,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -2006,9 +2006,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -2614,9 +2614,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", "dev": true, "license": "MIT", "optional": true, @@ -2684,9 +2684,9 @@ } }, "node_modules/@oclif/core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.3.tgz", - "integrity": "sha512-0mD8vcrrX5uRsxzvI8tbWmSVGngvZA/Qo6O0ZGvLPAWEauSf5GFniwgirhY0SkszuHwu0S1J1ivj/jHmqtIDuA==", + "version": "4.10.5", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.5.tgz", + "integrity": "sha512-qcdCF7NrdWPfme6Kr34wwljRCXbCVpL1WVxiNy0Ep6vbWKjxAjFQwuhqkoyL0yjI+KdwtLcOCGn5z2yzdijc8w==", "license": "MIT", "dependencies": { "ansi-escapes": "^4.3.2", @@ -2699,7 +2699,7 @@ "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "semver": "^7.7.3", "string-width": "^4.2.3", "supports-color": "^8", @@ -2713,9 +2713,9 @@ } }, "node_modules/@oclif/plugin-help": { - "version": "6.2.41", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.41.tgz", - "integrity": "sha512-oHqpm9a8NnLY9J5yIA+znchB2QCBqDUu5n7XINdZwfbhO6WOUZ2ANww6QN7crhvAKgpN5HK/ELN8Hy96kgLUuA==", + "version": "6.2.44", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.44.tgz", + "integrity": "sha512-x03Se2LtlOOlGfTuuubt5C4Z8NHeR4zKXtVnfycuLU+2VOMu2WpsGy9nbs3nYuInuvsIY1BizjVaTjUz060Sig==", "license": "MIT", "dependencies": { "@oclif/core": "^4" @@ -2725,13 +2725,13 @@ } }, "node_modules/@oclif/plugin-not-found": { - "version": "3.2.78", - "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.78.tgz", - "integrity": "sha512-wFg7rUYUxYsBMl0fEBHOJ+GAO0/3Nwpn4scmkqV3IQdch7+N1ke8qFOzLZal0kpa0wt+Tr/aJvaT8iYccPGZDQ==", + "version": "3.2.80", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.80.tgz", + "integrity": "sha512-yTLjWvR1r/Rd/cO2LxHdMCDoL5sQhBYRUcOMCmxZtWVWhx4rAZ8KVUPDVsb+SvjJDV5ADTDBgt1H52fFx7YWqg==", "license": "MIT", "dependencies": { "@inquirer/prompts": "^7.10.1", - "@oclif/core": "^4.10.3", + "@oclif/core": "^4.10.5", "ansis": "^3.17.0", "fast-levenshtein": "^3.0.0" }, @@ -2740,9 +2740,9 @@ } }, "node_modules/@oclif/plugin-plugins": { - "version": "5.4.59", - "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-5.4.59.tgz", - "integrity": "sha512-W/F3vNwhC3BHmn1o4g92H8kY4rYw9RsgVRm+GDulZg0XqSoseJYCMQell6ajTj8xljrrG0dZSTuEfc4ETwC2VA==", + "version": "5.4.61", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-5.4.61.tgz", + "integrity": "sha512-FsXYLdXJWucrAzDQ3Q2G/mFGeTaUIsL4o76ayG6qNaF8iq1n2O3YnniCl90RLphJmty2ScGTv2YIniOHt4HHjw==", "license": "MIT", "dependencies": { "@oclif/core": "^4.8.0", @@ -2812,9 +2812,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "dev": true, "license": "MIT", "funding": { @@ -2832,9 +2832,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ "arm64" ], @@ -2849,9 +2849,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -2866,9 +2866,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -2883,9 +2883,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -2900,9 +2900,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -2917,9 +2917,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -2934,9 +2934,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -2951,9 +2951,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -2968,9 +2968,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -2985,9 +2985,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -3002,9 +3002,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -3019,9 +3019,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -3036,9 +3036,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", "cpu": [ "wasm32" ], @@ -3046,16 +3046,52 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -3070,9 +3106,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -3087,9 +3123,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", "dev": true, "license": "MIT" }, @@ -3566,18 +3602,18 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "version": "15.3.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", + "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "node_modules/@sinonjs/samsam": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.3.tgz", - "integrity": "sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-10.0.2.tgz", + "integrity": "sha512-8lVwD1Df1BmzoaOLhMcGGcz/Jyr5QY2KSB75/YK1QgKzoabTeLdIVyhXNZK9ojfSKSdirbXqdbsXXqP9/Ve8+A==", "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", @@ -3877,9 +3913,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3896,7 +3932,7 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -3915,7 +3951,7 @@ "version": "18.3.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4282,14 +4318,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -4303,8 +4339,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.2", - "vitest": "4.1.2" + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4313,16 +4349,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4330,47 +4366,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", "dev": true, "license": "MIT", "dependencies": { @@ -4381,13 +4380,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.4", "pathe": "^2.0.3" }, "funding": { @@ -4395,14 +4394,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4411,9 +4410,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", "dev": true, "license": "MIT", "funding": { @@ -4421,13 +4420,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", + "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4451,9 +4450,9 @@ } }, "node_modules/@wordpress/block-serialization-default-parser": { - "version": "5.42.0", - "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.42.0.tgz", - "integrity": "sha512-XcX6gOeQOuG0RrUqJV1dadPBUi77uhLhpGfQH/s8vmAEGSWqgJAbjWwUKD8RP6wCGY+IE3Gayd5zu48aVRlB4A==", + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/block-serialization-default-parser/-/block-serialization-default-parser-5.44.0.tgz", + "integrity": "sha512-XaVZyQskiI/1Ysq9r2VH4sF017mj3Cl1jOI8IXdpKykOe3YZ6WXPN7FwglVJj5y9Qhw0RgpCObXAORI0PTqDpg==", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -4847,9 +4846,9 @@ } }, "node_modules/@wordpress/shortcode": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.42.0.tgz", - "integrity": "sha512-vaXEGjis5IqvPtSMYZgrT2zg5HwjePrs5fgWCwYfX5r/uiizfkeOSedpTBSH/FLpQQTMMeFsr22DLcuF0qdyeA==", + "version": "4.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/shortcode/-/shortcode-4.44.0.tgz", + "integrity": "sha512-Vh22BIujZdeeoKYsJ3qEineLeqN/5kURcg9OBIWGBCkKAiCktFcdXUsvaehjZ7VDKWfmNP/Hf9SP/Dt9Gyz44w==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -5006,9 +5005,9 @@ } }, "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", "license": "MIT", "engines": { "node": ">=12.0" @@ -5648,14 +5647,14 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6357,7 +6356,7 @@ "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^2.8.3" + "yaml": "^1.10.0" }, "engines": { "node": ">=10" @@ -6476,7 +6475,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/csv": { @@ -7020,7 +7019,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" @@ -7055,7 +7054,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7154,9 +7153,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -7309,9 +7308,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7322,32 +7321,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escalade": { @@ -7668,12 +7667,12 @@ } }, "node_modules/express-validator": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz", - "integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.2.tgz", + "integrity": "sha512-ctLw1Vl6dXVH62dIQMDdTAQkrh480mkFuG6/SGXOaVlwPNukhRAe7EgJIMJ2TSAni8iwHBRp530zAZE5ZPF2IA==", "license": "MIT", "dependencies": { - "lodash": "^4.17.21", + "lodash": "^4.18.1", "validator": "~13.15.23" }, "engines": { @@ -8111,9 +8110,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -8442,9 +8441,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -10344,7 +10343,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -11841,9 +11839,9 @@ } }, "node_modules/mysql2": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz", - "integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.0.tgz", + "integrity": "sha512-4jaJYBObj7FhD3lnZhqX1yDMuZN4mQNz+IolDySDXT7fbozMBpeGQNcuWXKUqo4ahkAEfkjUHPjnwuDI0/6VKw==", "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.2", @@ -14928,9 +14926,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -15070,9 +15068,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "funding": [ { @@ -15328,9 +15326,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -15420,7 +15418,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -15444,7 +15442,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -15709,11 +15707,12 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -15807,14 +15806,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" @@ -15823,21 +15822,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, "node_modules/rollup": { @@ -16064,7 +16063,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -16278,13 +16277,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -16350,16 +16349,15 @@ } }, "node_modules/sinon": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.3.tgz", - "integrity": "sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.1.2.tgz", + "integrity": "sha512-FS6mN+/bx7e2ajpXkEmOcWB6xBzWiuNoAQT18/+a20SS4U7FSYl8Ms7N6VTUxN/1JAjkx7aXp+THMC8xdpp0gA==", "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.1", - "@sinonjs/samsam": "^9.0.3", - "diff": "^8.0.3", - "supports-color": "^7.2.0" + "@sinonjs/fake-timers": "^15.3.2", + "@sinonjs/samsam": "^10.0.2", + "diff": "^8.0.4" }, "funding": { "type": "opencollective", @@ -16375,18 +16373,6 @@ "node": ">=0.3.1" } }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -16702,9 +16688,9 @@ } }, "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -17017,7 +17003,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, "license": "MIT" }, "node_modules/thirty-two": { @@ -17065,9 +17050,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", "engines": { @@ -17075,13 +17060,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -17417,7 +17402,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -17446,12 +17430,12 @@ } }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.1.0.tgz", + "integrity": "sha512-E9MkTS4xXLnRPYqxH2e6Hr2/49e7WFDKczKcCaFH4VaZs2iNvHMqeIkyUAD9vM8kujy9TjVrRlQ5KkdEJxB2pw==", "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { @@ -17605,9 +17589,9 @@ } }, "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -17622,17 +17606,144 @@ "node": ">= 0.8" } }, - "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "bin": { @@ -17650,7 +17761,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -17700,86 +17811,22 @@ } } }, - "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, + "license": "ISC", + "optional": true, + "peer": true, "bin": { - "vitest": "vitest.mjs" + "yaml": "bin.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 14.6" }, "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/void-elements": { @@ -18163,19 +18210,13 @@ } }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" + "node": ">= 6" } }, "node_modules/yargs": { diff --git a/api/package.json b/api/package.json index 4f98711e3..d75f4be29 100644 --- a/api/package.json +++ b/api/package.json @@ -34,6 +34,9 @@ "@contentstack/cli-utilities": "^1.17.1", "@contentstack/json-rte-serializer": "^3.0.5", "@contentstack/marketplace-sdk": "^1.5.0", + "@emnapi/core": "1.9.1", + "@emnapi/runtime": "1.9.1", + "@emnapi/wasi-threads": "1.2.0", "@wordpress/block-serialization-default-parser": "^5.39.0", "axios": "^1.15.0", "cheerio": "^1.2.0", @@ -59,10 +62,7 @@ "php-serialize": "^5.1.3", "socket.io": "^4.7.5", "uuid": "^9.0.1", - "winston": "^3.11.0", - "@emnapi/core": "1.9.1", - "@emnapi/runtime" : "1.9.1", - "@emnapi/wasi-threads": "1.2.0" + "winston": "^3.11.0" }, "devDependencies": { "@types/cors": "^2.8.17", diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index f457fcd09..a3974610b 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -1521,6 +1521,54 @@ const getExistingTaxonomies = async (req: Request) => { ); } } + + // Path 3: Contentful export validation (upload-api contentfulMigrationData) + if (sourceTaxonomies.length === 0) { + try { + const contentfulTaxonomyPath = path.join( + process.cwd(), + '..', + 'upload-api', + 'contentfulMigrationData', + 'taxonomySchema', + 'taxonomySchema.json', + ); + const resolvedCf = path.resolve(contentfulTaxonomyPath); + if ( + resolvedCf.includes('upload-api') && + resolvedCf.includes('contentfulMigrationData') + ) { + const stats = await fs.promises + .lstat(resolvedCf) + .catch(() => null); + if (stats && stats.isFile() && !stats.isSymbolicLink()) { + const taxonomyData = await fs.promises.readFile( + resolvedCf, + 'utf8', + ); + const taxonomiesArray = JSON.parse(taxonomyData); + const cfTaxonomies = ( + Array.isArray(taxonomiesArray) + ? taxonomiesArray + : Object.values(taxonomiesArray) + ).map((taxonomy: any) => ({ + uid: taxonomy.uid || '', + name: taxonomy.name || taxonomy.uid || '', + description: taxonomy.description || '', + source: 'source_cms', + })); + sourceTaxonomies.push(...cfTaxonomies); + logger.info( + `Found ${cfTaxonomies.length} taxonomies from upload-api contentfulMigrationData`, + ); + } + } + } catch (cfTaxError: any) { + logger.warn( + `Could not read Contentful taxonomies from upload-api: ${cfTaxError.message}`, + ); + } + } } // Step 2: Get destination taxonomies from Contentstack (if stack exists and token_payload is available) diff --git a/api/src/services/contentful.service.ts b/api/src/services/contentful.service.ts index 37af61b8b..d5ecc13f6 100644 --- a/api/src/services/contentful.service.ts +++ b/api/src/services/contentful.service.ts @@ -10,6 +10,11 @@ import { jsonToHtml, jsonToMarkdown, htmlToJson } from '@contentstack/json-rte-s import { CHUNK_SIZE, LOCALE_MAPPER, MIGRATION_DATA_CONFIG } from "../constants/index.js"; import { Locale } from "../models/types.js"; import jsonRTE from "./contentful/jsonRTE.js"; +import { + buildContentfulTaxonomyAssignments, + contentfulSchemeIdToStackTaxonomyUid, + createTaxonomy as createContentfulTaxonomyFromExport, +} from "./contentful/taxonomy.service.js"; import { getAllLocales, getLogMessage } from "../utils/index.js"; import customLogger from "../utils/custom-logger.utils.js"; @@ -99,6 +104,52 @@ const mapLocales = ({ masterLocale, locale, locales, isNull = false }: any) => { } } +/** + * When an entry has `metadata.concepts` but no field locales, choose Contentful locale key(s) + * that align with the project/package locale mapper so `mapLocales` can resolve them later. + */ +function pickContentfulLocaleFromMasterLocaleMap(master: unknown): string | undefined { + if (!master || typeof master !== 'object' || Array.isArray(master)) return undefined; + const m = master as Record; + const keys = Object.keys(m); + if (!keys.length) return undefined; + for (const k of keys) { + if (k.includes('-')) return k; + } + for (const v of Object.values(m)) { + if (typeof v === 'string' && v.includes('-')) return v; + } + return keys[0]; +} + +function resolveLocalesForTaxonomyMetadata( + entryLocaleKeys: Set, + entryDataBranch: Record | undefined, + localeMapper: Record, + entrySysLocale?: string, +): string[] { + const fromFields = [...entryLocaleKeys]; + if (fromFields.length) return fromFields; + + if (entrySysLocale && typeof entrySysLocale === 'string') { + return [entrySysLocale]; + } + + const fromExisting = Object.keys(entryDataBranch || {}); + if (fromExisting.length) return fromExisting; + + const fromProjectMaster = pickContentfulLocaleFromMasterLocaleMap(localeMapper?.masterLocale); + if (fromProjectMaster) return [fromProjectMaster]; + + const fromDefaultMaster = pickContentfulLocaleFromMasterLocaleMap(LOCALE_MAPPER?.masterLocale); + if (fromDefaultMaster) return [fromDefaultMaster]; + + const otherKeys = Object.keys(localeMapper || {}).filter((k) => k !== 'masterLocale'); + if (otherKeys.length) return [otherKeys[0]]; + + return ['en-US']; +} + function resolveEntryFieldKey(entry: Record, baseKey: string): string | undefined { if (baseKey in entry) return baseKey; const snake = baseKey.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`); @@ -106,6 +157,20 @@ function resolveEntryFieldKey(entry: Record, baseKey: string): return undefined; } +/** Allowed taxonomy scheme UIDs from Contentful export content type `metadata.taxonomy` (sanitized for Contentstack). */ +function getAllowedTaxonomySchemesFromExportContentType( + contentTypesFromPackage: any[] | undefined, + contentTypeId: string, +): string[] { + if (!contentTypesFromPackage?.length) return []; + const ctDef = contentTypesFromPackage.find((c: any) => c?.sys?.id === contentTypeId); + const links = ctDef?.metadata?.taxonomy; + if (!Array.isArray(links)) return []; + return links + .map((l: any) => contentfulSchemeIdToStackTaxonomyUid(l?.sys?.id)) + .filter(Boolean); +} + /** * Maps Contentful content type id → field id → whether that field is localized in the export schema. * Used so we only fan out values for fields with `localized: false`, not for localized fields that @@ -129,6 +194,85 @@ function buildContentfulFieldLocalizedByContentType( return byCt; } +/** + * When the export omits `widgetId`, infer defaults aligned with + * upload-api/migration-contentful/libs/contentTypeMapper.js. + */ +function inferContentfulDefaultWidgetId(fieldType: string | undefined): string | undefined { + switch (fieldType) { + case "Symbol": + return "singleLine"; + case "Text": + return "multipleLine"; + case "Integer": + case "Number": + return "numberEditor"; + case "RichText": + return "richTextEditor"; + case "Boolean": + return "boolean"; + default: + return undefined; + } +} + +function getContentfulFieldFromPackage( + contentTypesFromPackage: any[] | undefined, + ctId: string, + fieldId: string +): any | undefined { + const ct = contentTypesFromPackage?.find((c: any) => c?.sys?.id === ctId); + return ct?.fields?.find((f: any) => f?.id === fieldId); +} + +/** + * Picks one fieldMapping row when several share the same `uid` (e.g. bootstrap `title`/`url` rows + * from createInitialMapper plus the real Contentful field). Mapper `otherCmsType` is Contentful + * `widgetId` from the migration pipeline. + */ +function resolveFieldMappingRow( + fieldMapping: any[] | undefined, + contentTypesFromPackage: any[] | undefined, + ctId: string, + fieldId: string +): any | undefined { + const candidates = fieldMapping?.filter((item: any) => item?.uid === fieldId) ?? []; + if (candidates?.length === 0) return undefined; + if (candidates?.length === 1) return candidates?.[0]; + + const cfField = getContentfulFieldFromPackage(contentTypesFromPackage, ctId, fieldId); + const widgetId = cfField?.widgetId ?? inferContentfulDefaultWidgetId(cfField?.type); + if (widgetId) { + const byWidget = candidates?.filter((c: any) => c?.otherCmsType === widgetId); + if (byWidget?.length >= 1) return byWidget?.[0]; + } + + const typeToCs: Record = { + RichText: "json", + Boolean: "boolean", + Date: "isodate", + }; + const expectCs = cfField?.type ? typeToCs[cfField?.type as string] : undefined; + if (expectCs) { + const byCs = candidates?.filter((c: any) => c?.contentstackFieldType === expectCs); + if (byCs?.length >= 1) return byCs?.[0]; + } + + if (cfField?.type === "Boolean") { + const byBool = candidates?.filter((c: any) => c?.contentstackFieldType === "boolean"); + if (byBool?.length >= 1) return byBool?.[0]; + } + + // Legacy bootstrap rows use otherCmsType "text" while real Symbol/Text fields use widget ids + // (e.g. singleLine). Prefer non-"text" otherCmsType when the schema is Symbol/Text. + if (cfField && ["Symbol", "Text"]?.includes(cfField.type)) { + const nonBootstrap = candidates?.filter((c: any) => c?.otherCmsType !== "text"); + if (nonBootstrap?.length >= 1) return nonBootstrap[0]; + } + + return candidates?.[0]; +} + const transformCloudinaryObject = (input: any) => { const result: any = []; if (!Array.isArray(input)) { @@ -828,24 +972,32 @@ const createEntry = async (packagePath: any, destination_stack_id: string, proje { sys: { id, + locale: entrySysLocale, contentType: { sys: { id: name }, }, environment: { sys: { id: environment_id = "" } = {} } = {}, }, fields, + metadata, }: any ) => { entryData[name] ??= {}; + const currentCT = contentTypes?.find((ct: any) => ct?.otherCmsUid === name); - Object.entries(fields).forEach(([key, value]) => { - const currentCT = contentTypes?.find((ct: any) => ct?.otherCmsUid === name); + Object.entries(fields || {}).forEach(([key, value]) => { const locales: string[] = []; Object.entries(value as object).forEach(([lang, langValue]) => { entryData[name][lang] ??= {}; entryData[name][lang][id] ??= {}; locales.push(lang); - const fieldData = currentCT?.fieldMapping?.find?.((item: any) => key === item?.uid); + + const fieldData = resolveFieldMappingRow( + currentCT?.fieldMapping, + content, + name, + key + ); const newId = fieldData?.contentstackFieldUid ?? `${key}`?.replace?.(/[^a-zA-Z0-9]+/g, "_"); entryData[name][lang][id][newId] = processField( langValue, @@ -856,6 +1008,7 @@ const createEntry = async (packagePath: any, destination_stack_id: string, proje fieldData ); }); + const pathName = getDisplayName(name, displayField); locales.forEach((locale) => { const localeCode = mapLocales({ masterLocale: master_locale, locale, locales: LocaleMapper }); @@ -897,16 +1050,15 @@ const createEntry = async (packagePath: any, destination_stack_id: string, proje // Do not infer non-localized-ness from a single locale key — localized fields can legitimately // have only one locale when translations are missing. const entryLocaleKeys = new Set(); - for (const [, v] of Object?.entries?.(fields)) { + for (const [, v] of Object?.entries?.(fields || {})) { for (const lang of Object.keys(v as object)) { entryLocaleKeys.add(lang); } } - const ct = contentTypes?.find((c: any) => c?.otherCmsUid === name); - for (const [key, value] of Object?.entries?.(fields)) { + for (const [key, value] of Object?.entries?.(fields || {})) { const langs = Object?.keys(value as object); if (langs?.length !== 1) continue; - const fd = ct?.fieldMapping?.find?.((item: any) => key === item?.uid); + const fd = resolveFieldMappingRow(currentCT?.fieldMapping, content, name, key); const localizedInCf = cfFieldLocalizedByCt.get(name)?.get(key); const explicitlyNonLocalized = localizedInCf === false || @@ -929,6 +1081,51 @@ const createEntry = async (packagePath: any, destination_stack_id: string, proje } } + const metaTaxField = currentCT?.fieldMapping?.find( + (f: any) => + f?.otherCmsType === 'TaxonomyMetadata' || + f?.contentstackFieldType === 'taxonomy' || + f?.contentstackFieldUid === 'taxonomies' || + f?.contentstackFieldUid === 'metadata_taxonomies', + ); + let allowedFromMapper: string[] = []; + if (metaTaxField) { + const taxonomiesConfig = + metaTaxField?.advanced?.taxonomies || metaTaxField?.taxonomies || []; + allowedFromMapper = taxonomiesConfig + .map((t: any) => (typeof t === 'string' ? t : t?.taxonomy_uid)) + .filter(Boolean) + .map((uid: string) => contentfulSchemeIdToStackTaxonomyUid(uid)) + .filter(Boolean); + } + const allowedFromExport = getAllowedTaxonomySchemesFromExportContentType( + content, + name, + ); + const allowedSchemes = + allowedFromMapper.length > 0 ? allowedFromMapper : allowedFromExport; + + if (metadata?.concepts?.length) { + const taxValue = buildContentfulTaxonomyAssignments( + metadata.concepts, + allowedSchemes, + ); + if (taxValue.length) { + const fieldKey = metaTaxField?.contentstackFieldUid || 'taxonomies'; + const localesForTax = resolveLocalesForTaxonomyMetadata( + entryLocaleKeys, + entryData[name], + LocaleMapper, + entrySysLocale, + ); + for (const loc of localesForTax) { + entryData[name][loc] ??= {}; + entryData[name][loc][id] ??= {}; + entryData[name][loc][id][fieldKey] = taxValue; + } + } + } + return entryData; }, {} @@ -1468,4 +1665,5 @@ export const contentfulService = { createRefrence, createWebhooks, createVersionFile, + createTaxonomy: createContentfulTaxonomyFromExport, }; diff --git a/api/src/services/contentful/taxonomy.service.ts b/api/src/services/contentful/taxonomy.service.ts new file mode 100644 index 000000000..e93a0f855 --- /dev/null +++ b/api/src/services/contentful/taxonomy.service.ts @@ -0,0 +1,237 @@ +import fs from 'fs'; +import path from 'path'; +import { getLogMessage } from '../../utils/index.js'; +import customLogger from '../../utils/custom-logger.utils.js'; +import { MIGRATION_DATA_CONFIG } from '../../constants/index.js'; + +const { DATA, TAXONOMIES_DIR_NAME, TAXONOMIES_FILE_NAME } = MIGRATION_DATA_CONFIG; + +/** + * Contentful export uses scheme ids like `productCategory`. Contentstack taxonomy UIDs must be + * lowercase alphanumeric + underscores only (no camelCase). + */ +export function contentfulSchemeIdToStackTaxonomyUid(contentfulSchemeId: string): string { + if (!contentfulSchemeId || typeof contentfulSchemeId !== 'string') return ''; + return contentfulSchemeId + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +/** Maps Contentful concept id prefix (before first "-") to Contentstack taxonomy uid (sanitized). */ +export const CONCEPT_PREFIX_TO_SCHEME: Record = { + brd: 'brand', + cat: 'product_category', + branch: 'branch', + dis: 'discipline', +}; + +export function inferSchemeFromConceptId(conceptId: string): string | null { + if (!conceptId || typeof conceptId !== 'string') return null; + const prefix = conceptId.split('-')[0]; + return CONCEPT_PREFIX_TO_SCHEME[prefix] ?? null; +} + +export function sanitizeTermUid(conceptId: string): string { + return conceptId + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +function humanizeSchemeId(id: string): string { + if (!id) return ''; + const words = id.split('_').filter(Boolean); + return words + .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join(' '); +} + +export function buildContentfulTaxonomyAssignments( + concepts: Array<{ sys?: { id?: string } }> | undefined, + allowedSchemeIds: string[], +): Array<{ taxonomy_uid: string; term_uid: string }> { + const allow = (allowedSchemeIds || []).filter(Boolean); + const allowSet = new Set( + allow.map((id) => contentfulSchemeIdToStackTaxonomyUid(id)).filter(Boolean), + ); + const useAllow = allowSet.size > 0; + const out: Array<{ taxonomy_uid: string; term_uid: string }> = []; + const seen = new Set(); + + for (const c of concepts || []) { + const id = c?.sys?.id; + if (!id) continue; + const scheme = inferSchemeFromConceptId(id); + if (!scheme) continue; + if (useAllow && !allowSet.has(scheme)) continue; + const termUid = sanitizeTermUid(id); + const key = `${scheme}::${termUid}`; + if (seen.has(key)) continue; + seen.add(key); + out.push({ taxonomy_uid: scheme, term_uid: termUid }); + } + return out; +} + +interface TaxonomyTerm { + uid: string; + name: string; + parent_uid: string | null; + description?: string; + contentful_concept_id: string; +} + +interface TaxonomyStructure { + taxonomy: { + uid: string; + name: string; + description: string; + }; + terms: TaxonomyTerm[]; +} + +const saveTaxonomyFiles = async ( + taxonomies: Record, + taxonomiesPath: string, + projectId: string, + destination_stack_id: string, +): Promise => { + for (const [schemeUid, taxonomy] of Object.entries(taxonomies)) { + const filePath = path.join(taxonomiesPath, `${schemeUid}.json`); + await fs.promises.writeFile(filePath, JSON.stringify(taxonomy, null, 2), 'utf8'); + const message = getLogMessage( + 'saveTaxonomyFiles', + `Saved taxonomy file: ${schemeUid}.json with ${taxonomy.terms.length} terms.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + } + + const taxonomiesDataObject: Record = {}; + for (const [schemeUid, taxonomy] of Object.entries(taxonomies)) { + taxonomiesDataObject[schemeUid] = { + uid: taxonomy.taxonomy.uid, + name: taxonomy.taxonomy.name, + description: taxonomy.taxonomy.description, + }; + } + + const taxonomiesFilePath = path.join(taxonomiesPath, TAXONOMIES_FILE_NAME); + await fs.promises.writeFile( + taxonomiesFilePath, + JSON.stringify(taxonomiesDataObject, null, 2), + 'utf8', + ); + await customLogger( + projectId, + destination_stack_id, + 'info', + getLogMessage( + 'saveTaxonomyFiles', + `Saved consolidated ${TAXONOMIES_FILE_NAME} with ${Object.keys(taxonomiesDataObject).length} taxonomies.`, + {}, + ), + ); +}; + +/** + * Builds taxonomy vocabularies and terms from a Contentful export JSON (metadata.taxonomy on content types, + * metadata.concepts on entries) and writes the same layout as Drupal: per-scheme JSON + taxonomies.json. + */ +export const createTaxonomy = async ( + packagePath: string, + destination_stack_id: string, + projectId: string, +): Promise => { + const taxonomiesPath = path.join(DATA, destination_stack_id, TAXONOMIES_DIR_NAME); + + try { + await fs.promises.mkdir(taxonomiesPath, { recursive: true }); + const raw = await fs.promises.readFile(packagePath, 'utf8'); + const data = JSON.parse(raw); + const contentTypes = data?.contentTypes || []; + const entries = data?.entries || []; + + const schemeIds = new Set(); + for (const ct of contentTypes) { + for (const link of ct?.metadata?.taxonomy || []) { + const sid = link?.sys?.id; + if (sid) schemeIds.add(contentfulSchemeIdToStackTaxonomyUid(sid)); + } + } + + const termsByScheme: Record> = {}; + for (const sid of schemeIds) { + termsByScheme[sid] = new Map(); + } + + for (const entry of entries) { + for (const c of entry?.metadata?.concepts || []) { + const conceptId = c?.sys?.id; + if (!conceptId) continue; + const scheme = inferSchemeFromConceptId(conceptId); + if (!scheme || !termsByScheme[scheme]) continue; + const termUid = sanitizeTermUid(conceptId); + if (!termsByScheme[scheme].has(termUid)) { + termsByScheme[scheme].set(termUid, conceptId); + } + } + } + + const taxonomies: Record = {}; + + for (const schemeUid of schemeIds) { + const termMap = termsByScheme[schemeUid]; + const terms: TaxonomyTerm[] = []; + for (const [termUid, conceptId] of termMap) { + terms.push({ + uid: termUid, + name: conceptId, + parent_uid: null, + description: '', + contentful_concept_id: conceptId, + }); + } + taxonomies[schemeUid] = { + taxonomy: { + uid: schemeUid, + name: humanizeSchemeId(schemeUid) || schemeUid, + description: 'Imported from Contentful taxonomy', + }, + terms, + }; + } + + if (Object.keys(taxonomies).length === 0) { + const message = getLogMessage( + 'createTaxonomy', + 'No Contentful taxonomy schemes found on content types (metadata.taxonomy). Skipping taxonomy files.', + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', message); + return; + } + + await saveTaxonomyFiles(taxonomies, taxonomiesPath, projectId, destination_stack_id); + + const successMessage = getLogMessage( + 'createTaxonomy', + `Exported ${Object.keys(taxonomies).length} Contentful taxonomies.`, + {}, + ); + await customLogger(projectId, destination_stack_id, 'info', successMessage); + } catch (err) { + const message = getLogMessage( + 'createTaxonomy', + 'Error encountered while creating taxonomies from Contentful export.', + {}, + err, + ); + await customLogger(projectId, destination_stack_id, 'error', message); + throw err; + } +}; diff --git a/api/src/services/migration.service.ts b/api/src/services/migration.service.ts index b43b245a7..9a5494a3f 100644 --- a/api/src/services/migration.service.ts +++ b/api/src/services/migration.service.ts @@ -515,6 +515,11 @@ const startTestMigration = async (req: Request): Promise => { projectId, true ); + await contentfulService?.createTaxonomy( + cleanLocalPath, + project?.current_test_stack_id, + projectId, + ); await contentfulService?.createEntry( cleanLocalPath, project?.current_test_stack_id, @@ -925,6 +930,11 @@ const startMigration = async (req: Request): Promise => { project?.destination_stack_id, projectId ); + await contentfulService?.createTaxonomy( + cleanLocalPath, + project?.destination_stack_id, + projectId, + ); await contentfulService?.createEntry( cleanLocalPath, project?.destination_stack_id, diff --git a/api/src/services/wordpress.service.ts b/api/src/services/wordpress.service.ts index bfaac3f37..628c980a4 100644 --- a/api/src/services/wordpress.service.ts +++ b/api/src/services/wordpress.service.ts @@ -1638,11 +1638,11 @@ function getAuthorFieldValue(field: any, authorData: any, fallbackUrl?: string): const fieldMapping: Record = { 'email': 'wp:author_email', 'first_name': 'wp:author_first_name', - 'firstname': 'wp:author_first_name', + 'first name': 'wp:author_first_name', 'last_name': 'wp:author_last_name', - 'lastname': 'wp:author_last_name', + 'last name': 'wp:author_last_name', 'display_name': 'wp:author_display_name', - 'displayname': 'wp:author_display_name', + 'display name': 'wp:author_display_name', 'description': 'wp:author_description', 'website': 'wp:author_url', 'url': 'wp:author_url', diff --git a/api/src/utils/content-type-creator.utils.ts b/api/src/utils/content-type-creator.utils.ts index 1b74ff6fc..ddcdd3eda 100644 --- a/api/src/utils/content-type-creator.utils.ts +++ b/api/src/utils/content-type-creator.utils.ts @@ -8,7 +8,7 @@ import customLogger from './custom-logger.utils.js'; import { getLogMessage } from './index.js'; import { LIST_EXTENSION_UID, MIGRATION_DATA_CONFIG } from '../constants/index.js'; import { contentMapperService } from "../services/contentMapper.service.js"; -import appMeta from '../constants/app/index.json' with { type: 'json' }; +import appMeta from '../constants/app/index.json'; const { GLOBAL_FIELDS_FILE_NAME, @@ -39,6 +39,17 @@ interface ContentType { const RESERVED_UIDS = new Set(['locale', 'publish_details', 'tags']); +/** Contentful taxonomy scheme ids may be camelCase, Contentstack requires [a-z0-9_]. */ +function normalizeStackTaxonomyUid(raw?: string): string { + if (!raw || typeof raw !== 'string') return ''; + return raw + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + function sanitizeUid(uid?: string) { if (!uid) return uid; let out = uid?.replace?.(/[^a-zA-Z0-9_]/g, '_').replace?.(/^_+/, ''); @@ -116,11 +127,11 @@ const uidCorrector = ({ uid } : {uid : string}) => { * issues do not go unnoticed. * @returns The remapped UIDs. */ -function remapReferenceUids(uids: string[], keyMapper?: Record): string[] { - if (!keyMapper || !Object.keys(keyMapper).length) return uids; - return uids.map(uid => keyMapper[uid] ?? keyMapper[uidCorrector({ uid })] ?? uid); +function remapReferenceUids(uids: string | string[], keyMapper?: Record): string[] { + const uidsArray = Array.isArray(uids) ? uids : [uids]; + if (!keyMapper || !Object.keys(keyMapper).length) return uidsArray; + return uidsArray?.map(uid => keyMapper?.[uid] ?? keyMapper?.[uidCorrector({ uid })] ?? uid); } - function buildFieldSchema(item: any, marketPlacePath: string, parentUid = '', keyMapper?: Record): any { if (item?.isDeleted === true) return null; @@ -790,17 +801,19 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat 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), + taxonomy_uid: normalizeStackTaxonomyUid( + 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 + non_localizable: false })) : []; return { data_type: "taxonomy", display_name: field?.title, - uid: cleanedUid, + uid: 'taxonomies', taxonomies: taxonomiesArray, field_metadata: { description: field?.advanced?.description ?? '', @@ -812,7 +825,7 @@ export const convertToSchemaFormate = ({ field, advanced = false, marketPlacePat }, mandatory: field?.advanced?.mandatory ?? false, multiple: field?.advanced?.multiple !== false, // Default true for taxonomies - non_localizable: field?.advanced?.nonLocalizable ?? false, + non_localizable: false, unique: field?.advanced?.unique ?? false }; } @@ -1044,10 +1057,37 @@ const resolveIsSsoFlag = (is_sso: any): boolean => { ); }; +/** + * Resolves the Contentstack Management API UID for a content type / global field. + * @param migrationContentstackUid - The UID of the content type in the migration data. + * @param keyMapper - The key mapper object. + * @returns The Contentstack Management API UID. + */ +function resolveStackContentTypeUid( + migrationContentstackUid: string, + keyMapper?: Record, +): string { + const mapped = keyMapper?.[migrationContentstackUid]; + if (mapped === undefined || mapped === null || mapped === '') { + return migrationContentstackUid; + } + + const m = String(mapped).trim(); + const looksLikeUuid = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(m); + const looksLikeMongoId = /^[0-9a-f]{24}$/i.test(m); + + if (looksLikeUuid || looksLikeMongoId) { + return migrationContentstackUid; + } + + return m; +} + const existingCtMapper = async ({ keyMapper, contentTypeUid, projectId, region, user_id, is_sso, type}: any) => { try { const normalizedIsSso = resolveIsSsoFlag(is_sso); - const ctUid = keyMapper?.[contentTypeUid]; + const ctUid = resolveStackContentTypeUid(contentTypeUid, keyMapper); if(type === 'global_field') { @@ -1102,6 +1142,92 @@ const mergeArrays = async (a: any[], b: any[]) => { return a; } +/** + * Clones a schema branch. + * @param node - The node to clone. + * @returns The cloned node. + */ +function cloneSchemaBranch(node: any): any { + if (node === undefined || node === null) return node; + try { + return structuredClone(node); + } catch { + return JSON.parse(JSON.stringify(node)); + } +} + +/** + * Finds the target modular blocks field. + * @param field - The field to find the target modular blocks field for. + * @param targetSchema - The target schema. + * @returns The target modular blocks field. + */ +function findTargetModularBlocksField(field: any, targetSchema: any[]): any | undefined { + if (!Array.isArray(targetSchema) || !field || field?.data_type !== 'blocks') return undefined; + + const byUid = targetSchema.find( + (mb: any) => mb?.data_type === 'blocks' && mb?.uid === field?.uid, + ); + if (byUid) return byUid; + + const fd = (field?.display_name ?? '').toString().trim().toLowerCase(); + if (fd) { + const byName = targetSchema.find( + (mb: any) => + mb?.data_type === 'blocks' && + (mb?.display_name ?? '').toString().trim().toLowerCase() === fd, + ); + if (byName) return byName; + } + + return undefined; +} + +/** + * Merge modular blocks preserving destination block order and UIDs: + * 1. Walk destination blocks — merge matching source blocks, clone unmapped ones. + * 2. Append source-only blocks (uids not on destination) at the end. + */ +function mergeModularBlocksFieldFromDestination(field: any, targetMB: any) { + const targetBlocks = targetMB?.blocks ?? []; + const sourceBlocks = field?.blocks ?? []; + if (!targetBlocks.length) return; + + const resultBlocks: any[] = []; + const matchedSourceUids = new Set(); + + for (const tb of targetBlocks) { + const sb = sourceBlocks.find((b: any) => b?.uid === tb?.uid); + if (sb) { + const tSch = tb?.schema ?? []; + const additional = tSch.filter( + (tField: any) => + !(sb?.schema ?? []).some( + (sField: any) => + sField?.uid === tField?.uid && sField?.data_type === tField?.data_type, + ), + ); + sb.schema = removeDuplicateFields([ + ...(sb?.schema ?? []), + ...additional.map((f: any) => cloneSchemaBranch(f)), + ]); + mergeSchemaFields(sb?.schema ?? [], tSch); + resultBlocks.push(sb); + if (sb?.uid) matchedSourceUids.add(sb?.uid); + } else { + resultBlocks.push(cloneSchemaBranch(tb)); + } + } + + for (const sb of sourceBlocks) { + if (sb?.uid && !matchedSourceUids.has(sb?.uid)) { + resultBlocks.push(sb); + } + } + + field.blocks = removeDuplicateFields(resultBlocks); +} + function mergeSchemaFields(sourceSchema: any[], targetSchema: any[]) { for (const field of sourceSchema) { if (field?.data_type === 'group') { @@ -1119,27 +1245,10 @@ function mergeSchemaFields(sourceSchema: any[], targetSchema: any[]) { } if (field?.data_type === 'blocks') { - const targetMB = targetSchema?.find((mb: any) => - mb?.uid === field?.uid && mb?.data_type === 'blocks' - ); + const targetMB = findTargetModularBlocksField(field, targetSchema ?? []); - if (targetMB?.blocks) { - for (const sourceBlock of field?.blocks ?? []) { - const targetBlock = targetMB?.blocks?.find((tb: any) => tb?.uid === sourceBlock?.uid); - - if (targetBlock?.schema) { - const additional = (targetBlock?.schema ?? [])?.filter((tField: any) => - !sourceBlock?.schema?.find((sField: any) => sField?.uid === tField?.uid && sField?.data_type === tField?.data_type) - ); - sourceBlock.schema = removeDuplicateFields([...sourceBlock?.schema ?? [], ...additional]); - mergeSchemaFields(sourceBlock.schema, targetBlock.schema ?? []); - } - } - - const additionalBlocks = (targetMB?.blocks ?? []).filter((tb: any) => - !field?.blocks?.find((sb: any) => sb?.uid === tb?.uid) - ); - field.blocks = removeDuplicateFields([...field?.blocks ?? [], ...additionalBlocks]); + if (targetMB?.blocks?.length) { + mergeModularBlocksFieldFromDestination(field, targetMB); } } } diff --git a/api/tests/unit/services/migration.service.test.ts b/api/tests/unit/services/migration.service.test.ts index e72ebc7b6..e3b25473f 100644 --- a/api/tests/unit/services/migration.service.test.ts +++ b/api/tests/unit/services/migration.service.test.ts @@ -105,6 +105,7 @@ vi.mock('../../../src/services/contentful.service.js', () => ({ createRefrence: vi.fn().mockResolvedValue(undefined), createWebhooks: vi.fn().mockResolvedValue(undefined), createEnvironment: vi.fn().mockResolvedValue(undefined), + createTaxonomy: vi.fn().mockResolvedValue(undefined), createAssets: vi.fn().mockResolvedValue(undefined), createEntry: vi.fn().mockResolvedValue(undefined), createVersionFile: vi.fn().mockResolvedValue(undefined), diff --git a/api/tests/unit/services/user.service.test.ts b/api/tests/unit/services/user.service.test.ts index 0063b7bf7..c619016d2 100644 --- a/api/tests/unit/services/user.service.test.ts +++ b/api/tests/unit/services/user.service.test.ts @@ -1,10 +1,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -const { mockHttps, mockAuthModelRead, mockChainValue } = vi.hoisted(() => ({ - mockHttps: vi.fn(), - mockAuthModelRead: vi.fn(), - mockChainValue: vi.fn(), -})); +const { mockHttps, mockAuthModelRead, mockChainValue, mockRequestWithSsoTokenRefresh } = + vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockAuthModelRead: vi.fn(), + mockChainValue: vi.fn(), + mockRequestWithSsoTokenRefresh: vi.fn(), + })); vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); vi.mock('../../../src/utils/logger.js', () => ({ @@ -24,11 +26,26 @@ vi.mock('../../../src/models/authentication.js', () => ({ }), }, data: { - users: [{ user_id: 'user-123', region: 'NA', authtoken: 'cs-token' }], + users: [ + { + user_id: 'user-123', + region: 'NA', + authtoken: 'cs-token', + access_token: 'sso-access-token', + }, + ], }, }, })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ + getAppOrganization: vi.fn(() => ({ uid: 'org-1', name: 'Test Org' })), +})); +vi.mock('../../../src/utils/sso-request.utils.js', () => ({ + requestWithSsoTokenRefresh: mockRequestWithSsoTokenRefresh, +})); +import AuthenticationModel from '../../../src/models/authentication.js'; +import { getAppOrganization } from '../../../src/utils/auth.utils.js'; import { userService } from '../../../src/services/user.service.js'; describe('user.service', () => { @@ -98,5 +115,105 @@ describe('user.service', () => { expect(result.data.user.email).toBeUndefined(); expect(result.data.user.orgs).toEqual([]); }); + + it('should return SSO user profile when org matches app organization', async () => { + mockChainValue.mockReturnValue(0); + mockRequestWithSsoTokenRefresh.mockResolvedValue([ + null, + { + status: 200, + data: { + user: { + email: 'sso@example.com', + first_name: 'S', + last_name: 'O', + organizations: [{ uid: 'org-1', name: 'Org 1' }], + }, + }, + }, + ]); + + const result = await userService.getUserProfile({ + body: { + token_payload: { region: 'NA', user_id: 'user-123', is_sso: true }, + }, + } as any); + + expect(result.status).toBe(200); + expect(result.data.user.email).toBe('sso@example.com'); + expect(result.data.user.orgs).toEqual([ + { org_id: 'org-1', org_name: 'Test Org' }, + ]); + }); + + it('should throw when SSO user has no access token', async () => { + mockChainValue.mockReturnValue(0); + const user = AuthenticationModel.data.users[0] as { + access_token?: string; + }; + const prev = user.access_token; + delete user.access_token; + + await expect( + userService.getUserProfile({ + body: { + token_payload: { region: 'NA', user_id: 'user-123', is_sso: true }, + }, + } as any) + ).rejects.toMatchObject({ message: 'SSO authentication not completed' }); + + user.access_token = prev; + }); + + it('should return error payload when SSO CS request fails', async () => { + mockChainValue.mockReturnValue(0); + mockRequestWithSsoTokenRefresh.mockResolvedValue([ + { response: { data: { error: 'bad' }, status: 403 } }, + null, + ]); + + const result = await userService.getUserProfile({ + body: { + token_payload: { region: 'NA', user_id: 'user-123', is_sso: true }, + }, + } as any); + + expect(result.status).toBe(403); + expect(result.data).toEqual({ error: 'bad' }); + }); + + it('should throw when SSO user org list does not include app org', async () => { + mockChainValue.mockReturnValue(0); + mockRequestWithSsoTokenRefresh.mockResolvedValue([ + null, + { + status: 200, + data: { + user: { + organizations: [{ uid: 'other-org', name: 'Other' }], + }, + }, + }, + ]); + + await expect( + userService.getUserProfile({ + body: { + token_payload: { region: 'NA', user_id: 'user-123', is_sso: true }, + }, + } as any) + ).rejects.toMatchObject({ message: 'Organization access revoked' }); + }); + + it('should wrap unexpected errors in ExceptionFunction', async () => { + mockChainValue.mockReturnValue(0); + vi.mocked(getAppOrganization).mockImplementationOnce(() => { + throw new Error('unexpected'); + }); + + await expect( + userService.getUserProfile(createReq() as any) + ).rejects.toMatchObject({ message: 'unexpected' }); + }); }); }); diff --git a/api/vitest.config.ts b/api/vitest.config.ts index 5b452adb4..f1512c4f7 100644 --- a/api/vitest.config.ts +++ b/api/vitest.config.ts @@ -35,10 +35,10 @@ export default defineConfig({ 'src/models/types.ts', ], thresholds: { - lines: 77, + lines: 76, functions: 80, - branches: 57, - statements: 77, + branches: 56, + statements: 76, }, }, }, diff --git a/app.json b/app.json index 5e60a8d97..9708dec6c 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,5 @@ { - "timestamp": "2026-02-23T07:26:46.225Z", + "timestamp": "2026-04-08T12:40:32.171Z", "region": { "key": "NA", "name": "North America", @@ -14,21 +14,21 @@ } }, "user": { - "email": "user@example.com", - "uid": "user-uid" + "email": "user@contentstack.com", + "uid": "user_id" }, "organization": { - "name": "Organization Name", - "uid": "organization-uid" + "name": "organization_name", + "uid": "organization_uid" }, "app": { - "name": "Migration Tool", - "uid": "app-uid", - "manifest": "Migration Tool" + "name": "app_name", + "uid": "app_uid", + "manifest": "app_manifest" }, "oauthData": { - "client_id": "client-id", - "client_secret": "client-secret", + "client_id": "user_client_id", + "client_secret": "user_client_secret", "redirect_uri": "http://localhost:5001/v2/auth/save-token", "user_token_config": { "enabled": true, @@ -182,9 +182,9 @@ } }, "pkce": { - "code_verifier": "code-verifier", - "code_challenge": "code-challenge" + "code_verifier": "code_verifier", + "code_challenge": "code_challenge" }, - "authUrl": "auth-url", - "isDefault": true + "authUrl": "auth_url", + "isDefault": false } \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index 8ff49b067..c2d1d164e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -37,14 +37,14 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@vitest/coverage-v8": "^4.0.18", - "@vitest/ui": "^4.0.18", + "@vitest/coverage-v8": "4.1.2", + "@vitest/ui": "4.1.2", "eslint": "^8.51.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "jsdom": "^28.1.0", "prettier": "^3.3.3", - "vitest": "^4.0.18" + "vitest": "4.1.2" } }, "node_modules/@acemir/cssom": { @@ -2712,14 +2712,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", - "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", + "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.2", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2727,14 +2727,14 @@ "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.0", - "vitest": "4.1.0" + "@vitest/browser": "4.1.2", + "vitest": "4.1.2" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2743,31 +2743,31 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", - "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", + "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", "chai": "^6.2.2", - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", - "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", + "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.0", + "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2776,7 +2776,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2788,26 +2788,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", - "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", + "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", - "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", + "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.0", + "@vitest/utils": "4.1.2", "pathe": "^2.0.3" }, "funding": { @@ -2815,14 +2815,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", - "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", + "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/pretty-format": "4.1.2", + "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2831,9 +2831,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", - "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", + "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", "dev": true, "license": "MIT", "funding": { @@ -2862,20 +2862,7 @@ "vitest": "4.1.2" } }, - "node_modules/@vitest/ui/node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/ui/node_modules/@vitest/utils": { + "node_modules/@vitest/utils": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", @@ -2890,21 +2877,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", - "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.0", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -9205,19 +9177,19 @@ } }, "node_modules/vitest": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", - "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", + "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.0", - "@vitest/mocker": "4.1.0", - "@vitest/pretty-format": "4.1.0", - "@vitest/runner": "4.1.0", - "@vitest/snapshot": "4.1.0", - "@vitest/spy": "4.1.0", - "@vitest/utils": "4.1.0", + "@vitest/expect": "4.1.2", + "@vitest/mocker": "4.1.2", + "@vitest/pretty-format": "4.1.2", + "@vitest/runner": "4.1.2", + "@vitest/snapshot": "4.1.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9228,8 +9200,8 @@ "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -9245,13 +9217,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.0", - "@vitest/browser-preview": "4.1.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/ui": "4.1.0", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { diff --git a/ui/package.json b/ui/package.json index 177ca60a2..17367d847 100644 --- a/ui/package.json +++ b/ui/package.json @@ -47,14 +47,14 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", - "@vitest/coverage-v8": "^4.0.18", - "@vitest/ui": "^4.0.18", + "@vitest/coverage-v8": "4.1.2", + "@vitest/ui": "4.1.2", "eslint": "^8.51.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "jsdom": "^28.1.0", "prettier": "^3.3.3", - "vitest": "^4.0.18" + "vitest": "4.1.2" }, "overrides": { "@babel/runtime": ">=7.26.10", diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 181654807..d413f73df 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -40,6 +40,26 @@ interface Taxonomy { source?: string; } +/** From `advanced.taxonomies` + saved uids → rows with uid + label; deduped by uid. */ +function taxonomyRows( + oldList: Array<{ taxonomy_uid?: string; taxonomy_name?: string; name?: string } | string>, + extraUids: string[] +): { taxonomy_uid: string; taxonomy_name: string }[] { + const m = new Map(); + for (const t of oldList) { + const id = typeof t === 'string' ? t : t.taxonomy_uid || ''; + if (!id) continue; + m?.set(id, { + taxonomy_uid: id, + taxonomy_name: typeof t === 'string' ? t : t?.taxonomy_name || t?.name || id + }); + } + for (const id of extraUids) { + if (id && !m?.has(id)) m?.set(id, { taxonomy_uid: id, taxonomy_name: id }); + } + return [...m.values()]; +} + /** * Component for displaying advanced properties. * @param props - The schema properties. @@ -158,22 +178,9 @@ const AdvancePropertise = (props: SchemaProps) => { if (props?.fieldtype === 'Taxonomy') { 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 })) - ); + const rows = taxonomyRows(props?.data?.advanced?.taxonomies || [], getMappedTaxonomyUids()); + if (rows?.length > 0) { + setReferencedTaxonomies(rows?.map((r) => ({ label: r?.taxonomy_name, value: r?.taxonomy_uid }))); } } }, [props?.projectId, props?.fieldtype]); @@ -209,37 +216,24 @@ const AdvancePropertise = (props: SchemaProps) => { // 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 rows = taxonomyRows(props?.data?.advanced?.taxonomies || [], getMappedTaxonomyUids()); + const allTaxonomyUIDs = rows?.map((r) => r?.taxonomy_uid); + + if (allTaxonomyUIDs?.length > 0 && allTaxonomies?.length > 0) { 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; + 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) { + + 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); + setReferencedTaxonomies(rows?.map((r) => ({ label: r?.taxonomy_name, value: r?.taxonomy_uid }))); } - } 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 if (allTaxonomyUIDs?.length > 0 && allTaxonomies?.length === 0) { + setReferencedTaxonomies(rows?.map((r) => ({ label: r?.taxonomy_name, value: r?.taxonomy_uid }))); } else { // No existing taxonomies, clear the selection setReferencedTaxonomies(null); diff --git a/ui/src/components/ContentMapper/__tests__/groupSchema.utils.test.ts b/ui/src/components/ContentMapper/__tests__/groupSchema.utils.test.ts index 53fdf1dc4..ef6acad6a 100644 --- a/ui/src/components/ContentMapper/__tests__/groupSchema.utils.test.ts +++ b/ui/src/components/ContentMapper/__tests__/groupSchema.utils.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest'; import { shouldAddGroupOption, shouldRecurseIntoNestedDestGroup, + findGroupFieldInChildren, } from '../groupSchema.utils'; import type { FieldMapType, ExistingFieldType, ContentTypesSchema } from '../contentMapper.interface'; @@ -333,3 +334,46 @@ describe('shouldRecurseIntoNestedDestGroup', () => { }); }); }); + +// --------------------------------------------------------------------------- +// findGroupFieldInChildren +// --------------------------------------------------------------------------- + +describe('findGroupFieldInChildren', () => { + it('returns a group at the first level of children', () => { + const inner: FieldMapType = { + ...makeField('mb.quote.details', 'd_b'), + contentstackFieldType: 'group', + }; + const children: FieldMapType[] = [inner]; + expect(findGroupFieldInChildren(children, 'mb.quote.details')).toBe(inner); + }); + + it('finds a nested group when it is not a direct child', () => { + const details: FieldMapType = { + ...makeField('mb.quote.details', 'd_b'), + contentstackFieldType: 'group', + child: [], + }; + const quoteWrap: FieldMapType = { + ...makeField('mb.quote', 'q_b'), + contentstackFieldType: 'group', + child: [details], + }; + const children = [quoteWrap]; + expect(findGroupFieldInChildren(children, 'mb.quote.details')).toBe(details); + }); + + it('returns undefined when uid does not exist', () => { + expect(findGroupFieldInChildren([], 'x')).toBeUndefined(); + expect(findGroupFieldInChildren(undefined, 'x')).toBeUndefined(); + }); + + it('does not match non-group fields with the same uid', () => { + const leaf: FieldMapType = { + ...makeField('mb.quote.paragraph', 'p_b'), + contentstackFieldType: 'json', + }; + expect(findGroupFieldInChildren([leaf], 'mb.quote.paragraph')).toBeUndefined(); + }); +}); diff --git a/ui/src/components/ContentMapper/groupSchema.utils.ts b/ui/src/components/ContentMapper/groupSchema.utils.ts index fdd3dfd4f..1f34e5ffb 100644 --- a/ui/src/components/ContentMapper/groupSchema.utils.ts +++ b/ui/src/components/ContentMapper/groupSchema.utils.ts @@ -1,5 +1,25 @@ import { FieldMapType, ExistingFieldType } from './contentMapper.interface'; +/** + * Finds a source group field anywhere under a modular block child's `child` tree + * by uid. Direct `.find` on immediate children fails when groups are nested + * (e.g. quote → details → paragraph). + */ +export function findGroupFieldInChildren( + children: FieldMapType[] | undefined, + uid: string, +): FieldMapType | undefined { + if (!children?.length || !uid) return undefined; + for (const c of children) { + if (c?.uid === uid && c?.contentstackFieldType === 'group') { + return c; + } + const found = findGroupFieldInChildren(c?.child, uid); + if (found) return found; + } + return undefined; +} + /** * Determines whether a destination group field should be offered as a mapping * option for a source group field, by enforcing equal nesting depths. @@ -44,7 +64,9 @@ export function shouldRecurseIntoNestedDestGroup( } const parentSourceUid = sourceUidParts?.slice(0, -1)?.join('.'); + const parentSourceNode = nestedList?.find((item: FieldMapType) => item?.uid === parentSourceUid); + const parentMappedLabel = parentSourceNode?.backupFieldUid ? (existingField[parentSourceNode.backupFieldUid] as { label?: string })?.label : undefined; diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index ce3df6c62..09a207a19 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -483,4 +483,10 @@ div .table-row { .mapper-footer { padding: 10px; } +} + +.select { + .tippy-wrapper { + display: flex; + } } \ No newline at end of file diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 0798e395f..99d73fb11 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -1,5 +1,12 @@ // Libraries -import { useEffect, useState, useRef, useImperativeHandle, forwardRef } from 'react'; +import { + useEffect, + useState, + useRef, + useImperativeHandle, + forwardRef, + type ComponentProps, +} from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; import { @@ -68,12 +75,24 @@ import AdvanceSettings from '../AdvancePropertise'; import SaveChangesModal from '../Common/SaveChangesModal'; // Utilities -import { shouldAddGroupOption, shouldRecurseIntoNestedDestGroup } from './groupSchema.utils'; +import { + shouldAddGroupOption, + shouldRecurseIntoNestedDestGroup, + findGroupFieldInChildren, +} from './groupSchema.utils'; // Styles and Assets import './index.scss'; import { NoDataFound, SCHEMA_PREVIEW } from '../../common/assets'; +/** Renders the menu in the document body so `menuPlacement="auto"` matches the control when inside scroll/overflow containers (e.g. InfiniteScrollTable). */ +const CONTENT_MAPPER_SELECT_MENU_PORTAL = + typeof document !== 'undefined' ? document.body : undefined; + +const contentMapperSelectMenuStyles = { + menuPortal: (base: Record) => ({ ...base, zIndex: 10001 }), +}; + const rowHistoryObj: FieldHistoryObj = {} const Fields: MappingFields = { @@ -275,6 +294,74 @@ const flattenSchemaToUidMap = ( return result; }; +/** Match saved `contentstackField` labels against a modular-blocks subtree (block + fields + nested). */ +const matchRowAgainstModularBlocks = ( + row: FieldMapType, + mbFieldPath: string, + blocks: ContentTypesSchema[], + isFieldDeleted: boolean, + addMatch: (backupFieldUid: string, label: string, value: ContentTypesSchema) => void +) => { + if (!blocks?.length) return; + + for (const block of blocks) { + const blockTitle = block?.uid || block?.display_name; + const blockDisplayName = `${mbFieldPath} > ${blockTitle}`; + + if (row?.contentstackField === blockDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, blockDisplayName, block as unknown as ContentTypesSchema); + } + + if (block?.schema) { + for (const blockField of block.schema) { + const fieldDisplayName = `${blockDisplayName} > ${blockField?.display_name}`; + + if (row?.contentstackField === fieldDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, fieldDisplayName, blockField); + } + + if (blockField?.schema) { + for (const nestedField of blockField.schema) { + const nestedDisplayName = `${fieldDisplayName} > ${nestedField?.display_name}`; + if (row?.contentstackField === nestedDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, nestedDisplayName, nestedField); + } + } + } + + if (blockField?.data_type === 'blocks' && blockField?.blocks) { + for (const nestedBlock of blockField.blocks as ContentTypesSchema[]) { + const nestedBlockTitle = nestedBlock?.uid || nestedBlock?.display_name; + const nestedBlockDisplayName = `${fieldDisplayName} > ${nestedBlockTitle}`; + + if (row?.contentstackField === nestedBlockDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, nestedBlockDisplayName, nestedBlock as ContentTypesSchema); + } + + if (nestedBlock?.schema) { + for (const nestedBlockField of nestedBlock.schema) { + const nestedFieldDisplayName = `${nestedBlockDisplayName} > ${nestedBlockField?.display_name}`; + if (row?.contentstackField === nestedFieldDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, nestedFieldDisplayName, nestedBlockField); + } + + if (nestedBlockField?.schema) { + for (const deepField of nestedBlockField.schema) { + const deepDisplayName = `${nestedFieldDisplayName} > ${deepField?.display_name}`; + if (row?.contentstackField === deepDisplayName && !isFieldDeleted) { + addMatch(row?.backupFieldUid, deepDisplayName, deepField); + } + } + } + } + } + } + } + } + } + } +}; + const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: React.ForwardedRef) => { /** ALL CONTEXT HERE */ @@ -358,6 +445,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const filterRef = useRef(null); const tableWrapperRef = useRef(null); const clearedFieldsRef = useRef>(new Set()); + const prevOtherContentTypeIdRef = useRef(undefined); /********** ALL USEEFFECT HERE *************/ useEffect(() => { @@ -435,195 +523,109 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: // useEffect for rendering mapped fields with existing stack useEffect(() => { + if (!contentTypeSchema || contentTypeSchema.length === 0) return; + if (newMigrationData?.content_mapping?.content_type_mapping?.[selectedContentType?.contentstackUid || ''] !== otherContentType?.id) return; + const nextExistingField: ExistingFieldType = { ...existingField }; + const nextSelectedOptions: string[] = [...selectedOptions]; + let anyMatch = false; - if (newMigrationData?.content_mapping?.content_type_mapping?.[selectedContentType?.contentstackUid || ''] === otherContentType?.id) { - setIsAllCheck(false); - - tableData?.forEach((row) => { - contentTypeSchema?.forEach((schema) => { - - if (row?.contentstackField === schema?.display_name) { - if (!updatedSelectedOptions?.includes?.(schema?.display_name)) { - updatedSelectedOptions.push(schema?.display_name); - } - updatedExstingField[row?.backupFieldUid] = { - label: schema?.display_name, - value: schema - }; - } - - // 1st level group nesting - if (schema?.schema) { - schema?.schema?.forEach((childSchema) => { - if (row?.contentstackField === `${schema?.display_name} > ${childSchema?.display_name}`) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name}`)) { - updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name}`); - } - updatedExstingField[row?.backupFieldUid] = { - label: `${schema?.display_name} > ${childSchema?.display_name}`, - value: childSchema - } - } - } + const addMatch = (backupFieldUid: string, label: string, value: ContentTypesSchema) => { + if (!nextSelectedOptions.includes(label)) { + nextSelectedOptions.push(label); + } + nextExistingField[backupFieldUid] = { label, value }; + anyMatch = true; + }; - // 2nd level group nesting - if (childSchema?.schema) { - childSchema?.schema?.forEach((nestedSchema) => { - if (row?.contentstackField === `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`)) { - updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`); - } - updatedExstingField[row?.backupFieldUid] = { - label: `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name}`, - value: nestedSchema - } - } - } + setIsAllCheck(false); - // 3rd level group nesting - if (nestedSchema?.schema) { - nestedSchema?.schema?.forEach((nestedChild) => { - if (row?.contentstackField === `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`)) { - updatedSelectedOptions.push(`${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`); - } - updatedExstingField[row?.backupFieldUid] = { - label: `${schema?.display_name} > ${childSchema?.display_name} > ${nestedSchema?.display_name} > ${nestedChild?.display_name}`, - value: nestedChild - } - } - } - }) - } - }) - } + tableData?.forEach((row) => { + if (!row?.contentstackField || row?.contentstackField === row?.otherCmsField) return; - // Modular blocks mapping - if (schema?.data_type === 'blocks' && schema?.blocks) { - schema?.blocks?.forEach((block) => { - const blockTitle = block?.uid || block?.display_name; - const blockDisplayName = `${schema?.display_name} > ${blockTitle}`; - - // Modular block child - if (row?.contentstackField === blockDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(blockDisplayName)) { - updatedSelectedOptions.push(blockDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: blockDisplayName, - value: block - }; - } - } + contentTypeSchema?.forEach((schema) => { + if (row?.contentstackField === schema?.display_name) { + addMatch(row?.backupFieldUid, schema?.display_name, schema); + } - // Fields within modular block child - if (block?.schema) { - block?.schema?.forEach((blockField) => { - const fieldDisplayName = `${blockDisplayName} > ${blockField?.display_name}`; - - if (row?.contentstackField === fieldDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(fieldDisplayName)) { - updatedSelectedOptions?.push(fieldDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: fieldDisplayName, - value: blockField - }; - } - } + // Root-level modular blocks: Contentstack uses `blocks`, not `schema` — must not be nested under group-only handling + if (schema?.data_type === 'blocks' && schema?.blocks) { + matchRowAgainstModularBlocks( + row, + schema?.display_name ?? '', + schema.blocks as ContentTypesSchema[], + isFieldDeleted, + addMatch + ); + } - // Nested group within modular block child field - if (blockField?.schema) { - blockField?.schema?.forEach((nestedField) => { - const nestedDisplayName = `${fieldDisplayName} > ${nestedField?.display_name}`; + // 1st level group nesting + if (schema?.schema) { + schema?.schema?.forEach((childSchema) => { + const label1 = `${schema?.display_name} > ${childSchema?.display_name}`; + if (row?.contentstackField === label1 && !isFieldDeleted) { + addMatch(row?.backupFieldUid, label1, childSchema); + } - if (row?.contentstackField === nestedDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(nestedDisplayName)) { - updatedSelectedOptions?.push(nestedDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: nestedDisplayName, - value: nestedField - }; - } - } - }); - } + // Modular blocks field nested inside a group + if (childSchema?.data_type === 'blocks' && childSchema?.blocks) { + matchRowAgainstModularBlocks( + row, + label1, + childSchema.blocks as ContentTypesSchema[], + isFieldDeleted, + addMatch + ); + } - // Nested modular blocks within child block field - if (blockField?.data_type === 'blocks' && blockField?.blocks) { - blockField?.blocks?.forEach((nestedBlock: any) => { - const nestedBlockTitle = nestedBlock?.uid || nestedBlock?.display_name; - const nestedBlockDisplayName = `${fieldDisplayName} > ${nestedBlockTitle}`; + // 2nd level group nesting + if (childSchema?.schema) { + childSchema?.schema?.forEach((nestedSchema) => { + const label2 = `${label1} > ${nestedSchema?.display_name}`; + if (row?.contentstackField === label2 && !isFieldDeleted) { + addMatch(row?.backupFieldUid, label2, nestedSchema); + } - if (row?.contentstackField === nestedBlockDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(nestedBlockDisplayName)) { - updatedSelectedOptions?.push(nestedBlockDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: nestedBlockDisplayName, - value: nestedBlock - }; - } - } - - if (nestedBlock?.schema) { - nestedBlock?.schema?.forEach((nestedBlockField: any) => { - const nestedFieldDisplayName = `${nestedBlockDisplayName} > ${nestedBlockField?.display_name}`; - - if (row?.contentstackField === nestedFieldDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(nestedFieldDisplayName)) { - updatedSelectedOptions?.push(nestedFieldDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: nestedFieldDisplayName, - value: nestedBlockField - }; - } - } + // 3rd level group nesting + if (nestedSchema?.schema) { + nestedSchema?.schema?.forEach((nestedChild) => { + const label3 = `${label2} > ${nestedChild?.display_name}`; + if (row?.contentstackField === label3 && !isFieldDeleted) { + addMatch(row?.backupFieldUid, label3, nestedChild); + } - if (nestedBlockField?.schema) { - nestedBlockField?.schema?.forEach((deepField: any) => { - const deepDisplayName = `${nestedFieldDisplayName} > ${deepField?.display_name}`; - - if (row?.contentstackField === deepDisplayName) { - if (!isFieldDeleted) { - if (!updatedSelectedOptions?.includes?.(deepDisplayName)) { - updatedSelectedOptions?.push(deepDisplayName); - } - updatedExstingField[row?.backupFieldUid] = { - label: deepDisplayName, - value: deepField - }; - } - } - }); - } - }); - } - }); - } - }); - } - }); - } - }); - } - }); + // Deeper nesting: modular blocks inside 3rd-level group field + if (nestedChild?.data_type === 'blocks' && nestedChild?.blocks) { + const mbPath = `${label3}`; + matchRowAgainstModularBlocks( + row, + mbPath, + nestedChild.blocks as ContentTypesSchema[], + isFieldDeleted, + addMatch + ); + } + }); + } + }); + } + }); + } }); - setSelectedOptions(updatedSelectedOptions); - setExistingField(updatedExstingField); + }); + + if (anyMatch) { + setSelectedOptions(nextSelectedOptions); + setExistingField(nextExistingField); } - }, [tableData, otherContentType]); + }, [ + tableData, + otherContentType?.id, + contentTypeSchema, + newMigrationData, + selectedContentType?.contentstackUid, + isFieldDeleted, + ]); useEffect(() => { if (isUpdated) { @@ -636,12 +638,15 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } setIsUpdated(false); } - else { + else if ( + prevOtherContentTypeIdRef.current !== undefined && + prevOtherContentTypeIdRef.current !== otherContentType?.id + ) { setIsAllCheck(false); setExistingField({}); setSelectedOptions([]); - } + prevOtherContentTypeIdRef.current = otherContentType?.id; }, [isUpdated, otherContentType]); // To make all the fields checked @@ -1515,6 +1520,8 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: isClearable={false} options={option} menuPlacement="auto" + menuPortalTarget={CONTENT_MAPPER_SELECT_MENU_PORTAL} + styles={contentMapperSelectMenuStyles} isDisabled={ !(data?.contentstackFieldType === 'single_line_text' || data?.contentstackFieldType === 'multi_line_text' || data?.contentstackFieldType === 'html' || data?.contentstackFieldType === 'json') || @@ -1898,8 +1905,10 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const blockUid = `${uid}.${block?.uid}`; const parentBlockUid = data?.uid?.split('.')?.slice(0, -1)?.join('.'); + const parentBlockItem = tableData?.find(item => item?.uid === parentBlockUid); + const parentBlockKey = parentBlockItem?.backupFieldUid ?? parentBlockUid; - if (data?.backupFieldType === 'modular_blocks_child' && existingField[parentBlockUid]?.label === updatedDisplayName) { + if (data?.backupFieldType === 'modular_blocks_child' && existingField[parentBlockKey]?.label === updatedDisplayName) { const blockOption: ContentTypesSchema = { ...block, data_type: block?.data_type || undefined, @@ -1987,16 +1996,15 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } // Only process fields if current block matches the mapped block - if (mappedChildBlockTitle === blockTitle) { + if (mappedChildBlockTitle === blockTitle && existingChildBlockMapping?.label === blockDisplayName) { const parentDepth = dataParentChildBlockUid?.split('.')?.length ?? 0; const isDataInsideGroupField = (data?.uid?.split('.')?.length ?? 0) > parentDepth + 1; - for (const blockField of block?.schema ?? []) { const fieldTypeToMatch = Fields[data?.backupFieldType as keyof Mapping]?.type; if (!isDataInsideGroupField && checkConditions(fieldTypeToMatch, blockField, data) && blockField?.data_type !== 'group' && blockField?.data_type !== 'blocks') { const fieldDisplayName = `${blockDisplayName} > ${blockField?.display_name}`; const fieldUid = `${blockUid}.${blockField?.uid}`; - + OptionsForRow.push(getMatchingOption( blockField, true, @@ -2021,8 +2029,17 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: // Recursively process nested groups within block fields — group options are added inside processSchema's group handler if (blockField?.data_type === 'group' && blockField?.schema) { + const dataChildBlockUid = dataParentChildBlockUid; - const dataGroupUid = data?.uid?.split('.')?.slice(0, parentDepth + 1)?.join('.'); + // Parent source group uid for nestedList lookup: + // - Group rows map the group itself (full data.uid). + // - Leaf fields (e.g. paragraph under details) must use the immediate + // parent uid. slice(0, parentDepth + 1) breaks when multiple groups + // sit between the child block and the leaf (quote → details → paragraph). + const dataGroupUid = + data?.backupFieldType === 'group' && data?.contentstackFieldType === 'group' + ? data?.uid ?? '' + : data?.uid?.split('.')?.slice(0, -1)?.join('.') ?? ''; const modularBlock = nestedList?.find(item => item?.contentstackFieldType === 'modular_blocks' && @@ -2031,13 +2048,12 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const childBlock = modularBlock?.child?.find( (c: FieldMapType) => c?.uid === dataChildBlockUid ); - const groupField = childBlock?.child?.find( - (c: FieldMapType) => c?.uid === dataGroupUid && c?.contentstackFieldType === 'group' - ); + const groupField = findGroupFieldInChildren(childBlock?.child, dataGroupUid); const groupChildren = groupField?.child || []; const groupArr = groupField ? [groupField] : []; - + + processSchema( blockField, data, @@ -2074,7 +2090,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: return OptionsForRow; } else if (value?.data_type === 'group') { - + if (data?.backupFieldType === 'group' && checkConditions('Group', value, data) ) { if (shouldAddGroupOption(data?.uid ?? '', parentUid)) { const newOption = getMatchingOption(value, true, updatedDisplayName, uid ?? ''); @@ -2086,7 +2102,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } } } - + const existingLabel = existingField[groupArray?.[0]?.backupFieldUid]?.label ?? ''; const lastLabelSegment = existingLabel?.includes('>') @@ -2094,6 +2110,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: : existingLabel; if (value?.display_name === lastLabelSegment) { + const groupUid = groupArray?.[0]?.uid ?? ''; const groupDepth = groupUid?.split('.')?.length ?? 0; @@ -2101,7 +2118,6 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const fieldTypeToMatch = Fields[item?.backupFieldType as keyof Mapping]?.type; const itemDepth = item?.uid?.split('.')?.length ?? 0; const isRootLevelChild = itemDepth === groupDepth + 1; - if (item?.id === data?.id && isRootLevelChild) { for (const key of existingField[groupArray?.[0]?.backupFieldUid]?.value?.schema || []) { if (checkConditions(fieldTypeToMatch, key, item)) { @@ -2118,7 +2134,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: for (const key of existingField[groupArray?.[0]?.backupFieldUid]?.value?.schema || []) { if (key?.data_type === 'group') { - + const nestedGroupUid = data?.uid?.split('.')?.slice(0, groupDepth + 1)?.join('.'); const nestedGroupField = groupArray?.[0]?.child?.find( @@ -2133,6 +2149,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } else { + if (shouldRecurseIntoNestedDestGroup(data?.uid ?? '', updatedDisplayName, nestedList ?? [], existingField)) { for (const key of value?.schema || []) { if (key?.data_type === 'group') { @@ -2169,6 +2186,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: // Recursively process nested groups if (key?.data_type === 'group') { + processSchema(key, data, array, groupArray, OptionsForRow, fieldsOfContentstack, updatedDisplayName, uid); } } @@ -2392,28 +2410,41 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const isTypeMatch = checkConditions(Fields[data?.contentstackFieldType]?.type, existingField[data?.backupFieldUid]?.value, data); + const selectValueIsExistingField = + OptionsForRow?.length !== 0 && + isTypeMatch && + existingField?.[data?.backupFieldUid]?.label !== undefined; + return (
- { + if (OptionsForRow?.length === 0) { + handleValueChange(selectedOption, data?.uid, data?.backupFieldUid) + } else { + handleFieldChange(selectedOption, data?.uid, data?.contentstackFieldUid, data?.backupFieldUid) + } + }} + placeholder="Select Field" + version={'v2'} + maxWidth="290px" + isClearable={isTypeMatch && selectedOptions?.includes?.(existingField?.[data?.backupFieldUid]?.label ?? '')} + options={adjustedOptions} + isDisabled={OptionValue?.isDisabled || newMigrationData?.project_current_step > 4} + menuPlacement="auto" + menuPortalTarget={CONTENT_MAPPER_SELECT_MENU_PORTAL} + styles={contentMapperSelectMenuStyles} + /> +
{(!OptionValue?.isDisabled || OptionValue?.label === 'Dropdown' || (data?.backupFieldType !== 'extension' && diff --git a/upload-api/migration-aem/package-lock.json b/upload-api/migration-aem/package-lock.json index 28c9def83..79a596151 100644 --- a/upload-api/migration-aem/package-lock.json +++ b/upload-api/migration-aem/package-lock.json @@ -108,6 +108,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -755,6 +766,14 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/upload-api/migration-contentful/index.js b/upload-api/migration-contentful/index.js index 045af75ec..d38b50625 100644 --- a/upload-api/migration-contentful/index.js +++ b/upload-api/migration-contentful/index.js @@ -3,9 +3,11 @@ const extractContentTypes = require('./libs/extractContentTypes'); const createInitialMapper = require('./libs/createInitialMapper'); const extractLocale = require('./libs/extractLocale'); +const extractTaxonomy = require('./libs/extractTaxonomy'); module.exports = { extractContentTypes, createInitialMapper, - extractLocale + extractLocale, + extractTaxonomy }; diff --git a/upload-api/migration-contentful/libs/createInitialMapper.js b/upload-api/migration-contentful/libs/createInitialMapper.js index 331359311..3a3cdc761 100644 --- a/upload-api/migration-contentful/libs/createInitialMapper.js +++ b/upload-api/migration-contentful/libs/createInitialMapper.js @@ -9,6 +9,54 @@ const path = require('path'); // const contentTypeMapper = require('./contentTypeMapper'); const contentTypeMapper = require('./contentTypeMapper'); +/** Contentstack taxonomy_uid: lowercase, a-z0-9_ only */ +function contentfulSchemeIdToStackTaxonomyUid(contentfulSchemeId) { + if (!contentfulSchemeId || typeof contentfulSchemeId !== 'string') return ''; + return contentfulSchemeId + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +/** + * Maps Contentful content-type metadata.taxonomy (TaxonomyConceptScheme links) to a Contentstack taxonomy field. + * Field uid must be `taxonomies` Taxonomy fields must be localizable. + * @param {object|undefined} metadata - Content type `metadata` from export JSON. + * @returns {object[]} Field mapping rows (empty if no taxonomy). + */ +const buildContentfulTaxonomyFields = (metadata) => { + const links = metadata?.taxonomy; + if (!Array.isArray(links) || !links.length) return []; + const schemes = links + .map((t) => contentfulSchemeIdToStackTaxonomyUid(t?.sys?.id)) + .filter(Boolean); + if (!schemes.length) return []; + return [ + { + uid: 'taxonomies', + otherCmsField: 'Contentful taxonomy (metadata)', + otherCmsType: 'TaxonomyMetadata', + contentstackField: 'Taxonomies', + contentstackFieldUid: 'taxonomies', + contentstackFieldType: 'taxonomy', + backupFieldType: 'taxonomy', + backupFieldUid: 'taxonomies', + advanced: { + taxonomies: schemes.map((schemeUid) => ({ + taxonomy_uid: schemeUid, + mandatory: false, + multiple: true, + non_localizable: false + })), + mandatory: false, + multiple: true, + nonLocalizable: false + } + } + ]; +}; /** * Internal module dependencies. @@ -65,7 +113,14 @@ const uidCorrector = (uid, prefix) => { const createInitialMapper = async (cleanLocalPath, affix) => { try { const alldata = readFile(cleanLocalPath); - const { entries } = alldata; + const { entries, contentTypes: exportContentTypes = [] } = alldata; + + const ctMetaById = {}; + for (const ct of exportContentTypes) { + if (ct?.sys?.id) { + ctMetaById[ct.sys.id] = ct.metadata || {}; + } + } const initialMapper = []; const files = await fs.readdir( @@ -113,7 +168,10 @@ const createInitialMapper = async (cleanLocalPath, affix) => { advanced: { mandatory: true } } ]; - const contentstackFields = [...uidTitle, ...contentTypeMapper(data, entries)]?.filter?.( + const ctId = data?.[0]?.contentfulID; + const ctMetadata = ctMetaById[ctId] || {}; + const taxonomyRows = buildContentfulTaxonomyFields(ctMetadata); + const contentstackFields = [...uidTitle, ...contentTypeMapper(data, entries), ...taxonomyRows]?.filter?.( Boolean ); diff --git a/upload-api/migration-contentful/libs/extractTaxonomy.js b/upload-api/migration-contentful/libs/extractTaxonomy.js new file mode 100644 index 000000000..91f222a0d --- /dev/null +++ b/upload-api/migration-contentful/libs/extractTaxonomy.js @@ -0,0 +1,59 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const fs = require('fs'); +const path = require('path'); + +function contentfulSchemeIdToStackTaxonomyUid(contentfulSchemeId) { + if (!contentfulSchemeId || typeof contentfulSchemeId !== 'string') return ''; + return contentfulSchemeId + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +/** Display name for mapper UI (product_category -> Product Category). */ +function humanizeSchemeId(id) { + if (!id || typeof id !== 'string') return ''; + return id + .split('_') + .filter(Boolean) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join(' '); +} + +/** + * Collects unique TaxonomyConceptScheme ids from Contentful export content types (`metadata.taxonomy`). + * + * @param {string} filePath - Absolute path to the Contentful export JSON. + * @returns {Promise>} + */ +const extractTaxonomy = async (filePath) => { + const raw = await fs.promises.readFile(filePath, 'utf8'); + const data = JSON.parse(raw); + const contentTypes = data?.contentTypes || []; + const schemeIds = new Set(); + + for (const ct of contentTypes) { + const links = ct?.metadata?.taxonomy; + if (!Array.isArray(links)) continue; + for (const link of links) { + const sid = link?.sys?.id; + if (sid) schemeIds.add(contentfulSchemeIdToStackTaxonomyUid(sid)); + } + } + + const taxonomySchema = [...schemeIds].sort().map((uid) => ({ + uid, + name: humanizeSchemeId(uid) || uid, + })); + + const outputDir = path.join(process.cwd(), 'contentfulMigrationData', 'taxonomySchema'); + await fs.promises.mkdir(outputDir, { recursive: true }); + const outputPath = path.join(outputDir, 'taxonomySchema.json'); + await fs.promises.writeFile(outputPath, JSON.stringify(taxonomySchema, null, 2)); + + return taxonomySchema; +}; + +module.exports = extractTaxonomy; diff --git a/upload-api/migration-wordpress/libs/extractItems.ts b/upload-api/migration-wordpress/libs/extractItems.ts index da0948bfc..3b31c8aeb 100644 --- a/upload-api/migration-wordpress/libs/extractItems.ts +++ b/upload-api/migration-wordpress/libs/extractItems.ts @@ -5,7 +5,7 @@ import * as cheerio from 'cheerio'; import { setupWordPressBlocks } from "../utils/parseUtil"; -import { getFieldName, getFieldUid, schemaMapper } from "./schemaMapper"; +import { clientIdForUid, getFieldName, getFieldUid, schemaMapper } from "./schemaMapper"; import helper from "../utils/helper"; import config from '../config/index.json'; import extractTaxonomy from './extractTaxonomy'; @@ -25,6 +25,15 @@ function resolveBlockName(field: any): string { const { contentTypes: contentTypesConfig } = config?.modules; const contentTypeFolderPath = path.resolve(config?.data, contentTypesConfig?.dirName); +const blocksJsonOutputDir = path.resolve(config?.data, 'wordpress_blocks'); + +function sanitizeBlocksJsonFileName(title: string, maxLen = 80): string { + return String(title || 'untitled') + .replace(/[<>:"/\\|?*\x00-\x1f]/g, '_') + .trim() + .replace(/\s+/g, '_') + .slice(0, maxLen); +} function findSimilarBlocks(data: any[][], targetId: string) { for (const group of data) { @@ -276,7 +285,8 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix: }, }; - for (const data of item) { + for (let itemIndex = 0; itemIndex < item?.length; itemIndex++) { + const data = item[itemIndex]; const processedSimilarBlocks = new Set(); const targetItem = items?.filter((i, el) => { return $(el)?.find("title")?.text() === data?.title; @@ -306,7 +316,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix: const contentEncoded = targetItem?.find("content\\:encoded")?.text() || ''; const blocksJson = await setupWordPressBlocks(contentEncoded); - + // Example usage @@ -317,7 +327,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix: // Track processed similar blocks to avoid duplicates for (const field of blocksJson) { - const fieldUid = getFieldUid(`${field?.name}_${field?.clientId}`|| '', affix || ''); + const fieldUid = getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`|| '', affix || ''); const contentstackFieldName = getFieldName(resolveBlockName(field)); const similarBlocks = findSimilarBlocks(result, field?.clientId); @@ -380,7 +390,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix: // No duplicate found - add the modular block child if(Schema?.length > 0){ CT?.push?.({ - "uid": `modular_blocks.${getFieldUid(`${field?.name}_${field?.clientId}`, affix)}`, + "uid": `modular_blocks.${getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`, affix)}`, "backupFieldUid": `modular_blocks.${fieldUid}`, "contentstackFieldUid": `modular_blocks.${fieldUid}`, "otherCmsField": contentstackFieldName, @@ -436,7 +446,7 @@ const extractItems = async (item: any, config: DataConfig, type: string, affix: // No duplicate found - add the modular block child if(Schema?.length > 0){ CT?.push?.({ - "uid": `modular_blocks.${getFieldUid(`${field?.name}_${field?.clientId}`, affix)}`, + "uid": `modular_blocks.${getFieldUid(`${field?.name}_${clientIdForUid(field?.clientId)}`, affix)}`, "backupFieldUid": `modular_blocks.${fieldUid}`, "contentstackFieldUid": `modular_blocks.${fieldUid}`, "otherCmsField": contentstackFieldName, diff --git a/upload-api/migration-wordpress/libs/extractTaxonomy.ts b/upload-api/migration-wordpress/libs/extractTaxonomy.ts index 55b2f74c8..a1bf0c1bb 100644 --- a/upload-api/migration-wordpress/libs/extractTaxonomy.ts +++ b/upload-api/migration-wordpress/libs/extractTaxonomy.ts @@ -10,6 +10,7 @@ const handleTaxonomySchema = async(categories: any, allCategories : Categories[] taxonomyArray?.push( { "taxonomy_uid": `${categoryData?.["wp:category_nicename"]}_${categoryData?.["wp:term_id"]}`, + "taxonomy_name": categoryData?.["wp:cat_name"], "mandatory": false, "multiple": true, "non_localizable": false @@ -20,6 +21,7 @@ const handleTaxonomySchema = async(categories: any, allCategories : Categories[] const parentCategory = allCategories?.find((category: any) => category?.["wp:category_nicename"] === categoryData?.['wp:category_parent']); taxonomyArray?.push({ "taxonomy_uid": `${parentCategory?.["wp:category_nicename"]}_${parentCategory?.["wp:term_id"]}`, + "taxonomy_name": parentCategory?.["wp:cat_name"], "mandatory": false, "multiple": true, "non_localizable": false diff --git a/upload-api/migration-wordpress/libs/schemaMapper.ts b/upload-api/migration-wordpress/libs/schemaMapper.ts index 25328fad0..9bb88ee97 100644 --- a/upload-api/migration-wordpress/libs/schemaMapper.ts +++ b/upload-api/migration-wordpress/libs/schemaMapper.ts @@ -43,6 +43,13 @@ const getFieldUid = (key: string, affix: string) => { return isPresent ? `${affix}_${uid}` : uid; }; + +/** First 4 chars of clientId (hyphens stripped) — short UIDs; tiny collision risk on huge pages. */ +export function clientIdForUid(clientId: string | undefined): string { + if (!clientId) return '0'; + const compact = clientId?.replace?.(/-/g, '')?.toLowerCase(); + return compact?.slice?.(0, 4) || '0'; +} async function processInnerBlocks(key: WordPressBlock, parentUid: string | null = null, parentFieldName: string | null = null, affix: string | null = null): Promise { @@ -201,8 +208,8 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/verse': case 'core/code': { const rteUid = parentUid ? - `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` - : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` + : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); return { uid: rteUid, otherCmsField: getFieldName(key?.name), @@ -217,8 +224,8 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: } case 'core/missing': const rteUid = parentUid ? - `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` - : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` + : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); if(key?.attributes?.originalName === 'jetpack/markdown'){ return { uid: rteUid, @@ -248,7 +255,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/audio': case 'core/video': case 'core/file': { - const fileUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const fileUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); return { uid: fileUid, @@ -266,7 +273,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/heading': case 'core/accordion-heading': case 'core/list-item': { - const textUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const textUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); return { uid: textUid, otherCmsField: getFieldName(key?.name), @@ -283,7 +290,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/social-link': case 'core/navigation-link': { - const LinkUid = parentUid ? `${parentUid}.${getFieldUid(key?.name, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const LinkUid = parentUid ? `${parentUid}.${getFieldUid(key?.name, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); return { uid: LinkUid, otherCmsField: getFieldName(key?.name), @@ -307,7 +314,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/accordion-panel': case 'core/navigation': { const groupSchema: Field[] = []; - const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); const innerBlocks = await processInnerBlocks( key, @@ -346,7 +353,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: } case 'core/search': { - const searchEleUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const searchEleUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); const searchEle = await processAttributes(key, searchEleUid,fieldName, affix); const groupSchema: Field[] = []; searchEle?.length > 0 && groupSchema?.push({ @@ -374,7 +381,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/button': { const fieldName = parentFieldName ? `${parentFieldName} > ${getFieldName(key?.attributes?.metadata?.name ?? key?.name)}` : `${getFieldName(key?.attributes?.metadata?.name ?? key?.name)}` ; - const buttonUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const buttonUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); return { uid: buttonUid, @@ -392,7 +399,7 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: case 'core/buttons': { const groupSchema: Field[] = []; - const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` : getFieldUid(`${key?.name}_${key?.clientId}`, affix); + const groupUid = parentUid ? `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}` : getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix); const innerBlocks = await processInnerBlocks( key, @@ -402,11 +409,11 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: ); if (innerBlocks?.length === 1) { const items = Array.isArray(innerBlocks[0]) ? innerBlocks[0] : [innerBlocks[0]]; - items.forEach((item: Field) => { - item.uid = `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}`, + items?.forEach((item: Field) => { + item.uid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`; item.contentstackField = `${parentFieldName} > ${getFieldName(resolveBlockName(key))}`; - item.contentstackFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}`, - item.backupFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${key?.clientId}`, affix)}` + item.contentstackFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`; + item.backupFieldUid = `${parentUid}.${getFieldUid(`${key?.name}_${clientIdForUid(key?.clientId)}`, affix)}`; }); return items; } @@ -444,4 +451,4 @@ async function schemaMapper (key: WordPressBlock | WordPressBlock[], parentUid: return []; } -export { getFieldName, getFieldUid ,schemaMapper, handleAttributesSchema}; \ No newline at end of file +export { getFieldName, getFieldUid, schemaMapper, handleAttributesSchema }; \ No newline at end of file diff --git a/upload-api/src/config/index.ts b/upload-api/src/config/index.ts index adf727923..4bde1a7e6 100644 --- a/upload-api/src/config/index.ts +++ b/upload-api/src/config/index.ts @@ -23,5 +23,5 @@ export default { base_url: process.env.DRUPAL_ASSETS_BASE_URL || 'drupal_assets_base_url', public_path: process.env.DRUPAL_ASSETS_PUBLIC_PATH || 'drupal_assets_public_path' }, - localPath: process.env.CMS_LOCAL_PATH || process.env.CONTAINER_PATH || 'localPath', + localPath: process.env.CMS_LOCAL_PATH || process.env.CONTAINER_PATH || 'your_local_cms_data_path', }; diff --git a/upload-api/src/services/contentful/index.ts b/upload-api/src/services/contentful/index.ts index 55db118e7..6a36ab389 100644 --- a/upload-api/src/services/contentful/index.ts +++ b/upload-api/src/services/contentful/index.ts @@ -1,11 +1,18 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; import logger from '../../utils/logger'; import { HTTP_CODES, HTTP_TEXTS } from '../../constants'; import { Config } from '../../models/types'; -const { extractContentTypes, createInitialMapper, extractLocale } = require('migration-contentful'); +const { + extractContentTypes, + createInitialMapper, + extractLocale, + extractTaxonomy +} = require('migration-contentful'); const createContentfulMapper = async ( projectId: string | string[], @@ -20,6 +27,25 @@ const createContentfulMapper = async ( await extractContentTypes(cleanLocalPath, affix); const initialMapper = await createInitialMapper(cleanLocalPath, affix); + // Must run after createInitialMapper: that step deletes contentfulMigrationData (contentfulSchema) and would remove taxonomy files written earlier. + await extractTaxonomy(cleanLocalPath); + + let taxonomies: any[] = []; + try { + const taxonomyPath = path.join( + process.cwd(), + 'contentfulMigrationData', + 'taxonomySchema', + 'taxonomySchema.json' + ); + if (fs.existsSync(taxonomyPath)) { + const taxonomyData = await fs.promises.readFile(taxonomyPath, 'utf8'); + taxonomies = JSON.parse(taxonomyData); + logger.info(`Loaded ${taxonomies.length} Contentful taxonomies to send to API`); + } + } catch (error: any) { + logger.warn(`Could not read Contentful taxonomies: ${error.message}`); + } const req = { method: 'post', maxBodyLength: Infinity, @@ -28,7 +54,10 @@ const createContentfulMapper = async ( app_token, 'Content-Type': 'application/json' }, - data: JSON.stringify(initialMapper) + data: JSON.stringify({ + ...initialMapper, + taxonomies + }) }; const { data} = await axios.request(req); if (data?.data?.content_mapper?.length) { diff --git a/upload-api/tests/unit/migration-wordpress/schemaMapper.test.ts b/upload-api/tests/unit/migration-wordpress/schemaMapper.test.ts index 38f41ccf4..1c91d0b86 100644 --- a/upload-api/tests/unit/migration-wordpress/schemaMapper.test.ts +++ b/upload-api/tests/unit/migration-wordpress/schemaMapper.test.ts @@ -212,9 +212,9 @@ describe('schemaMapper', () => { const result = await schemaMapper(block, 'page', 'Page', affix); result.forEach((field: any) => { - expect(field.contentstackFieldUid).toBe(`page.${getFieldUid('core/buttons_btns1', affix)}`); - expect(field.uid).toBe(`page.${getFieldUid('core/buttons_btns1', affix)}`); - expect(field.backupFieldUid).toBe(`page.${getFieldUid('core/buttons_btns1', affix)}`); + expect(field.contentstackFieldUid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`); + expect(field.uid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`); + expect(field.backupFieldUid).toBe(`page.${getFieldUid('core/buttons_btns', affix)}`); expect(field.contentstackField).toContain('Page > '); }); }); From 00b2f3854bad851b3b80d4ff6d26066a3ad4efdd Mon Sep 17 00:00:00 2001 From: Aishwarya Date: Thu, 23 Apr 2026 19:39:05 +0530 Subject: [PATCH 2/3] docs: update README with installation instructions and usage guidelines --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa7e06ced..6ba74491b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,19 @@ Check for readme.md files and install dependencies for folders This is the migration V2's node server. +### Installation + +1. Navigate to the project directory: + + ```sh + cd api + ``` + +2. Install the dependencies: + ```sh + npm install + ``` + ### Environment Variables The following environment variables are used in this project: @@ -105,7 +118,7 @@ The migration-v2 upload-api project is designed to facilitate the migration of d Navigate to the project directory: ``` -cd migration-v2/upload-api +cd upload-api ``` Install dependencies: @@ -155,6 +168,14 @@ The following configuration is used in this project: - `npm run postinstall`: Installs dependencies for the api, ui, and upload-api directories. - `npm test`: Displays an error message indicating that no tests are specified. +### Usage + +Start the development server: + +```sh +npm start +``` + ## Repository - Type: git From cd96132e6589cae784305d4c85b21bb607f0a89a Mon Sep 17 00:00:00 2001 From: Aishwarya Date: Thu, 23 Apr 2026 19:51:40 +0530 Subject: [PATCH 3/3] docs: clarify README environment variable instructions and add code block for server start command --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ba74491b..1ffb51323 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,16 @@ The following environment variables are used in this project: - `APP_TOKEN_KEY`: The token key for the application. Default is `MIGRATION_V2`. - `PORT`: The port number on which the application runs. Default is `5001`. -Make sure to set these variables in a `.env` file at the root of your project. +Make sure to set these variables in a `.env` file at the root of your api project. 1. To run the development server, create a `./development.env` file and add environment variables as per `./example.env` 2. To run the production server, create a `./production.env` file and add environment variables as per `./example.env` ### To start the server +```sh Run `npm run dev` +``` ## Migration UI @@ -134,7 +136,7 @@ The following environment variables are used in this project: - `PORT`: The port number on which the application runs. Default is `4002`. - `NODE_BACKEND_API`: The backend API endpoint. Default is `http://localhost:5001`. -Make sure to set these variables in a `.env` file at the root of your project. +Make sure to set these variables in a `.env` file at the root of your upload-api project. ### Configuration