diff --git a/package-lock.json b/package-lock.json index 3d69b66d7f..560081d042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -811,11 +811,11 @@ } }, "node_modules/@contentstack/management": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.10.2.tgz", - "integrity": "sha512-jO24EqcCJhOjqdsqw8y3T0SPPAd0DG4BByjUcV0S28W2yoa8aBbcjcbZioRPzRLYKTmZWsAZissl18cIJm5djQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.12.0.tgz", + "integrity": "sha512-+L+WVhSYEtfdG9v794TjLT8Fd6fCB8meqoho666mg1kNufzXcsqr7hjubX5cSL7GcZFdKntkDpZ2RaOnTHReJg==", "dependencies": { - "axios": "^1.4.0", + "axios": "^1.5.1", "form-data": "^3.0.1", "lodash": "^4.17.21", "qs": "^6.11.2" @@ -825,9 +825,9 @@ } }, "node_modules/@contentstack/management/node_modules/axios": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", - "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -2529,9 +2529,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==" }, "node_modules/@types/cli-progress": { "version": "3.11.0", @@ -4462,9 +4462,9 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -22764,26 +22764,26 @@ }, "packages/contentstack": { "name": "@contentstack/cli", - "version": "1.10.0", + "version": "1.11.0", "license": "MIT", "dependencies": { "@contentstack/cli-audit": "~1.2.0", "@contentstack/cli-auth": "~1.3.15", - "@contentstack/cli-cm-bootstrap": "~1.6.0", + "@contentstack/cli-cm-bootstrap": "~1.6.1", "@contentstack/cli-cm-branches": "~1.0.15", "@contentstack/cli-cm-bulk-publish": "~1.3.13", - "@contentstack/cli-cm-clone": "~1.6.0", - "@contentstack/cli-cm-export": "~1.9.2", - "@contentstack/cli-cm-export-to-csv": "~1.4.4", - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-clone": "~1.7.0", + "@contentstack/cli-cm-export": "~1.10.0", + "@contentstack/cli-cm-export-to-csv": "~1.5.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-cm-migrate-rte": "~1.4.13", - "@contentstack/cli-cm-seed": "~1.6.0", + "@contentstack/cli-cm-seed": "~1.6.1", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-config": "~1.4.13", "@contentstack/cli-launch": "~1.0.13", - "@contentstack/cli-migration": "~1.3.14", + "@contentstack/cli-migration": "~1.4.0", "@contentstack/cli-utilities": "~1.5.4", - "@contentstack/management": "~1.10.0", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", "@oclif/plugin-not-found": "^2.4.0", @@ -23522,10 +23522,10 @@ }, "packages/contentstack-bootstrap": { "name": "@contentstack/cli-cm-bootstrap", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-seed": "~1.6.0", + "@contentstack/cli-cm-seed": "~1.6.1", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "inquirer": "8.2.4", @@ -23711,12 +23711,12 @@ }, "packages/contentstack-clone": { "name": "@contentstack/cli-cm-clone", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { "@colors/colors": "^1.5.0", - "@contentstack/cli-cm-export": "~1.9.0", - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-export": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "async": "^3.2.4", @@ -24135,7 +24135,7 @@ }, "packages/contentstack-export": { "name": "@contentstack/cli-cm-export", - "version": "1.9.2", + "version": "1.10.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.2.14", @@ -24185,7 +24185,7 @@ }, "packages/contentstack-export-to-csv": { "name": "@contentstack/cli-cm-export-to-csv", - "version": "1.4.4", + "version": "1.5.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.2.14", @@ -24198,12 +24198,15 @@ }, "devDependencies": { "@oclif/test": "^2.2.10", - "chai": "^4.2.0", + "@types/chai": "^4.3.6", + "@types/mocha": "^10.0.1", + "chai": "^4.3.8", "debug": "^4.3.1", + "dotenv": "^16.3.1", "eslint": "^7.32.0", "eslint-config-oclif": "^4.0.0", "globby": "^10.0.2", - "mocha": "^10.0.0", + "mocha": "^10.2.0", "nyc": "^15.1.0", "oclif": "^3.8.1" }, @@ -24254,6 +24257,12 @@ "node": ">=10.10.0" } }, + "packages/contentstack-export-to-csv/node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "packages/contentstack-export-to-csv/node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -24282,6 +24291,32 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "packages/contentstack-export-to-csv/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "packages/contentstack-export-to-csv/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "packages/contentstack-export-to-csv/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "packages/contentstack-export-to-csv/node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -24395,6 +24430,26 @@ "node": ">=4" } }, + "packages/contentstack-export-to-csv/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/contentstack-export-to-csv/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -24422,6 +24477,22 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "packages/contentstack-export-to-csv/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/contentstack-export-to-csv/node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -24436,6 +24507,94 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/contentstack-export-to-csv/node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "packages/contentstack-export-to-csv/node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "packages/contentstack-export-to-csv/node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "packages/contentstack-export-to-csv/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "packages/contentstack-export-to-csv/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "packages/contentstack-export/node_modules/@oclif/test": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/@oclif/test/-/test-1.2.9.tgz", @@ -24450,12 +24609,12 @@ }, "packages/contentstack-import": { "name": "@contentstack/cli-cm-import", - "version": "1.10.0", + "version": "1.11.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "big-json": "^3.2.0", "bluebird": "^3.7.2", @@ -25112,7 +25271,7 @@ }, "packages/contentstack-migration": { "name": "@contentstack/cli-migration", - "version": "1.3.14", + "version": "1.4.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.2.14", @@ -25145,10 +25304,10 @@ }, "packages/contentstack-seed": { "name": "@contentstack/cli-cm-seed", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "axios": "1.3.4", @@ -25241,7 +25400,7 @@ "version": "1.5.4", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "axios": "1.3.4", "chalk": "^4.0.0", @@ -26840,21 +26999,21 @@ "requires": { "@contentstack/cli-audit": "~1.2.0", "@contentstack/cli-auth": "~1.3.15", - "@contentstack/cli-cm-bootstrap": "~1.6.0", + "@contentstack/cli-cm-bootstrap": "~1.6.1", "@contentstack/cli-cm-branches": "~1.0.15", "@contentstack/cli-cm-bulk-publish": "~1.3.13", - "@contentstack/cli-cm-clone": "~1.6.0", - "@contentstack/cli-cm-export": "~1.9.2", - "@contentstack/cli-cm-export-to-csv": "~1.4.4", - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-clone": "~1.7.0", + "@contentstack/cli-cm-export": "~1.10.0", + "@contentstack/cli-cm-export-to-csv": "~1.5.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-cm-migrate-rte": "~1.4.13", - "@contentstack/cli-cm-seed": "~1.6.0", + "@contentstack/cli-cm-seed": "~1.6.1", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-config": "~1.4.13", "@contentstack/cli-launch": "~1.0.13", - "@contentstack/cli-migration": "~1.3.14", + "@contentstack/cli-migration": "~1.4.0", "@contentstack/cli-utilities": "~1.5.4", - "@contentstack/management": "~1.10.0", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", "@oclif/plugin-not-found": "^2.4.0", @@ -27401,7 +27560,7 @@ "@contentstack/cli-cm-bootstrap": { "version": "file:packages/contentstack-bootstrap", "requires": { - "@contentstack/cli-cm-seed": "~1.6.0", + "@contentstack/cli-cm-seed": "~1.6.1", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "@oclif/test": "^2.2.10", @@ -27555,8 +27714,8 @@ "version": "file:packages/contentstack-clone", "requires": { "@colors/colors": "^1.5.0", - "@contentstack/cli-cm-export": "~1.9.0", - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-export": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "@oclif/test": "^1.2.7", @@ -27659,9 +27818,12 @@ "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "@oclif/test": "^2.2.10", - "chai": "^4.2.0", + "@types/chai": "^4.3.6", + "@types/mocha": "^10.0.1", + "chai": "^4.3.8", "chalk": "^4.1.0", "debug": "^4.3.1", + "dotenv": "^16.3.1", "eslint": "^7.32.0", "eslint-config-oclif": "^4.0.0", "fast-csv": "^4.3.6", @@ -27669,7 +27831,7 @@ "inquirer": "8.2.4", "inquirer-checkbox-plus-prompt": "1.0.1", "mkdirp": "^3.0.1", - "mocha": "^10.0.0", + "mocha": "^10.2.0", "nyc": "^15.1.0", "oclif": "^3.8.1" }, @@ -27711,6 +27873,12 @@ "minimatch": "^3.0.4" } }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -27729,6 +27897,32 @@ "uri-js": "^4.2.2" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -27819,6 +28013,20 @@ } } }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -27840,10 +28048,90 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, @@ -27852,7 +28140,7 @@ "requires": { "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "@oclif/test": "^1.2.6", "@types/big-json": "^3.2.0", @@ -28001,7 +28289,7 @@ "@contentstack/cli-cm-seed": { "version": "file:packages/contentstack-seed", "requires": { - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "@oclif/plugin-help": "^5.1.19", @@ -28729,7 +29017,7 @@ "@contentstack/cli-utilities": { "version": "file:packages/contentstack-utilities", "requires": { - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "@oclif/test": "^2.2.10", "@types/chai": "^4.2.18", @@ -29583,20 +29871,20 @@ } }, "@contentstack/management": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.10.2.tgz", - "integrity": "sha512-jO24EqcCJhOjqdsqw8y3T0SPPAd0DG4BByjUcV0S28W2yoa8aBbcjcbZioRPzRLYKTmZWsAZissl18cIJm5djQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@contentstack/management/-/management-1.12.0.tgz", + "integrity": "sha512-+L+WVhSYEtfdG9v794TjLT8Fd6fCB8meqoho666mg1kNufzXcsqr7hjubX5cSL7GcZFdKntkDpZ2RaOnTHReJg==", "requires": { - "axios": "^1.4.0", + "axios": "^1.5.1", "form-data": "^3.0.1", "lodash": "^4.17.21", "qs": "^6.11.2" }, "dependencies": { "axios": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", - "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -31012,9 +31300,9 @@ } }, "@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==" + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==" }, "@types/cli-progress": { "version": "3.11.0", @@ -32505,9 +32793,9 @@ } }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "requires": { "assertion-error": "^1.1.0", diff --git a/packages/contentstack-bootstrap/README.md b/packages/contentstack-bootstrap/README.md index dff552cae8..50a4f8f2e7 100644 --- a/packages/contentstack-bootstrap/README.md +++ b/packages/contentstack-bootstrap/README.md @@ -15,7 +15,7 @@ $ npm install -g @contentstack/cli-cm-bootstrap $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-bootstrap/1.6.0 darwin-arm64 node-v20.8.0 +@contentstack/cli-cm-bootstrap/1.6.1 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index e8914a7b3e..144b233512 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-bootstrap", "description": "Bootstrap contentstack apps", - "version": "1.6.0", + "version": "1.6.1", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -17,7 +17,7 @@ "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, "dependencies": { - "@contentstack/cli-cm-seed": "~1.6.0", + "@contentstack/cli-cm-seed": "~1.6.1", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "inquirer": "8.2.4", diff --git a/packages/contentstack-clone/README.md b/packages/contentstack-clone/README.md index 648300f4db..b93bca2f5e 100644 --- a/packages/contentstack-clone/README.md +++ b/packages/contentstack-clone/README.md @@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-clone $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-clone/1.6.0 darwin-arm64 node-v20.8.0 +@contentstack/cli-cm-clone/1.7.0 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index d4b345e29f..14c7e4bd1a 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -1,12 +1,12 @@ { "name": "@contentstack/cli-cm-clone", "description": "Contentstack stack clone plugin", - "version": "1.6.0", + "version": "1.7.0", "author": "Contentstack", "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues", "dependencies": { - "@contentstack/cli-cm-export": "~1.9.0", - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-export": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "@colors/colors": "^1.5.0", diff --git a/packages/contentstack-export-to-csv/package.json b/packages/contentstack-export-to-csv/package.json index 0f5c36b25b..fcbb2c40fe 100644 --- a/packages/contentstack-export-to-csv/package.json +++ b/packages/contentstack-export-to-csv/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-export-to-csv", "description": "Export entities to csv", - "version": "1.4.4", + "version": "1.5.0", "author": "Abhinav Gupta @abhinav-from-contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { @@ -15,12 +15,15 @@ }, "devDependencies": { "@oclif/test": "^2.2.10", - "chai": "^4.2.0", + "@types/chai": "^4.3.6", + "@types/mocha": "^10.0.1", + "chai": "^4.3.8", "debug": "^4.3.1", + "dotenv": "^16.3.1", "eslint": "^7.32.0", "eslint-config-oclif": "^4.0.0", "globby": "^10.0.2", - "mocha": "^10.0.0", + "mocha": "^10.2.0", "nyc": "^15.1.0", "oclif": "^3.8.1" }, @@ -44,7 +47,8 @@ "postpack": "rm -f oclif.manifest.json", "prepack": "oclif manifest && oclif readme", "test": "nyc mocha --forbid-only \"test/**/*.test.js\"", - "test:unit": "nyc mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.js\"", + "test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"", + "test:unit:report": "nyc --extension .js mocha --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"", "version": "oclif readme && git add README.md", "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo" }, @@ -61,4 +65,4 @@ } }, "repository": "https://github.com/contentstack/cli" -} \ No newline at end of file +} diff --git a/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js b/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js index 8fa579906c..2d9ff357d3 100644 --- a/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js +++ b/packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js @@ -15,7 +15,7 @@ class ExportToCsvCommand extends Command { action: flags.string({ required: false, multiple: false, - options: ['entries', 'users'], + options: ['entries', 'users', 'taxonomies'], description: `Option to export data (entries, users)`, }), alias: flags.string({ @@ -59,6 +59,9 @@ class ExportToCsvCommand extends Command { multiple: false, required: false, }), + 'taxonomy-uid': flags.string({ + description: 'Provide the taxonomy UID of the related terms you want to export', + }), }; async run() { @@ -75,6 +78,7 @@ class ExportToCsvCommand extends Command { 'content-type': contentTypesFlag, alias: managementTokenAlias, branch: branchUid, + 'taxonomy-uid': taxonomyUID, }, } = await this.parse(ExportToCsvCommand); @@ -96,69 +100,17 @@ class ExportToCsvCommand extends Command { let stackAPIClient; let language; let contentTypes = []; - let stackBranches; - const listOfTokens = configHandler.get('tokens'); - if (managementTokenAlias && listOfTokens[managementTokenAlias]) { - managementAPIClient = await managementSDKClient({ - host: this.cmaHost, - management_token: listOfTokens[managementTokenAlias].token, - }); - stack = { - name: stackName || managementTokenAlias, - apiKey: listOfTokens[managementTokenAlias].apiKey, - token: listOfTokens[managementTokenAlias].token, - }; - } else if (managementTokenAlias) { - this.error('Provided management token alias not found in your config.!'); + if (managementTokenAlias) { + const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName); + managementAPIClient = apiClient; + stack = stackDetails; } else { - let organization; - - if (!isAuthenticated()) { - this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, { - exit: 2, - suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], - }); - } - - if (org) { - organization = { uid: org }; - } else { - organization = await util.chooseOrganization(managementAPIClient); // prompt for organization - } - if (!stackAPIKey) { - stack = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack - } else { - stack = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey); - } + stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); } stackAPIClient = this.getStackClient(managementAPIClient, stack); - - if (branchUid) { - try { - const branchExists = await doesBranchExist(stackAPIClient, branchUid); - if (branchExists?.errorCode) { - throw new Error(branchExists.errorMessage); - } - stack.branch_uid = branchUid; - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } catch (error) { - if (error.message || error.errorMessage) { - cliux.error(util.formatError(error)); - this.exit(); - } - } - } else { - stackBranches = await this.getStackBranches(stackAPIClient); - if (stackBranches === undefined) { - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } else { - const { branch } = await util.chooseBranch(stackBranches); - stack.branch_uid = branch; - stackAPIClient = this.getStackClient(managementAPIClient, stack); - } - } + await this.checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient); const contentTypeCount = await util.getContentTypeCount(stackAPIClient); @@ -258,6 +210,22 @@ class ExportToCsvCommand extends Command { } break; } + case config.exportTaxonomies: + case 'taxonomies': { + let stack; + let stackAPIClient; + if (managementTokenAlias) { + const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName); + managementAPIClient = apiClient; + stack = stackDetails; + } else { + stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org); + } + + stackAPIClient = this.getStackClient(managementAPIClient, stack); + await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID); + break; + } } } catch (error) { if (error.message || error.errorMessage) { @@ -273,8 +241,8 @@ class ExportToCsvCommand extends Command { getStackClient(managementAPIClient, stack) { const stackInit = { api_key: stack.apiKey, - branch_uid: stack.branch_uid, }; + if(stack?.branch_uid) stackInit['branch_uid'] = stack.branch_uid; if (stack.token) { return managementAPIClient.stack({ ...stackInit, @@ -292,9 +260,149 @@ class ExportToCsvCommand extends Command { .then(({ items }) => (items !== undefined ? items : [])) .catch((_err) => {}); } + + /** + * check whether branch enabled org or not and update branch details + * @param {string} branchUid + * @param {object} stack + * @param {*} stackAPIClient + * @param {*} managementAPIClient + */ + async checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient) { + if (branchUid) { + try { + const branchExists = await doesBranchExist(stackAPIClient, branchUid); + if (branchExists?.errorCode) { + throw new Error(branchExists.errorMessage); + } + stack.branch_uid = branchUid; + stackAPIClient = this.getStackClient(managementAPIClient, stack); + } catch (error) { + if (error?.message || error?.errorMessage) { + cliux.error(util.formatError(error)); + this.exit(); + } + } + } else { + const stackBranches = await this.getStackBranches(stackAPIClient); + if (stackBranches === undefined) { + stackAPIClient = this.getStackClient(managementAPIClient, stack); + } else { + const { branch } = await util.chooseBranch(stackBranches); + stack.branch_uid = branch; + stackAPIClient = this.getStackClient(managementAPIClient, stack); + } + } + } + + /** + * fetch stack details from alias token + * @param {string} managementTokenAlias + * @param {string} stackName + * @returns + */ + async getAliasDetails(managementTokenAlias, stackName) { + let apiClient, stackDetails; + const listOfTokens = configHandler.get('tokens'); + if (managementTokenAlias && listOfTokens[managementTokenAlias]) { + apiClient = await managementSDKClient({ + host: this.cmaHost, + management_token: listOfTokens[managementTokenAlias].token, + }); + stackDetails = { + name: stackName || managementTokenAlias, + apiKey: listOfTokens[managementTokenAlias].apiKey, + token: listOfTokens[managementTokenAlias].token, + }; + } else if (managementTokenAlias) { + this.error('Provided management token alias not found in your config.!'); + } + return { + apiClient, + stackDetails, + }; + } + + /** + * fetch stack details on basis of the selected org and stack + * @param {*} managementAPIClient + * @param {string} stackAPIKey + * @param {string} org + * @returns + */ + async getStackDetails(managementAPIClient, stackAPIKey, org) { + let organization, stackDetails; + + if (!isAuthenticated()) { + this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, { + exit: 2, + suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'], + }); + } + + if (org) { + organization = { uid: org }; + } else { + organization = await util.chooseOrganization(managementAPIClient); // prompt for organization + } + if (!stackAPIKey) { + stackDetails = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack + } else { + stackDetails = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey); + } + return stackDetails; + } + + /** + * Create a taxonomies csv file for stack and a terms csv file for associated taxonomies + * @param {string} stackName + * @param {object} stack + * @param {string} taxUID + */ + async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID) { + const payload = { + stackAPIClient, + type: '', + limit: config.limit || 100 + }; + //check whether the taxonomy is valid or not + let taxonomies = []; + if (taxUID) { + payload['taxonomyUID'] = taxUID; + const taxonomy = await util.getTaxonomy(payload); + taxonomies.push(taxonomy); + } else { + taxonomies = await util.getAllTaxonomies(payload); + } + + const formattedTaxonomiesData = util.formatTaxonomiesData(taxonomies); + if (formattedTaxonomiesData?.length) { + const fileName = `${stackName ? stackName : stack.name}_taxonomies.csv`; + util.write(this, formattedTaxonomiesData, fileName, 'taxonomies'); + } else { + cliux.print('info: No taxonomies found! Please provide a valid stack.', { color: 'blue' }); + } + + for (let index = 0; index < taxonomies?.length; index++) { + const taxonomy = taxonomies[index]; + const taxonomyUID = taxonomy?.uid; + if (taxonomyUID) { + payload['taxonomyUID'] = taxonomyUID; + const terms = await util.getAllTermsOfTaxonomy(payload); + const formattedTermsData = util.formatTermsOfTaxonomyData(terms, taxonomyUID); + const taxonomyName = taxonomy?.name ?? ''; + const termFileName = `${stackName ?? stack.name}_${taxonomyName}_${taxonomyUID}_terms.csv`; + if (formattedTermsData?.length) { + util.write(this, formattedTermsData, termFileName, 'terms'); + } else { + cliux.print(`info: No terms found for the taxonomy UID - '${taxonomyUID}'!`, { color: 'blue' }); + } + } + } + } } -ExportToCsvCommand.description = `Export entries or organization users to csv using this command`; +ExportToCsvCommand.description = `Export entries, taxonomies, terms or organization users to csv using this command`; ExportToCsvCommand.examples = [ 'csdx cm:export-to-csv', @@ -310,6 +418,10 @@ ExportToCsvCommand.examples = [ '', 'Exporting organization users to csv with organization name provided', 'csdx cm:export-to-csv --action --org --org-name ', + 'Exporting taxonomies and related terms to a .CSV file with the provided taxonomy UID', + 'csdx cm:export-to-csv --action --alias --taxonomy-uid ', + 'Exporting taxonomies and respective terms to a .CSV file', + 'csdx cm:export-to-csv --action --alias ', ]; module.exports = ExportToCsvCommand; diff --git a/packages/contentstack-export-to-csv/src/util/config.js b/packages/contentstack-export-to-csv/src/util/config.js index 6a424cc3a5..8690274366 100644 --- a/packages/contentstack-export-to-csv/src/util/config.js +++ b/packages/contentstack-export-to-csv/src/util/config.js @@ -1,10 +1,12 @@ module.exports = { + limit:100, cancelString: 'Cancel and Exit', exportEntries: 'Export entries to a .CSV file', - exportUsers: "Export organization users' data to a .CSV file", + exportUsers: "Export organization users data to a .CSV file", + exportTaxonomies: 'Export taxonomies to a .CSV file', adminError: "Unable to export data. Make sure you're an admin or owner of this organization", organizationNameRegex: /\'/, CLI_EXPORT_CSV_LOGIN_FAILED: "You need to login to execute this command. See: auth:login --help", - CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command" - + CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command", + CLI_EXPORT_CSV_API_FAILED: 'Something went wrong. Please try again' }; diff --git a/packages/contentstack-export-to-csv/src/util/index.js b/packages/contentstack-export-to-csv/src/util/index.js index daeee46058..b38c1c1d54 100644 --- a/packages/contentstack-export-to-csv/src/util/index.js +++ b/packages/contentstack-export-to-csv/src/util/index.js @@ -2,13 +2,21 @@ const os = require('os'); const fs = require('fs'); const mkdirp = require('mkdirp'); const find = require('lodash/find'); +const flat = require('lodash/flatten'); const fastcsv = require('fast-csv'); const inquirer = require('inquirer'); const debug = require('debug')('export-to-csv'); const checkboxPlus = require('inquirer-checkbox-plus-prompt'); const config = require('./config.js'); -const { cliux, configHandler } = require('@contentstack/cli-utilities'); +const { + cliux, + configHandler, + HttpClient, + messageHandler, + managementSDKClient, + ContentstackClient, +} = require('@contentstack/cli-utilities'); const directory = './data'; const delimeter = os.platform() === 'win32' ? '\\' : '/'; @@ -105,7 +113,7 @@ async function getOrganizationsWhereUserIsAdmin(managementAPIClient) { organizations.forEach((org) => { result[org.name] = org.uid; }); - } + } return result; } catch (error) { @@ -155,9 +163,7 @@ function chooseStack(managementAPIClient, orgUid, stackApiKey) { async function chooseBranch(branchList) { try { - const branches = await branchList; - - const branchesArray = branches.map((branch) => branch.uid); + const branchesArray = branchList.map((branch) => branch.uid); let _chooseBranch = [ { @@ -321,7 +327,13 @@ function getEntries(stackAPIClient, contentType, language, skip, limit) { stackAPIClient .contentType(contentType) .entry() - .query({ include_publish_details: true, locale: language, skip: skip * 100, limit: limit, include_workflow: true }) + .query({ + include_publish_details: true, + locale: language, + skip: skip * 100, + limit: limit, + include_workflow: true, + }) .find() .then((entries) => resolve(entries)) .catch((error) => reject(error)); @@ -371,20 +383,20 @@ function exitProgram() { process.exit(); } -function sanitizeEntries(flatEntry) { +function sanitizeData(flatData) { // sanitize against CSV Injections - const CSVRegex = /^[\\+\\=@\\-]/ - for (key in flatEntry) { - if (typeof flatEntry[key] === 'string' && flatEntry[key].match(CSVRegex)) { - flatEntry[key] = flatEntry[key].replace(/\"/g, "\"\""); - flatEntry[key] = `"'${flatEntry[key]}"` - } else if (typeof flatEntry[key] === 'object') { + const CSVRegex = /^[\\+\\=@\\-]/; + for (key in flatData) { + if (typeof flatData[key] === 'string' && flatData[key].match(CSVRegex)) { + flatData[key] = flatData[key].replace(/\"/g, '""'); + flatData[key] = `"'${flatData[key]}"`; + } else if (typeof flatData[key] === 'object') { // convert any objects or arrays to string // to store this data correctly in csv - flatEntry[key] = JSON.stringify(flatEntry[key]); + flatData[key] = JSON.stringify(flatData[key]); } } - return flatEntry; + return flatData; } function cleanEntries(entries, language, environments, contentTypeUid) { @@ -394,7 +406,7 @@ function cleanEntries(entries, language, environments, contentTypeUid) { return filteredEntries.map((entry) => { let workflow = ''; const envArr = []; - if(entry.publish_details.length) { + if (entry?.publish_details?.length) { entry.publish_details.forEach((env) => { envArr.push(JSON.stringify([environments[env['environment']], env['locale'], env['time']])); }); @@ -403,13 +415,13 @@ function cleanEntries(entries, language, environments, contentTypeUid) { delete entry.publish_details; delete entry.setWorkflowStage; if ('_workflow' in entry) { - if(entry._workflow?.name) { - workflow = entry['_workflow']['name']; - delete entry['_workflow']; - } + if (entry._workflow?.name) { + workflow = entry['_workflow']['name']; + delete entry['_workflow']; + } } entry = flatten(entry); - entry = sanitizeEntries(entry); + entry = sanitizeData(entry); entry['publish_details'] = envArr; entry['_workflow'] = workflow; entry['ACL'] = JSON.stringify({}); // setting ACL to empty obj @@ -459,7 +471,7 @@ function startupQuestions() { type: 'list', name: 'action', message: 'Choose Action', - choices: [config.exportEntries, config.exportUsers, 'Exit'], + choices: [config.exportEntries, config.exportUsers, config.exportTaxonomies, 'Exit'], }, ]; inquirer @@ -678,6 +690,158 @@ function wait(time) { }); } +/** + * fetch all taxonomies in the provided stack + * @param {object} payload + * @param {number} skip + * @param {array} taxonomies + * @returns + */ +async function getAllTaxonomies(payload, skip = 0, taxonomies = []) { + payload['type'] = 'taxonomies'; + const { items, count } = await taxonomySDKHandler(payload, skip); + if (items) { + skip += payload.limit; + taxonomies.push(...items); + if (skip >= count) { + return taxonomies; + } else { + return getAllTaxonomies(payload, skip, taxonomies); + } + } + return taxonomies; +} + +/** + * fetch taxonomy related terms + * @param {object} payload + * @param {number} skip + * @param {number} limit + * @param {array} terms + * @returns + */ +async function getAllTermsOfTaxonomy(payload, skip = 0, terms = []) { + payload['type'] = 'terms'; + const { items, count } = await taxonomySDKHandler(payload, skip); + if (items) { + skip += payload.limit; + terms.push(...items); + if (skip >= count) { + return terms; + } else { + return getAllTermsOfTaxonomy(payload, skip, terms); + } + } + return terms; +} + +/** + * Verify the existence of a taxonomy. Obtain its details if it exists and return + * @param {object} payload + * @param {string} taxonomyUID + * @returns + */ +async function getTaxonomy(payload) { + payload['type'] = 'taxonomy'; + const resp = await taxonomySDKHandler(payload); + return resp; +} + +/** + * taxonomy & term sdk handler + * @async + * @method + * @param payload + * @param skip + * @param limit + * @returns {*} Promise + */ +async function taxonomySDKHandler(payload, skip) { + const { stackAPIClient, taxonomyUID, type } = payload; + + const queryParams = { include_count: true, limit: payload.limit }; + if (skip >= 0) queryParams['skip'] = skip || 0; + + switch (type) { + case 'taxonomies': + return await stackAPIClient + .taxonomy() + .query(queryParams) + .find() + .then((data) => data) + .catch((err) => handleErrorMsg(err)); + case 'taxonomy': + return await stackAPIClient + .taxonomy(taxonomyUID) + .fetch() + .then((data) => data) + .catch((err) => handleErrorMsg(err)); + case 'terms': + queryParams['depth'] = 0; + return await stackAPIClient + .taxonomy(taxonomyUID) + .terms() + .query(queryParams) + .find() + .then((data) => data) + .catch((err) => handleErrorMsg(err)); + default: + handleErrorMsg({ errorMessage: 'Invalid module!' }); + } +} + +/** + * Change taxonomies data in required CSV headers format + * @param {array} taxonomies + * @returns + */ +function formatTaxonomiesData(taxonomies) { + if (taxonomies?.length) { + const formattedTaxonomies = taxonomies.map((taxonomy) => { + return sanitizeData({ + 'Taxonomy UID': taxonomy.uid, + Name: taxonomy.name, + Description: taxonomy.description, + }); + }); + return formattedTaxonomies; + } +} + +/** + * Modify the linked taxonomy data's terms in required CSV headers format + * @param {array} terms + * @param {string} taxonomyUID + * @returns + */ +function formatTermsOfTaxonomyData(terms, taxonomyUID) { + if (terms?.length) { + const formattedTerms = terms.map((term) => { + return sanitizeData({ + 'Taxonomy UID': taxonomyUID, + UID: term.uid, + Name: term.name, + 'Parent UID': term.parent_uid, + Depth: term.depth + }); + }); + return formattedTerms; + } +} + +function handleErrorMsg(err) { + if (err?.errorMessage) { + cliux.print(`Error: ${err.errorMessage}`, { color: 'red' }); + } else if (err?.message) { + const errorMsg = err?.errors?.taxonomy || err?.errors?.term || err?.message; + cliux.print(`Error: ${errorMsg}`, { color: 'red' }); + } else { + console.log(err); + cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' }); + } + process.exit(1); +} + module.exports = { chooseOrganization: chooseOrganization, chooseStack: chooseStack, @@ -704,4 +868,10 @@ module.exports = { chooseInMemContentTypes: chooseInMemContentTypes, getEntriesCount: getEntriesCount, formatError: formatError, + getAllTaxonomies, + getAllTermsOfTaxonomy, + formatTaxonomiesData, + formatTermsOfTaxonomyData, + getTaxonomy, + getStacks }; diff --git a/packages/contentstack-export-to-csv/test/mock-data/common.mock.json b/packages/contentstack-export-to-csv/test/mock-data/common.mock.json new file mode 100644 index 0000000000..d0fc9be610 --- /dev/null +++ b/packages/contentstack-export-to-csv/test/mock-data/common.mock.json @@ -0,0 +1,243 @@ +{ + "taxonomiesResp": { + "taxonomies": [ + { + "uid": "taxonomy_uid_1", + "name": "taxonomy uid 1", + "description": "", + "created_at": "2023-09-01T06:09:44.934Z", + "created_by": "user1", + "updated_at": "2023-09-01T06:44:16.604Z", + "updated_by": "user1" + }, + { + "uid": "taxonomy_uid_2", + "name": "taxonomy uid 2", + "description": "", + "created_at": "2023-09-01T06:09:44.934Z", + "created_by": "user1", + "updated_at": "2023-09-01T06:44:16.604Z", + "updated_by": "user1" + } + ], + "count": 2 + }, + "termsResp": { + "terms": [ + { + "uid": "wsq", + "name": "wsq", + "created_at": "2023-08-30T09:51:12.043Z", + "created_by": "user1", + "updated_at": "2023-08-30T09:51:12.043Z", + "updated_by": "user1", + "parent_uid": null, + "depth": 1 + }, + { + "uid": "term2", + "name": "term2", + "created_at": "2023-08-30T09:45:11.963Z", + "created_by": "user2", + "updated_at": "2023-08-30T09:45:11.963Z", + "updated_by": "user2", + "parent_uid": null, + "depth": 1 + } + ], + "count": 2 + }, + "organizations": [ + { + "uid": "test-uid-1", + "name": "test org 1" + }, + { + "uid": "test-uid-2", + "name": "test org 2" + } + ], + "stacks": [ + { + "name": "Stack 1", + "uid": "stack-uid-1", + "api_key": "stack_api_key_1" + }, + { + "name": "Stack 2", + "uid": "stack-uid-2", + "api_key": "stack_api_key_2" + } + ], + "users": [ + { + "uid": "uid1", + "email": "test@gmail.abc", + "user_uid": "user1", + "org_uid": "test-uid-1", + "invited_by": "user2", + "invited_at": "2023-08-21T11:08:41.038Z", + "status": "accepted", + "acceptance_token": "dfghdfgd", + "created_at": "2023-08-21T11:08:41.036Z", + "updated_at": "2023-08-21T11:09:11.342Z", + "urlPath": "/user", + "organizations": [ + { + "uid": "test-uid-1", + "name": "test org 1", + "org_roles": [ + { + "uid": "role1", + "name": "Admin", + "description": "Admin Role", + "org_uid": "test-uid-1", + "admin": true, + "default": true + } + ] + } + ] + }, + { + "uid": "test-uid-2", + "name": "test org 2" + } + ], + "roles": [ + { + "urlPath": "/roles/role1", + "uid": "role1", + "name": "admin", + "description": "The Admin role has rights to add/remove users from an organization, and has access to stacks created by self or shared by others.", + "org_uid": "test-uid-1", + "owner_uid": "user1", + "admin": true, + "default": true, + "users": ["user2", "user3"], + "created_at": "2023-08-08T10:09:43.445Z", + "updated_at": "2023-08-21T11:08:41.042Z" + } + ], + "contentTypes": [ + { + "stackHeaders": { + "api_key": "stack_api_key_1" + }, + "urlPath": "/content_types/ct1", + "created_at": "2023-08-08T13:52:31.980Z", + "updated_at": "2023-08-08T13:52:34.265Z", + "title": "CT 1", + "uid": "ct_1", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "multiple": false, + "non_localizable": false + } + ] + }, + { + "stackHeaders": { + "api_key": "stack_api_key_1" + }, + "urlPath": "/content_types/ct2", + "created_at": "2023-08-08T13:52:31.980Z", + "updated_at": "2023-08-08T13:52:34.265Z", + "title": "CT 2", + "uid": "ct_2", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "multiple": false, + "non_localizable": false + } + ] + } + ], + "branch": { + "stackHeaders": { + "api_key": "stack_api_key_1" + }, + "urlPath": "/stacks/branches/test_branch1", + "uid": "test_branch1", + "source": "", + "created_by": "user1", + "updated_by": "user1", + "created_at": "2023-08-08T13:51:43.217Z", + "updated_at": "2023-08-08T13:51:43.217Z", + "deleted_at": false + }, + "entry": [ + { + "stackHeaders": { + "api_key": "stack_api_key_1" + }, + "content_type_uid": "home", + "urlPath": "/content_types/ct1/entries/test_entry1", + "title": "Test Entry1", + "url": "/", + "tags": [], + "locale": "en1", + "uid": "test_entry1", + "created_by": "user1", + "updated_by": "user1", + "created_at": "2023-08-08T13:52:46.592Z", + "updated_at": "2023-08-08T13:52:46.592Z", + "_version": 1 + } + ], + "environments": [ + { + "stackHeaders": { + "api_key": "stack_api_key_1" + }, + "urlPath": "/environments/development", + "urls": [ + { + "url": "http://localhost:3000/", + "locale": "en1" + } + ], + "name": "development", + "_version": 3, + "uid": "env1", + "created_by": "user1", + "updated_by": "user1", + "created_at": "2023-06-12T18:59:56.853Z", + "updated_at": "2023-06-12T18:59:56.853Z" + } + ], + "locales": [ + { + "code": "en1", + "name": "English - En", + "fallback_locale": "en-us", + "uid": "gsfdasgdf", + "created_at": "2023-09-11T10:44:40.213Z", + "updated_at": "2023-09-11T10:44:40.213Z", + "ACL": [], + "_version": 1 + } + ] +} diff --git a/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js b/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js index d2415f7179..31a4a888e3 100644 --- a/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js +++ b/packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.js @@ -1,219 +1,287 @@ -/* eslint-disable no-undef */ -const { describe, it, beforeEach, afterEach } = require('mocha'); -const ExportToCsvCommand = require('../../../src/commands/cm/export-to-csv'); -const { stub, assert } = require('sinon'); -const { config } = require('dotenv'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const { test, expect } = require('@oclif/test'); +const { join } = require('path'); +const { PassThrough } = require('stream'); const inquirer = require('inquirer'); -const { cliux } = require('@contentstack/cli-utilities'); +const checkboxPlus = require('inquirer-checkbox-plus-prompt'); +const { cliux, configHandler } = require('@contentstack/cli-utilities'); -config(); +const mockData = require('../../mock-data/common.mock.json'); -describe('Export to csv command with action = entries', () => { - let inquireStub; - let errorStub; - let consoleLogStub; +const { cma } = configHandler.get('region'); - beforeEach(() => { - inquireStub = stub(inquirer, 'prompt'); - errorStub = stub(cliux, 'error'); - consoleLogStub = stub(cliux, 'print'); +describe('export-to-csv with action taxonomies', () => { + describe('Create taxonomies & terms csv file with all flags including taxonomy uid', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .nock(cma, (api) => { + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}`) + .reply(200, { taxonomy: mockData.taxonomiesResp.taxonomies[0] }); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}/terms?include_count=true&limit=100&skip=0&depth=0`) + .reply(200, mockData.termsResp); + }) + .command([ + 'cm:export-to-csv', + '--action', + 'taxonomies', + '--taxonomy-uid', + mockData.taxonomiesResp.taxonomies[0].uid, + '--stack-api-key', + mockData.stacks[0].api_key, + '--org', + mockData.organizations[0].uid, + ]) + .it('CSV file should be created'); }); - afterEach(() => { - inquireStub.restore(); - errorStub.restore(); - consoleLogStub.restore(); + describe('Create taxonomies & terms csv file with all flags excluding taxonomy uid', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .nock(cma, (api) => { + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }); + }) + .nock(cma, (api) => { + api + .get('/v3/taxonomies?include_count=true&limit=100&skip=0&count=true') + .reply(200, mockData.taxonomiesResp); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}/terms?include_count=true&limit=100&skip=0&depth=0`) + .reply(200, mockData.termsResp); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[1].uid}/terms?include_count=true&limit=100&skip=0&depth=0`) + .reply(200, { terms: [], count: 0 }); + }) + .command([ + 'cm:export-to-csv', + '--action', + 'taxonomies', + '--stack-api-key', + mockData.stacks[0].api_key, + '--org', + mockData.organizations[0].uid, + ]) + .it('file should be created'); }); - it('Should ask for action when action is not passed (entries or users)', async () => { - await ExportToCsvCommand.run([]); - assert.calledOnce(inquireStub); - }); - - it('Should ask for org when org is not passed', async () => { - const args = ['--action', 'entries']; - await ExportToCsvCommand.run(args); - assert.calledOnce(inquireStub); - }); - - it('Should ask for stack when stack api key flag is not passed', async (done) => { - const args = ['--action', 'entries', '--org', process.env.ORG]; - done(); - await ExportToCsvCommand.run(args); - assert.calledOnce(inquireStub); - }); - - it('Should ask for branch when branch flag is not passed', async () => { - const args = ['--action', 'entries', '--org', process.env.ORG, '--stack-api-key', process.env.STACK]; - await ExportToCsvCommand.run(args); - assert.calledTwice(inquireStub); - }); - - it('Should throw an error if stack does not have branches enabled', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG_WITH_NO_BRANCHES, - '--stack-api-key', - process.env.STACK_WITH_ORG_WITH_NO_BRANCHES, - '--branch', - 'invalid', - ]; - await ExportToCsvCommand.run(args); - assert.calledWith( - errorStub, - 'Branches are not part of your plan. Please contact support@contentstack.com to upgrade your plan.', - ); - }); - - it('Should ask for content type when content type flag is not passed', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG, - '--stack-api-key', - process.env.STACK, - '--branch', - process.env.BRANCH, - ]; - await ExportToCsvCommand.run(args); - assert.calledOnce(inquireStub); - }); - - it('Should create a file starting with the name passed as stack-name flag', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG, - '--stack-api-key', - process.env.STACK, - '--branch', - process.env.BRANCH, - '--content-type', - 'page', - '--locale', - 'en-us', - '--stack-name', - 'okok', - ]; - await ExportToCsvCommand.run(args); - assert.calledWith(consoleLogStub, `Writing entries to file: ${process.cwd()}/okok_page_en-us_entries_export.csv`); - }); - - it('Should throw an error when invalid org is passed', async () => { - const args = ['--action', 'entries', '--org', 'invalid']; - await ExportToCsvCommand.run(args); - assert.calledWith(errorStub, `Couldn't find the organization. Please check input parameters.`); - }); - - it('Should throw an error when invalid stack is passed', async () => { - const args = ['--action', 'entries', '--org', process.env.ORG, '--stack-api-key', 'invalid']; - await ExportToCsvCommand.run(args); - assert.calledWith(errorStub, 'Could not find stack'); - }); - - it('Should throw an error when invalid branch is passed', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG, - '--stack-api-key', - process.env.STACK, - '--branch', - process.env.INVALID_BRANCH, - ]; - await ExportToCsvCommand.run(args); - assert.calledWith(errorStub, 'Failed to fetch Branch. Please try again with valid parameters.'); - }); - - it('Should throw an error when invalid contenttype is passed', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG, - '--stack-api-key', - process.env.STACK, - '--branch', - process.env.BRANCH, - '--content-type', - 'invalid', - '--locale', - 'en-us', - ]; - await ExportToCsvCommand.run(args); - assert.calledWith( - errorStub, - `The Content Type invalid was not found. Please try again. Content Type is not valid.`, - ); - }); - - it('Should throw an error when invalid locale is passed', async () => { - const args = [ - '--action', - 'entries', - '--org', - process.env.ORG, - '--stack-api-key', - process.env.STACK, - '--branch', - process.env.BRANCH, - '--content-type', - 'header', - '--locale', - 'invalid', - ]; - await ExportToCsvCommand.run(args); - assert.calledWith(errorStub, 'Language was not found. Please try again.'); + describe('Create taxonomies & terms csv file with prompt', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .stub(inquirer, 'registerPrompt', () => {}) + .stub(inquirer, 'prompt', () => { + return Promise.resolve({ + action: 'taxonomies', + chosenOrg: mockData.organizations[0].name, + chosenStack: mockData.stacks[0].name, + }); + }) + .nock(cma, (api) => { + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }); + }) + .nock(cma, (api) => { + api.get(`/v3/organizations?limit=100`).reply(200, { organizations: mockData.organizations }); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}`) + .reply(200, { taxonomy: mockData.taxonomiesResp.taxonomies[0] }); + }) + .nock(cma, (api) => { + api + .get(`/v3/taxonomies/${mockData.taxonomiesResp.taxonomies[0].uid}/terms?include_count=true&limit=100&skip=0&depth=0`) + .reply(200, mockData.termsResp); + }) + .command(['cm:export-to-csv', '--taxonomy-uid', 'taxonomy_uid_1']) + .it('CSV file should be created'); }); }); -describe('Export to csv command with action = users', () => { - let inquireStub; - let errorStub; - let consoleLogStub; - - beforeEach(() => { - inquireStub = stub(inquirer, 'prompt'); - errorStub = stub(cliux, 'error'); - consoleLogStub = stub(cliux, 'print'); - }); +describe('export-to-csv with action entries', () => { - afterEach(() => { - inquireStub.restore(); - errorStub.restore(); - consoleLogStub.restore(); + describe('Create entries csv file with flags', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .nock(cma, (api) => { + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }); + }) + .nock(cma, (api) => { + api.get('/v3/environments').reply(200, { environments: mockData.environments }); + }) + .nock(cma, (api) => { + api.get('/v3/content_types?count=true').reply(200, { content_types: 2 }); + }) + .nock(cma, (api) => { + api.get('/v3/content_types').reply(200, { content_types: mockData.contentTypes }); + }) + .nock(cma, (api) => { + api + .get( + `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=en1&count=true`, + ) + .reply(200, { entries: 2 }); + }) + .nock(cma, (api) => { + api + .get( + `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=en1&skip=0&limit=100&include_workflow=true`, + ) + .reply(200, { entries: mockData.entry }); + }) + .command([ + 'cm:export-to-csv', + '--action', + 'entries', + '--stack-api-key', + mockData.stacks[0].api_key, + '--org', + mockData.organizations[0].uid, + '--branch', + mockData.branch.uid, + '--locale', + 'en1', + '--content-type', + mockData.contentTypes[0].uid, + ]) + .it('Entries CSV file should be created'); }); - it('Should ask for org when org is not passed', async () => { - const args = ['--action', 'entries']; - await ExportToCsvCommand.run(args); - assert.calledOnce(inquireStub); - }); - - it('Should write users data to file if the user has permissions', async () => { - const args = ['--action', 'users', '--org', process.env.ORG]; - - await ExportToCsvCommand.run(args); - assert.calledWith( - consoleLogStub, - `Writing organization details to file: ${process.cwd()}/${process.env.ORG}_users_export.csv`, - ); + describe('Create entries csv file with prompt', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .stub(inquirer, 'registerPrompt', () => {}) + .stub(inquirer, 'prompt', () => { + return Promise.resolve({ + action: 'entries', + chosenOrg: mockData.organizations[0].name, + chosenLanguage: mockData.locales[0].name, + chosenStack: mockData.stacks[0].name, + chosenContentTypes: [mockData.contentTypes[0].uid], + branch: mockData.branch.uid, + }); + }) + .nock(cma, (api) => { + api.get(`/v3/organizations?limit=100`).reply(200, { organizations: mockData.organizations }); + }) + .nock(cma, (api) => { + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }); + }) + .nock(cma, (api) => { + api.get('/v3/environments').reply(200, { environments: mockData.environments }); + }) + .nock(cma, (api) => { + api.get('/v3/locales').reply(200, { locales: mockData.locales }); + }) + .nock(cma, (api) => { + api.get('/v3/stacks/branches').reply(200, { branches: mockData.branch }); + }) + .nock(cma, (api) => { + api.get('/v3/content_types?count=true').reply(200, { content_types: 2 }); + }) + .nock(cma, (api) => { + api.get('/v3/content_types?skip=0&include_branch=true').reply(200, { content_types: mockData.contentTypes }); + }) + .nock(cma, (api) => { + api + .get( + `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=${mockData.locales[0].code}&count=true`, + ) + .reply(200, { entries: 1 }); + }) + .nock(cma, (api) => { + api + .get( + `/v3/content_types/${mockData.contentTypes[0].uid}/entries?include_publish_details=true&locale=${mockData.locales[0].code}&skip=0&limit=100&include_workflow=true`, + ) + .reply(200, { entries: mockData.entry }); + }) + .command(['cm:export-to-csv']) + .it('Entries CSV file should be created with prompt'); }); +}); - it('Should show an error that user does not have org permissions to perform the operation if user enters such org', async () => { - const args = ['--action', 'users', '--org', process.env.ORG_WITH_NO_PERMISSION]; - await ExportToCsvCommand.run(args); - assert.calledWith(errorStub, `You don't have the permission to do this operation.`); +describe('export-to-csv with action users', () => { + describe('Export users csv file with flags', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .nock(cma, (api) => { + api.get('/v3/user?include_orgs_roles=true').reply(200, { user: mockData.users[0] }).persist(); + }) + .nock(cma, (api) => { + api.get(`/v3/organizations/${mockData.organizations[0].uid}/roles`).reply(200, { roles: mockData.roles }); + }) + .nock(cma, (api) => { + api + .get(`/v3/organizations/${mockData.organizations[0].uid}/share?skip=0&page=1&limit=100`) + .reply(200, { users: mockData.users }); + }) + .command(['cm:export-to-csv', '--action', 'users', '--org', mockData.organizations[0].uid]) + .it('Users csv file should be successfully created'); }); - it('Should create a file starting with the name passed as org-name flag', async () => { - const args = ['--action', 'users', '--org', process.env.ORG, '--org-name', 'okok']; - await ExportToCsvCommand.run(args); - assert.calledWith(consoleLogStub, `Writing organization details to file: ${process.cwd()}/okok_users_export.csv`); + describe('Export users csv file with prompt', () => { + test + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(fs, 'createWriteStream', () => new PassThrough()) + .stub(mkdirp, 'sync', () => {}) + .stub(process, 'chdir', () => {}) + .stub(inquirer, 'registerPrompt', () => {}) + .stub(inquirer, 'prompt', () => { + return Promise.resolve({ action: 'users', chosenOrg: mockData.organizations[0].name }); + }) + .nock(cma, (api) => { + api.get(`/v3/organizations?limit=100`).reply(200, { organizations: mockData.organizations }); + }) + .nock(cma, (api) => { + api.get('/v3/user?include_orgs_roles=true').reply(200, { user: mockData.users[0] }).persist(); + }) + .nock(cma, (api) => { + api.get(`/v3/organizations/${mockData.organizations[0].uid}/roles`).reply(200, { roles: mockData.roles }); + }) + .nock(cma, (api) => { + api + .get(`/v3/organizations/${mockData.organizations[0].uid}/share?skip=0&page=1&limit=100`) + .reply(200, { users: mockData.users }); + }) + .command(['cm:export-to-csv']) + .it('Users csv file should be successfully created'); }); }); diff --git a/packages/contentstack-export-to-csv/test/util/common-utils.test.js b/packages/contentstack-export-to-csv/test/util/common-utils.test.js new file mode 100644 index 0000000000..042ff4b84b --- /dev/null +++ b/packages/contentstack-export-to-csv/test/util/common-utils.test.js @@ -0,0 +1,52 @@ +const { fancy } = require('fancy-test'); +const { test, expect } = require('@oclif/test'); +const inquirer = require('inquirer'); +const { cliux, configHandler, ContentstackClient, managementSDKClient } = require('@contentstack/cli-utilities'); + +const mockData = require('../mock-data/common.mock.json'); +const { getStacks, chooseBranch } = require('../../src/util/index'); + +const { cma } = configHandler.get('region'); + +describe('common utils', () => { + let managementSdk; + before(async () => { + managementSdk = await managementSDKClient({ + host: cma.replace('https://', ''), + }); + }); + + describe('chooseStack', () => { + describe('choose stack from list of stacks', () => { + fancy + .nock(cma, (api) => + api + .get(`/v3/stacks?&query={"org_uid":"${mockData.organizations[0].uid}"}`) + .reply(200, { stacks: mockData.stacks }), + ) + .stub(inquirer, 'prompt', () => { + return Promise.resolve({ + chosenStack: mockData.stacks[0].name, + }); + }) + .it('Returns list of stacks', async () => { + await getStacks(managementSdk, mockData.organizations[0].uid); + }); + }); + }); + + describe('chooseBranch', () => { + describe('choose branch from list of branch', () => { + fancy + .stub(inquirer, 'prompt', () => { + return Promise.resolve({ + branch: mockData.branch.uid, + }); + }) + .it('Returns list of stacks', async () => { + const { branch } = await chooseBranch([mockData.branch]); + expect(branch).to.equal(mockData.branch.uid); + }); + }); + }); +}); diff --git a/packages/contentstack-export/README.md b/packages/contentstack-export/README.md index c36ef67f18..ac60d614d2 100755 --- a/packages/contentstack-export/README.md +++ b/packages/contentstack-export/README.md @@ -48,7 +48,7 @@ $ npm install -g @contentstack/cli-cm-export $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-export/1.9.2 darwin-arm64 node-v20.8.0 +@contentstack/cli-cm-export/1.10.0 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-export/package.json b/packages/contentstack-export/package.json index b0c89fbd78..33b67d5bec 100644 --- a/packages/contentstack-export/package.json +++ b/packages/contentstack-export/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-export", "description": "Contentstack CLI plugin to export content from stack", - "version": "1.9.2", + "version": "1.10.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-export/src/config/index.ts b/packages/contentstack-export/src/config/index.ts index ceb09c1cb0..75fd504a73 100644 --- a/packages/contentstack-export/src/config/index.ts +++ b/packages/contentstack-export/src/config/index.ts @@ -26,6 +26,7 @@ const config: DefaultConfig = { 'environments', 'extensions', 'webhooks', + 'taxonomies', 'global-fields', 'content-types', 'custom-roles', @@ -159,6 +160,16 @@ const config: DefaultConfig = { dirName: 'marketplace_apps', fileName: 'marketplace_apps.json', }, + taxonomies: { + dirName: 'taxonomies', + fileName: 'taxonomies.json', + invalidKeys: ['updated_at', 'created_by', 'updated_by'], + }, + terms: { + dirName: 'terms', + fileName: 'terms.json', + invalidKeys: ['updated_at', 'created_by', 'updated_by', 'stackHeaders', 'urlPath'], + }, }, languagesCode: [ 'af-za', diff --git a/packages/contentstack-export/src/export/modules/taxonomies.ts b/packages/contentstack-export/src/export/modules/taxonomies.ts new file mode 100644 index 0000000000..19f1448b01 --- /dev/null +++ b/packages/contentstack-export/src/export/modules/taxonomies.ts @@ -0,0 +1,182 @@ +import omit from 'lodash/omit'; +import keys from 'lodash/keys'; +import isEmpty from 'lodash/isEmpty'; +import { resolve as pResolve } from 'node:path'; + +import BaseClass from './base-class'; +import { log, fsUtil } from '../../utils'; +import { TaxonomiesConfig, TermsConfig, ModuleClassParams } from '../../types'; + +export default class ExportTaxonomies extends BaseClass { + private taxonomies: Record>; + private terms: Record>; + private taxonomiesConfig: TaxonomiesConfig; + private termsConfig: TermsConfig; + private qs: { + include_count: boolean; + skip: number; + asc: string; + depth?: number; + }; + public taxonomiesFolderPath: string; + public termsFolderPath: string; + + constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { + super({ exportConfig, stackAPIClient }); + this.taxonomies = {}; + this.terms = {}; + this.taxonomiesConfig = exportConfig.modules.taxonomies; + this.termsConfig = exportConfig.modules.terms; + this.qs = { include_count: true, skip: 0, asc: 'created_at' }; + } + + async start(): Promise { + log(this.exportConfig, 'Starting taxonomies export', 'info'); + + //create taxonomies and terms folder in data directory path + this.taxonomiesFolderPath = pResolve( + this.exportConfig.data, + this.exportConfig.branchName || '', + this.taxonomiesConfig.dirName, + ); + await fsUtil.makeDirectory(this.taxonomiesFolderPath); + this.termsFolderPath = pResolve(this.taxonomiesFolderPath, this.termsConfig.dirName); + await fsUtil.makeDirectory(this.termsFolderPath); + + //fetch all taxonomies and write into taxonomies folder + await this.getAllTaxonomies(); + if (this.taxonomies === undefined || isEmpty(this.taxonomies)) { + log(this.exportConfig, 'No taxonomies found!', 'info'); + return; + } else { + fsUtil.writeFile(pResolve(this.taxonomiesFolderPath, this.taxonomiesConfig.fileName), this.taxonomies); + log(this.exportConfig, 'All taxonomies exported successfully!', 'success'); + } + + //fetch all terms of respective and write into taxonomies/terms folder + await this.getAllTerms(); + } + + /** + * fetch all taxonomies in the provided stack + * @param {number} skip + * @returns {Promise} + */ + async getAllTaxonomies(skip = 0): Promise { + if (skip) { + this.qs.skip = skip; + } + await this.stack + .taxonomy() + .query(this.qs) + .find() + .then(async (data: any) => { + const { items, count } = data; + const taxonomiesCount = count !== undefined ? count : items?.length; + + if (items?.length) { + this.sanitizeTaxonomiesAttribs(items); + skip += this.taxonomiesConfig.limit || 100; + if (skip >= taxonomiesCount) { + return; + } + return await this.getAllTaxonomies(skip); + } + }) + .catch((error: any) => { + this.handleErrorMsg(error); + }); + } + + /** + * remove invalid keys and write data into taxonomies + * @function sanitizeTaxonomiesAttribs + * @param taxonomies + */ + sanitizeTaxonomiesAttribs(taxonomies: Record[]) { + for (let index = 0; index < taxonomies?.length; index++) { + const taxonomyUID = taxonomies[index].uid; + this.taxonomies[taxonomyUID] = omit(taxonomies[index], this.taxonomiesConfig.invalidKeys); + log(this.exportConfig, `'${taxonomyUID}' taxonomy exported successfully!`, 'success'); + } + } + + /** + * fetch all terms of respective taxonomy and write it into -terms file + * @returns {Promise} + */ + async getAllTerms() { + const taxonomiesUID = keys(this.taxonomies) || []; + this.qs.depth = 0; + + for (let index = 0; index < taxonomiesUID?.length; index++) { + const taxonomyUID = taxonomiesUID[index]; + this.terms = {}; + await this.fetchTermsOfTaxonomy(taxonomyUID); + if (this.terms === undefined || isEmpty(this.terms)) { + log(this.exportConfig, `No terms found for taxonomy - '${taxonomyUID}'!`, 'info'); + } else { + fsUtil.writeFile(pResolve(this.termsFolderPath, `${taxonomyUID}-${this.termsConfig.fileName}`), this.terms); + log(this.exportConfig, `Terms from taxonomy '${taxonomyUID}' exported successfully!`, 'success'); + } + } + log(this.exportConfig, `All the terms exported successfully!`, 'success'); + } + + /** + * fetch all terms of the provided taxonomy uid + * @async + * @param {string} taxonomyUID + * @param {number} skip + * @returns {Promise} + */ + async fetchTermsOfTaxonomy(taxonomyUID: string, skip = 0): Promise { + if (skip) { + this.qs.skip = skip; + } + await this.stack + .taxonomy(taxonomyUID) + .terms() + .query(this.qs) + .find() + .then(async (data: any) => { + const { items, count } = data; + const termsCount = count !== undefined ? count : items?.length; + + if (items?.length) { + this.sanitizeTermsAttribs(items); + skip += this.taxonomiesConfig.limit || 100; + if (skip >= termsCount) { + return; + } + return await this.fetchTermsOfTaxonomy(taxonomyUID, skip); + } + }) + .catch((error: any) => { + this.handleErrorMsg(error); + }); + } + + /** + * remove invalid keys and write data into taxonomies + * @function sanitizeTaxonomiesAttribs + * @param terms + */ + sanitizeTermsAttribs(terms: Record[]) { + for (let index = 0; index < terms?.length; index++) { + const termUID = terms[index]?.uid; + this.terms[termUID] = omit(terms[index], this.termsConfig.invalidKeys); + } + } + + handleErrorMsg(err: any) { + if (err?.errorMessage) { + log(this.exportConfig, `Failed to export! ${err.errorMessage}`, 'error'); + } else if (err?.message) { + const errorMsg = err?.errors?.taxonomy || err?.errors?.term || err?.message; + log(this.exportConfig, `Failed to export! ${errorMsg}`, 'error'); + }else{ + log(this.exportConfig, `Failed to export! ${err}`, 'error'); + } + } +} diff --git a/packages/contentstack-export/src/types/default-config.ts b/packages/contentstack-export/src/types/default-config.ts index e3f92cbc63..01ffbdb00b 100644 --- a/packages/contentstack-export/src/types/default-config.ts +++ b/packages/contentstack-export/src/types/default-config.ts @@ -144,6 +144,18 @@ export default interface DefaultConfig { fileName: string; requiredKeys: string[]; }; + taxonomies: { + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + }; + terms: { + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + }; }; languagesCode: string[]; apis: { diff --git a/packages/contentstack-export/src/types/index.ts b/packages/contentstack-export/src/types/index.ts index ccf95c0918..ee5121491b 100644 --- a/packages/contentstack-export/src/types/index.ts +++ b/packages/contentstack-export/src/types/index.ts @@ -40,7 +40,8 @@ export type Modules = | 'custom-roles' | 'workflows' | 'labels' - | 'marketplace-apps'; + | 'marketplace-apps' + | 'taxonomies'; export type ModuleClassParams = { stackAPIClient: ReturnType; @@ -121,5 +122,21 @@ export interface StackConfig{ limit?: number; } +export interface TaxonomiesConfig{ + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + limit?: number; +} + +export interface TermsConfig{ + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + limit?: number; +} + export { default as DefaultConfig } from './default-config'; export { default as ExportConfig } from './export-config'; diff --git a/packages/contentstack-import/README.md b/packages/contentstack-import/README.md index d8d1ef8968..960b49f1de 100644 --- a/packages/contentstack-import/README.md +++ b/packages/contentstack-import/README.md @@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-import/1.10.0 darwin-arm64 node-v20.8.0 +@contentstack/cli-cm-import/1.11.0 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-import/package.json b/packages/contentstack-import/package.json index e24d4d11a0..678ae5eaae 100644 --- a/packages/contentstack-import/package.json +++ b/packages/contentstack-import/package.json @@ -1,13 +1,13 @@ { "name": "@contentstack/cli-cm-import", "description": "Contentstack CLI plugin to import content into stack", - "version": "1.10.0", + "version": "1.11.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "big-json": "^3.2.0", "bluebird": "^3.7.2", diff --git a/packages/contentstack-import/src/config/index.ts b/packages/contentstack-import/src/config/index.ts index 4f472a5bb6..e1eae76583 100644 --- a/packages/contentstack-import/src/config/index.ts +++ b/packages/contentstack-import/src/config/index.ts @@ -26,6 +26,7 @@ const config: DefaultConfig = { 'locales', 'environments', 'assets', + 'taxonomies', 'extensions', 'marketplace-apps', 'global-fields', @@ -142,6 +143,14 @@ const config: DefaultConfig = { dirName: 'marketplace_apps', fileName: 'marketplace_apps.json', }, + taxonomies: { + dirName: 'taxonomies', + fileName: 'taxonomies.json' + }, + terms: { + dirName: 'terms', + fileName: 'terms.json' + }, }, languagesCode: [ 'af-za', diff --git a/packages/contentstack-import/src/import/modules/base-class.ts b/packages/contentstack-import/src/import/modules/base-class.ts index 6f601a1f21..d4a69296c9 100644 --- a/packages/contentstack-import/src/import/modules/base-class.ts +++ b/packages/contentstack-import/src/import/modules/base-class.ts @@ -48,7 +48,9 @@ export type ApiModuleType = | 'create-entries' | 'update-entries' | 'publish-entries' - | 'delete-entries'; + | 'delete-entries' + | 'create-taxonomies' + | 'create-terms'; export type ApiOptions = { uid?: string; @@ -382,6 +384,17 @@ export default abstract class BaseClass { .delete({ locale: additionalInfo.locale }) .then(onSuccess) .catch(onReject); + case 'create-taxonomies': + return this.stack.taxonomy().create({ taxonomy: apiData }).then(onSuccess).catch(onReject); + case 'create-terms': + if (apiData?.taxonomy_uid) { + return this.stack + .taxonomy(apiData.taxonomy_uid) + .terms() + .create({ term: apiData }) + .then(onSuccess) + .catch(onReject); + } default: return Promise.resolve(); } diff --git a/packages/contentstack-import/src/import/modules/content-types.ts b/packages/contentstack-import/src/import/modules/content-types.ts index 86557f6a8b..0ca255da31 100644 --- a/packages/contentstack-import/src/import/modules/content-types.ts +++ b/packages/contentstack-import/src/import/modules/content-types.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { isEmpty, find, cloneDeep, map } from 'lodash'; -import { fsUtil, log, formatError, schemaTemplate, lookupExtension } from '../../utils'; +import { fsUtil, log, formatError, schemaTemplate, lookupExtension, lookUpTaxonomy } from '../../utils'; import { ImportConfig, ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; import { updateFieldRules } from '../../utils/content-type-helper'; @@ -49,6 +49,8 @@ export default class ContentTypesImport extends BaseClass { limit: number; writeConcurrency?: number; }; + private taxonomiesPath: string; + public taxonomies: Record; constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); @@ -75,6 +77,7 @@ export default class ContentTypesImport extends BaseClass { this.gFs = []; this.createdGFs = []; this.pendingGFs = []; + this.taxonomiesPath = path.join(importConfig.data, 'mapper/taxonomies', 'success.json'); } async start(): Promise { @@ -85,7 +88,6 @@ export default class ContentTypesImport extends BaseClass { * Update pending global fields * write field rules */ - this.cTs = fsUtil.readFile(path.join(this.cTsFolderPath, 'schema.json')) as Record[]; if (!this.cTs || isEmpty(this.cTs)) { log(this.importConfig, 'No content type found to import', 'info'); @@ -95,6 +97,7 @@ export default class ContentTypesImport extends BaseClass { this.installedExtensions = ( ((await fsUtil.readFile(this.marketplaceAppMapperPath)) as any) || { extension_uid: {} } ).extension_uid; + this.taxonomies = fsUtil.readFile(this.taxonomiesPath) as Record; await this.seedCTs(); log(this.importConfig, 'Created content types', 'success'); @@ -152,7 +155,7 @@ export default class ContentTypesImport extends BaseClass { async updateCTs(): Promise { const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { - log(this.importConfig, `${uid} updated with references`, 'success'); + log(this.importConfig, `'${uid}' updated with references`, 'success'); }; const onReject = ({ error, apiData: { uid } }: any) => { log(this.importConfig, formatError(error), 'error'); @@ -186,6 +189,8 @@ export default class ContentTypesImport extends BaseClass { } this.fieldRules.push(contentType.uid); } + //will remove taxonomy if taxonomy doesn't exists in stack + lookUpTaxonomy(this.importConfig, contentType.schema, this.taxonomies); lookupExtension( this.importConfig, contentType.schema, diff --git a/packages/contentstack-import/src/import/modules/entries.ts b/packages/contentstack-import/src/import/modules/entries.ts index 75c77da1c4..7782598654 100644 --- a/packages/contentstack-import/src/import/modules/entries.ts +++ b/packages/contentstack-import/src/import/modules/entries.ts @@ -20,6 +20,7 @@ import { lookupEntries, lookupAssets, fileHelper, + lookUpTerms, } from '../../utils'; import { ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; @@ -53,6 +54,8 @@ export default class EntriesImport extends BaseClass { private entriesUidMapper: Record; private envs: Record; private autoCreatedEntries: Record[]; + private taxonomiesPath: string; + public taxonomies: Record; constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); @@ -64,6 +67,7 @@ export default class EntriesImport extends BaseClass { this.uniqueUidMapperPath = path.join(this.entriesMapperPath, 'unique-mapping.json'); this.modifiedCTsPath = path.join(this.entriesMapperPath, 'modified-schemas.json'); this.marketplaceAppMapperPath = path.join(this.importConfig.data, 'mapper', 'marketplace_apps', 'uid-mapping.json'); + this.taxonomiesPath = path.join(this.importConfig.data, 'mapper', 'taxonomies', 'terms', 'success.json'); this.entriesConfig = importConfig.modules.entries; this.entriesPath = path.resolve(importConfig.data, this.entriesConfig.dirName); this.cTsPath = path.resolve(importConfig.data, importConfig.modules['content-types'].dirName); @@ -96,6 +100,8 @@ export default class EntriesImport extends BaseClass { this.assetUidMapper = (fsUtil.readFile(this.assetUidMapperPath) as Record) || {}; this.assetUrlMapper = (fsUtil.readFile(this.assetUrlMapperPath) as Record) || {}; + + this.taxonomies = (fsUtil.readFile(this.taxonomiesPath) as Record); fsUtil.makeDirectory(this.entriesMapperPath); await this.disableMandatoryCTReferences(); @@ -412,6 +418,8 @@ export default class EntriesImport extends BaseClass { if (this.jsonRteCTsWithRef.indexOf(cTUid) > -1) { entry = removeEntryRefsFromJSONRTE(entry, contentType.schema); } + //will remove term if term doesn't exists in taxonomy + lookUpTerms(contentType?.schema, entry, this.taxonomies, this.importConfig); // will replace all old asset uid/urls with new ones entry = lookupAssets( { diff --git a/packages/contentstack-import/src/import/modules/taxonomies.ts b/packages/contentstack-import/src/import/modules/taxonomies.ts new file mode 100644 index 0000000000..91d2ca0e87 --- /dev/null +++ b/packages/contentstack-import/src/import/modules/taxonomies.ts @@ -0,0 +1,258 @@ +import keys from 'lodash/keys'; +import pick from 'lodash/pick'; +import { join } from 'node:path'; +import values from 'lodash/values'; +import isEmpty from 'lodash/isEmpty'; + +import BaseClass, { ApiOptions } from './base-class'; +import { log, formatError, fsUtil, fileHelper } from '../../utils'; +import { ModuleClassParams, TaxonomiesConfig, TermsConfig } from '../../types'; + +export default class ImportTaxonomies extends BaseClass { + private taxonomiesMapperDirPath: string; + private taxonomiesFolderPath: string; + private taxSuccessPath: string; + private taxFailsPath: string; + private taxonomiesConfig: TaxonomiesConfig; + private taxonomies: Record; + private termsFolderPath: string; + private termsMapperDirPath: string; + private termsConfig: TermsConfig; + private termsSuccessPath: string; + private termsFailsPath: string; + public taxonomiesSuccess: Record = {}; + public taxonomiesFailed: Record = {}; + public termsSuccess: Record> = {}; + public termsFailed: Record> = {}; + public terms: Record = {}; + public taxonomyUIDs: string[] = []; + + constructor({ importConfig, stackAPIClient }: ModuleClassParams) { + super({ importConfig, stackAPIClient }); + this.taxonomiesConfig = importConfig.modules.taxonomies; + this.termsConfig = importConfig.modules.terms; + this.taxonomiesMapperDirPath = join(importConfig.backupDir, 'mapper', 'taxonomies'); + this.termsMapperDirPath = join(this.taxonomiesMapperDirPath, 'terms'); + this.taxonomiesFolderPath = join(importConfig.backupDir, this.taxonomiesConfig.dirName); + this.termsFolderPath = join(this.taxonomiesFolderPath, this.termsConfig.dirName); + this.taxSuccessPath = join(this.taxonomiesMapperDirPath, 'success.json'); + this.taxFailsPath = join(this.taxonomiesMapperDirPath, 'fails.json'); + this.termsSuccessPath = join(this.termsMapperDirPath, 'success.json'); + this.termsFailsPath = join(this.termsMapperDirPath, 'fails.json'); + } + + /** + * @method start + * @returns {Promise} Promise + */ + async start(): Promise { + log(this.importConfig, 'Migrating taxonomies...', 'info'); + + //Step1 check folder exists or not + if (fileHelper.fileExistsSync(this.taxonomiesFolderPath)) { + this.taxonomies = fsUtil.readFile(join(this.taxonomiesFolderPath, 'taxonomies.json'), true) as Record< + string, + unknown + >; + } else { + log(this.importConfig, `No such file or directory - '${this.taxonomiesFolderPath}'`, 'error'); + return; + } + + //Step 2 create taxonomies & terms mapper directory + await fsUtil.makeDirectory(this.taxonomiesMapperDirPath); + await fsUtil.makeDirectory(this.termsMapperDirPath); + + //Step 3 import taxonomy and create success & failure file + await this.importTaxonomies(); + this.createTaxonomySuccessAndFailedFile(); + + if (!fileHelper.fileExistsSync(this.termsFolderPath)) { + log(this.importConfig, `No such file or directory - '${this.termsFolderPath}'`, 'error'); + return; + } + //Step 4 import terms and create success & failure file + await this.importTerms(); + this.createTermSuccessAndFailedFile(); + + log(this.importConfig, 'Taxonomies imported successfully!', 'success'); + } + + /** + * create taxonomy and enter success & failure related data into taxonomies mapper file + * @method importTaxonomies + * @async + * @returns {Promise} Promise + */ + async importTaxonomies(): Promise { + if (this.taxonomies === undefined || isEmpty(this.taxonomies)) { + log(this.importConfig, 'No Taxonomies Found!', 'info'); + return; + } + + const apiContent = values(this.taxonomies) as Record[]; + this.taxonomyUIDs = keys(this.taxonomies); + + const onSuccess = ({ response }: any) => { + const { uid } = response; + this.taxonomiesSuccess[uid] = pick(response, ['name', 'description']); + log(this.importConfig, `Taxonomy '${uid}' imported successfully!`, 'success'); + }; + + const onReject = ({ error, apiData }: any) => { + const err = error?.message ? JSON.parse(error.message) : error; + const { uid } = apiData; + if (err?.errors?.taxonomy) { + this.taxonomiesFailed[uid] = apiData; + log(this.importConfig, `Taxonomy '${uid}' failed to be import! ${err.errors.taxonomy}`, 'error'); + } else { + this.taxonomiesFailed[apiData.uid] = apiData; + log(this.importConfig, `Taxonomy '${uid}' failed to be import! ${formatError(error)}`, 'error'); + } + }; + + await this.makeConcurrentCall( + { + apiContent, + processName: 'import taxonomies', + apiParams: { + serializeData: this.serializeTaxonomy.bind(this), + reject: onReject, + resolve: onSuccess, + entity: 'create-taxonomies', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1, + }, + undefined, + false, + ); + } + + /** + * @method serializeTaxonomy + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeTaxonomy(apiOptions: ApiOptions): ApiOptions { + const { apiData: taxonomy } = apiOptions; + apiOptions.apiData = taxonomy; + return apiOptions; + } + + /** + * create taxonomies success and fail in (mapper/taxonomies) + * @method createTaxonomySuccessAndFailedFile + */ + createTaxonomySuccessAndFailedFile() { + if (this.taxonomiesSuccess !== undefined && !isEmpty(this.taxonomiesSuccess)) { + fsUtil.writeFile(this.taxSuccessPath, this.taxonomiesSuccess); + } + + if (this.taxonomiesFailed !== undefined && !isEmpty(this.taxonomiesFailed)) { + fsUtil.writeFile(this.taxFailsPath, this.taxonomiesFailed); + } + } + + /** + * create terms and enter success & failure related data into terms mapper file + * @method importTerms + * @async + * @returns {Promise} Promise + */ + async importTerms(): Promise { + if (!this.taxonomyUIDs?.length) { + return; + } + + const onSuccess = ({ response, apiData: { taxonomy_uid } = { taxonomy_uid: null } }: any) => { + const { uid } = response; + if (!this.termsSuccess[taxonomy_uid]) this.termsSuccess[taxonomy_uid] = {}; + this.termsSuccess[taxonomy_uid][uid] = pick(response, ['name']); + log(this.importConfig, `Term '${uid}' imported successfully!`, 'success'); + }; + + const onReject = ({ error, apiData }: any) => { + const { taxonomy_uid, uid } = apiData; + if (!this.termsFailed[taxonomy_uid]) this.termsFailed[taxonomy_uid] = {}; + const err = error?.message ? JSON.parse(error.message) : error; + + if (err?.errors?.term) { + this.termsFailed[taxonomy_uid][uid] = apiData; + log(this.importConfig, `Term '${uid}' failed to be import! ${err.errors.term}`, 'error'); + } else { + this.termsFailed[taxonomy_uid][uid] = apiData; + log(this.importConfig, `Term '${uid}' failed to be import! ${formatError(error)}`, 'error'); + } + }; + + for (const taxUID of this.taxonomyUIDs) { + //read terms from respective taxonomy + this.terms = fsUtil.readFile( + join(this.termsFolderPath, `${taxUID}-${this.termsConfig.fileName}`), + true, + ) as Record; + + if (this.terms !== undefined && !isEmpty(this.terms)) { + const apiContent = values(this.terms) as Record[]; + await this.makeConcurrentCall( + { + apiContent, + processName: 'import terms', + apiParams: { + serializeData: this.serializeTerms.bind(this), + reject: onReject, + resolve: onSuccess, + entity: 'create-terms', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1, + }, + undefined, + false, + ); + } + } + } + + /** + * @method serializeTerms + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeTerms(apiOptions: ApiOptions): ApiOptions { + const { apiData: term } = apiOptions; + const {parent_uid, taxonomy_uid} = term; + + //check whether parent term exists or not in taxonomy + if (parent_uid !== null) { + if (!this.termsSuccess[taxonomy_uid][parent_uid]) { + log( + this.importConfig, + `Parent term '${term?.parent_uid}' does not exist! Skipping '${term.uid}' creation to avoid further issues.`, + 'info', + ); + apiOptions.apiData = undefined; + } else { + apiOptions.apiData = term; + } + } else { + apiOptions.apiData = term; + } + return apiOptions; + } + + /** + * create terms success and fail in (mapper/taxonomies/terms) + * @method createTermSuccessAndFailedFile + */ + createTermSuccessAndFailedFile() { + if (this.termsSuccess !== undefined && !isEmpty(this.termsSuccess)) { + fsUtil.writeFile(this.termsSuccessPath, this.termsSuccess); + } + + if (this.termsFailed !== undefined && !isEmpty(this.termsFailed)) { + fsUtil.writeFile(this.termsFailsPath, this.termsFailed); + } + } +} diff --git a/packages/contentstack-import/src/types/default-config.ts b/packages/contentstack-import/src/types/default-config.ts index 7e4dfd1067..15eb3f295d 100644 --- a/packages/contentstack-import/src/types/default-config.ts +++ b/packages/contentstack-import/src/types/default-config.ts @@ -113,6 +113,16 @@ export default interface DefaultConfig { fileName: string; requiredKeys: string[]; }; + taxonomies: { + dirName: string; + fileName: string; + dependencies?: Modules[]; + }; + terms: { + dirName: string; + fileName: string; + dependencies?: Modules[]; + }; }; languagesCode: string[]; apis: { diff --git a/packages/contentstack-import/src/types/index.ts b/packages/contentstack-import/src/types/index.ts index 2b5fee4e28..bf612e188b 100644 --- a/packages/contentstack-import/src/types/index.ts +++ b/packages/contentstack-import/src/types/index.ts @@ -40,7 +40,8 @@ export type Modules = | 'custom-roles' | 'workflows' | 'labels' - | 'marketplace-apps'; + | 'marketplace-apps' + | 'taxonomies'; export type ModuleClassParams = { stackAPIClient: ReturnType; @@ -89,5 +90,17 @@ export interface CustomRoleConfig { customRolesLocalesFileName: string; } +export interface TaxonomiesConfig{ + dirName: string; + fileName: string; + dependencies?: Modules[]; +} + +export interface TermsConfig{ + dirName: string; + fileName: string; + dependencies?: Modules[]; +} + export { default as DefaultConfig } from './default-config'; export { default as ImportConfig } from './import-config'; diff --git a/packages/contentstack-import/src/utils/index.ts b/packages/contentstack-import/src/utils/index.ts index 19cfca611e..c76ee3340d 100644 --- a/packages/contentstack-import/src/utils/index.ts +++ b/packages/contentstack-import/src/utils/index.ts @@ -27,3 +27,4 @@ export { restoreJsonRteEntryRefs, } from './entries-helper'; export * from './common-helper'; +export { lookUpTaxonomy, lookUpTerms } from './taxonomies-helper'; diff --git a/packages/contentstack-import/src/utils/logger.ts b/packages/contentstack-import/src/utils/logger.ts index d6ca174a75..de906a7e6f 100644 --- a/packages/contentstack-import/src/utils/logger.ts +++ b/packages/contentstack-import/src/utils/logger.ts @@ -81,7 +81,12 @@ function init(_logPath: string) { logger = winston.createLogger({ transports: [ new winston.transports.File(successTransport), - new winston.transports.Console({ format: winston.format.simple() }), + new winston.transports.Console({ + format: winston.format.combine( + winston.format.simple(), + winston.format.colorize({ all: true, colors: { warn: 'yellow', info: 'white' } }), + ), + }), ], levels: myCustomLevels.levels, }); @@ -109,7 +114,7 @@ function init(_logPath: string) { logger.log('info', logString); } }, - warn: function () { + warn: function (message: any) { let args = slice.call(arguments); let logString = returnString(args); if (logString) { @@ -138,7 +143,8 @@ export const log = async (config: ImportConfig, message: any, type: string) => { // ignoring the type argument, as we are not using it to create a logfile anymore if (type !== 'error') { // removed type argument from init method - init(config.data).log(message); + if (type === 'warn') init(config.data).warn(message); //logged warning message in log file + else init(config.data).log(message); } else { init(config.data).error(message); } diff --git a/packages/contentstack-import/src/utils/taxonomies-helper.ts b/packages/contentstack-import/src/utils/taxonomies-helper.ts new file mode 100644 index 0000000000..09755fc2e2 --- /dev/null +++ b/packages/contentstack-import/src/utils/taxonomies-helper.ts @@ -0,0 +1,132 @@ +/** + * taxonomy lookup + */ +import { log } from './'; +import { ImportConfig } from '../types'; + +/** + * check and remove if referenced taxonomy doesn't exists in stack + * @param {any} schema content type schema + * @param {Record} taxonomies created taxonomies + * @param {ImportConfig} importConfig + */ +export const lookUpTaxonomy = function (importConfig: ImportConfig, schema: any, taxonomies: Record) { + for (let i in schema) { + if (schema[i].data_type === 'taxonomy') { + const taxonomyFieldData = schema[i].taxonomies as Record[]; + const { updatedTaxonomyData, isTaxonomyFieldRemoved } = verifyAndRemoveTaxonomy( + taxonomyFieldData, + taxonomies, + importConfig, + ); + + //Handle API error -> The 'taxonomies' property must have atleast one taxonomy object. Remove taxonomy field from schema. + if (isTaxonomyFieldRemoved) { + schema.splice(i, 1); + } else { + schema[i].taxonomies = updatedTaxonomyData; + } + } + } +}; + +/** + * verify and remove referenced taxonomy with warning from respective content type + * @param {Record[]} taxonomyFieldData + * @param {Record} taxonomies created taxonomies + * @param {ImportConfig} importConfig + * @returns + */ +const verifyAndRemoveTaxonomy = function ( + taxonomyFieldData: Record[], + taxonomies: Record, + importConfig: ImportConfig, +): { + updatedTaxonomyData: Record[]; + isTaxonomyFieldRemoved: boolean; +} { + let isTaxonomyFieldRemoved: boolean = false; + + for (let index = 0; index < taxonomyFieldData?.length; index++) { + const taxonomyData = taxonomyFieldData[index]; + + if (taxonomies === undefined || !taxonomies.hasOwnProperty(taxonomyData?.taxonomy_uid)) { + // remove taxonomy from taxonomies field data with warning if respective taxonomy doesn't exists + log( + importConfig, + `Taxonomy '${taxonomyData?.taxonomy_uid}' does not exist. Removing the data from the taxonomies field`, + 'warn', + ); + taxonomyFieldData.splice(index, 1); + --index; + } + } + + if (!taxonomyFieldData?.length) { + log( + importConfig, + 'Taxonomy does not exist. Removing the field from content type', + 'warn', + ); + isTaxonomyFieldRemoved = true; + } + + return { + updatedTaxonomyData: taxonomyFieldData, + isTaxonomyFieldRemoved, + }; +}; + +/** + * check and remove if referenced terms doesn't exists in taxonomy + * @param {Record[]} ctSchema content type schema + * @param {any} entry + * @param {Record} taxonomiesAndTermData created taxonomies and terms + * @param {ImportConfig} importConfig + */ +export const lookUpTerms = function ( + ctSchema: Record[], + entry: any, + taxonomiesAndTermData: Record, + importConfig: ImportConfig, +) { + for (let index = 0; index < ctSchema?.length; index++) { + if (ctSchema[index].data_type === 'taxonomy') { + const taxonomyFieldData = entry[ctSchema[index].uid]; + const updatedTaxonomyData = verifyAndRemoveTerms(taxonomyFieldData, taxonomiesAndTermData, importConfig); + entry[ctSchema[index].uid] = updatedTaxonomyData; + } + } +}; + +/** + * verify and remove referenced term with warning from respective entry + * @param {Record[]} taxonomyFieldData entry taxonomies data + * @param {Record} taxonomiesAndTermData created taxonomies and terms + * @param {ImportConfig} importConfig + * @returns { Record[]} + */ +const verifyAndRemoveTerms = function ( + taxonomyFieldData: Record[], + taxonomiesAndTermData: Record, + importConfig: ImportConfig, +): Record[] { + for (let index = 0; index < taxonomyFieldData?.length; index++) { + const taxonomyData = taxonomyFieldData[index]; + const taxUID = taxonomyData?.taxonomy_uid; + const termUID = taxonomyData?.term_uid; + + if ( + taxonomiesAndTermData === undefined || + !taxonomiesAndTermData.hasOwnProperty(taxUID) || + (taxonomiesAndTermData.hasOwnProperty(taxUID) && !taxonomiesAndTermData[taxUID].hasOwnProperty(termUID)) + ) { + // remove term from taxonomies field data with warning if respective term doesn't exists + log(importConfig, `Term '${termUID}' does not exist. Removing it from taxonomy - '${taxUID}'`, 'warn'); + taxonomyFieldData.splice(index, 1); + --index; + } + } + + return taxonomyFieldData; +}; \ No newline at end of file diff --git a/packages/contentstack-migration/README.md b/packages/contentstack-migration/README.md index fbed0cb23b..ddeacecf2a 100644 --- a/packages/contentstack-migration/README.md +++ b/packages/contentstack-migration/README.md @@ -21,7 +21,7 @@ $ npm install -g @contentstack/cli-migration $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-migration/1.3.14 darwin-arm64 node-v20.8.0 +@contentstack/cli-migration/1.4.0 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-migration/docs/api-reference.md b/packages/contentstack-migration/docs/api-reference.md index 6db907e107..da8e9a473f 100644 --- a/packages/contentstack-migration/docs/api-reference.md +++ b/packages/contentstack-migration/docs/api-reference.md @@ -53,10 +53,12 @@ Creates content type by passing content type name and options **Example** ```js -module.exports = {migrations} => { - const blog = migrations.createContentType('blog', { - title: 'blog' - }) +module.exports = ({migration}) => { + const blog = migration + .createContentType('blog') + .title('blog title') + .description('blog 1') + blog.createField('title').display_name('Title').data_type('text').mandatory(true); } ``` @@ -98,10 +100,8 @@ Edits content type by passing content type name and options **Example** ```js -module.exports = {migrations} => { - const blog = migrations.editContentType('blog', { - title: 'blog' - }); +module.exports = ({migration}) => { + const blog = migration.editContentType('blog'); blog.description('Changed description'); } ``` @@ -177,6 +177,8 @@ Chained function takes boolean value for force while deleting content type * [.unique(value)](#Field+unique) ⇒ [Field](#Field) * [.reference_to(value)](#Field+reference_to) ⇒ [Field](#Field) * [.ref_multiple(value)](#Field+ref_multiple) ⇒ [Field](#Field) + * [.taxonomies(value)](#Field+taxonomies) ⇒ [Field](#Field) + * [.multiple(value)](#Field+multiple) ⇒ [Field](#Field) * [.ref_multipleContentType(value)](#Field+ref_multipleContentType) ⇒ [Field](#Field) * [.getTaskDefinition()](#Field+getTaskDefinition) ⇒ [Task](#Task) @@ -203,11 +205,24 @@ Creates a field with provided uid. module.exports =({ migration })=> { const blog = migration.editContentType('blog'); - blog.createField('author'); + blog.createField('author') .display_name('Author') .data_type('text') .mandatory(false); }; + +Create a taxonomy field + + module.exports =({ migration })=> { + const blog = migration.editContentType('blog'); + + blog.createField('taxonomies') + .display_name('Taxonomy1') + .data_type('taxonomy') + .taxonomies([{ "taxonomy_uid": "test_taxonomy1", "max_terms": 2, "mandatory": false}]) + .multiple(true) + .mandatory(false); +}; ``` @@ -356,6 +371,28 @@ module.exports = ({migration}) => { | --- | --- | --- | | value | string | set true if accepts multiple entries as reference | + + +### field.taxonomies(value) ⇒ [Field](#Field) +The 'taxonomies' property should contain at least one taxonomy object + +**Kind**: instance method of [Field](#Field) +**Returns**: [Field](#Field) - current instance of field object to chain further methods. + +| Param | Type | Description | +| --- | --- | --- | +| value | string \| Array.<string> | list of taxonomies. | + + + +### field.multiple(value) ⇒ [Field](#Field) +**Kind**: instance method of [Field](#Field) +**Returns**: [Field](#Field) - current instance of field object to chain further methods. + +| Param | Type | Description | +| --- | --- | --- | +| value | boolean | set true if field is multiple | + ### field.ref\_multipleContentType(value) ⇒ [Field](#Field) diff --git a/packages/contentstack-migration/package.json b/packages/contentstack-migration/package.json index 61be6b7dc9..2257fcab4a 100644 --- a/packages/contentstack-migration/package.json +++ b/packages/contentstack-migration/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/cli-migration", - "version": "1.3.14", + "version": "1.4.0", "author": "@contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-migration/src/config/default-options.js b/packages/contentstack-migration/src/config/default-options.js index 90d34e835f..72dc159fe8 100644 --- a/packages/contentstack-migration/src/config/default-options.js +++ b/packages/contentstack-migration/src/config/default-options.js @@ -2,6 +2,6 @@ 'use strict'; module.exports = { - is_page: true, + is_page: false, singleton: false, }; diff --git a/packages/contentstack-migration/src/modules/content-types.js b/packages/contentstack-migration/src/modules/content-types.js index b47b0598c3..e597ece69a 100644 --- a/packages/contentstack-migration/src/modules/content-types.js +++ b/packages/contentstack-migration/src/modules/content-types.js @@ -38,10 +38,12 @@ class ContentType extends Base { * @param {Object} opts Optional: Content type fields definition * @returns {Field} instance of Field * @example - * module.exports = {migrations} => { - * const blog = migrations.createContentType('blog', { - * title: 'blog' - * }) + * module.exports = ({migration}) => { + * const blog = migration + * .createContentType('blog') + * .title('blog title') + * .description('blog 1') + * blog.createField('title').display_name('Title').data_type('text').mandatory(true); * } */ createContentType(id, opts = {}) { @@ -114,10 +116,8 @@ class ContentType extends Base { * @param {Object} opts Optional: Content type fields definition * @returns {Field} instance of Field * @example - * module.exports = {migrations} => { - * const blog = migrations.editContentType('blog', { - * title: 'blog' - * }); + * module.exports = ({migration}) => { + * const blog = migration.editContentType('blog'); * blog.description('Changed description'); * } */ diff --git a/packages/contentstack-migration/src/modules/fields.js b/packages/contentstack-migration/src/modules/fields.js index 9ace6c74d9..0b6e69f9e6 100644 --- a/packages/contentstack-migration/src/modules/fields.js +++ b/packages/contentstack-migration/src/modules/fields.js @@ -16,6 +16,8 @@ const { field_metadata, reference_to, actions: _actions, + taxonomies, + multiple, } = constants; // Base class @@ -52,11 +54,24 @@ class Field extends Base { * module.exports =({ migration })=> { * const blog = migration.editContentType('blog'); * - * blog.createField('author'); + * blog.createField('author') * .display_name('Author') * .data_type('text') * .mandatory(false); * }; + * + * Create a taxonomy field + * + * module.exports =({ migration })=> { + * const blog = migration.editContentType('blog'); + * + * blog.createField('taxonomies') + * .display_name('Taxonomy1') + * .data_type('taxonomy') + * .taxonomies([{ "taxonomy_uid": "test_taxonomy1", "max_terms": 2, "mandatory": false}]) + * .multiple(true) + * .mandatory(false); + * }; */ createField(field, opts) { this.updateContentTypeSchema(field); @@ -227,6 +242,26 @@ class Field extends Base { return this; } + /** + * The 'taxonomies' property should contain at least one taxonomy object + * @param {string | string[]} value list of taxonomies. + * @returns {Field} current instance of field object to chain further methods. + */ + taxonomies(value) { + this.buildSchema(taxonomies, this.field, value); + return this; + } + + /** + * + * @param {boolean} value set true if field is multiple + * @returns {Field} current instance of field object to chain further methods. + */ + multiple(value) { + this.buildSchema(multiple, this.field, value); + return this; + } + /** * * @param {boolean} value set true if refer to multiple content types diff --git a/packages/contentstack-migration/src/utils/constants.js b/packages/contentstack-migration/src/utils/constants.js index 1ebc59fa80..0705c0117c 100644 --- a/packages/contentstack-migration/src/utils/constants.js +++ b/packages/contentstack-migration/src/utils/constants.js @@ -21,6 +21,8 @@ exports.unique = 'unique'; exports.display_name = 'display_name'; exports.reference_to = 'reference_to'; exports.field_metadata = 'field_metadata'; +exports.taxonomies = 'taxonomies'; +exports.multiple = 'multiple'; exports.actions = { CUSTOM_TASK: 'CUSTOM_TASK', diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index 9db67abe81..3a39f4158d 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -1,11 +1,11 @@ { "name": "@contentstack/cli-cm-seed", "description": "create a Stack from existing content types, entries, assets, etc.", - "version": "1.6.0", + "version": "1.6.1", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-cm-import": "~1.10.0", + "@contentstack/cli-cm-import": "~1.11.0", "@contentstack/cli-command": "~1.2.14", "@contentstack/cli-utilities": "~1.5.4", "axios": "1.3.4", diff --git a/packages/contentstack-utilities/package.json b/packages/contentstack-utilities/package.json index 9df6b36e8e..3c6afff4c6 100644 --- a/packages/contentstack-utilities/package.json +++ b/packages/contentstack-utilities/package.json @@ -32,7 +32,7 @@ "author": "contentstack", "license": "MIT", "dependencies": { - "@contentstack/management": "~1.10.2", + "@contentstack/management": "~1.12.0", "@oclif/core": "^2.9.3", "axios": "1.3.4", "chalk": "^4.0.0", diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 1cabac530d..9650e540fe 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli $ csdx COMMAND running command... $ csdx (--version|-v) -@contentstack/cli/1.10.0 darwin-arm64 node-v20.8.0 +@contentstack/cli/1.11.0 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND @@ -2014,27 +2014,28 @@ EXAMPLES ## `csdx cm:export-to-csv` -Export entries or organization users to csv using this command +Export entries, taxonomies, terms or organization users to csv using this command ``` USAGE - $ csdx cm:export-to-csv [--action entries|users] [-a ] [--org ] [-n ] [-k ] [--org-name - ] [--locale ] [--content-type ] [--branch ] + $ csdx cm:export-to-csv [--action entries|users|taxonomies] [-a ] [--org ] [-n ] [-k ] + [--org-name ] [--locale ] [--content-type ] [--branch ] [--taxonomy-uid ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= API key of the source stack -n, --stack-name= Name of the stack that needs to be created as csv filename. --action=