diff --git a/package-lock.json b/package-lock.json index cc52ca525e..c9dda70718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20298,24 +20298,24 @@ }, "packages/contentstack": { "name": "@contentstack/cli", - "version": "1.7.12", + "version": "1.8.0", "license": "MIT", "dependencies": { - "@contentstack/cli-auth": "^1.3.12", - "@contentstack/cli-cm-bootstrap": "^1.4.13", - "@contentstack/cli-cm-branches": "^1.0.10", - "@contentstack/cli-cm-bulk-publish": "^1.3.10", - "@contentstack/cli-cm-clone": "^1.4.14", - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-export-to-csv": "^1.3.12", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-cm-migrate-rte": "^1.4.10", - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-config": "^1.4.10", - "@contentstack/cli-launch": "^1.0.10", - "@contentstack/cli-migration": "^1.3.10", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-auth": "~1.3.12", + "@contentstack/cli-cm-bootstrap": "~1.4.14", + "@contentstack/cli-cm-branches": "~1.0.10", + "@contentstack/cli-cm-bulk-publish": "~1.3.10", + "@contentstack/cli-cm-clone": "~1.4.15", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-export-to-csv": "~1.3.12", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-cm-migrate-rte": "~1.4.10", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-config": "~1.4.10", + "@contentstack/cli-launch": "~1.0.10", + "@contentstack/cli-migration": "~1.3.10", + "@contentstack/cli-utilities": "~1.5.1", "@contentstack/management": "~1.10.0", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", @@ -20368,8 +20368,8 @@ "version": "1.3.12", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.0.0", "debug": "^4.1.1", "inquirer": "8.2.4", @@ -20406,12 +20406,12 @@ }, "packages/contentstack-bootstrap": { "name": "@contentstack/cli-cm-bootstrap", - "version": "1.4.13", + "version": "1.4.14", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "inquirer": "8.2.4", "mkdirp": "^1.0.4", "tar": "^6.1.13" @@ -20488,8 +20488,8 @@ "version": "1.0.10", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -20510,9 +20510,9 @@ "winston": "^3.7.2" }, "devDependencies": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", "@oclif/plugin-help": "^5.1.19", "@oclif/test": "^1.2.6", "@types/flat": "^5.0.2", @@ -20551,8 +20551,8 @@ "version": "1.3.10", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "bluebird": "^3.7.2", "chalk": "^4.1.2", "dotenv": "^16.1.4", @@ -20597,15 +20597,14 @@ }, "packages/contentstack-clone": { "name": "@contentstack/cli-cm-clone", - "version": "1.4.14", + "version": "1.4.15", "license": "MIT", "dependencies": { "@colors/colors": "^1.5.0", - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/management": "~1.10.0", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "async": "^3.2.4", "chalk": "^4.1.0", "child_process": "^1.0.2", @@ -20663,7 +20662,7 @@ "version": "1.2.11", "license": "MIT", "dependencies": { - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-utilities": "~1.5.1", "contentstack": "^3.10.1" }, "devDependencies": { @@ -20737,8 +20736,8 @@ "version": "1.4.10", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.0.0", "debug": "^4.1.1", "inquirer": "8.2.4", @@ -21022,11 +21021,11 @@ }, "packages/contentstack-export": { "name": "@contentstack/cli-cm-export", - "version": "1.7.0", + "version": "1.8.0", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -21046,9 +21045,9 @@ "winston": "^3.7.2" }, "devDependencies": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", "@oclif/plugin-help": "^5.1.19", "@oclif/test": "^1.2.6", "@types/mkdirp": "^1.0.2", @@ -21076,8 +21075,8 @@ "version": "1.3.12", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.1.0", "fast-csv": "^4.3.6", "inquirer": "8.2.4", @@ -21324,11 +21323,11 @@ }, "packages/contentstack-import": { "name": "@contentstack/cli-cm-import", - "version": "1.7.1", + "version": "1.8.0", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@contentstack/management": "~1.10.0", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -21460,8 +21459,8 @@ "license": "MIT", "dependencies": { "@apollo/client": "^3.7.9", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", "@oclif/plugin-plugins": "^2.3.2", @@ -21950,9 +21949,9 @@ "version": "1.4.10", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/json-rte-serializer": "^2.0.2", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", + "@contentstack/json-rte-serializer": "~2.0.2", "chalk": "^4.1.2", "collapse-whitespace": "^1.1.7", "jsdom": "^20.0.3", @@ -21989,8 +21988,8 @@ "version": "1.3.10", "license": "MIT", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "async": "^3.2.4", @@ -22021,12 +22020,12 @@ }, "packages/contentstack-seed": { "name": "@contentstack/cli-cm-seed", - "version": "1.4.13", + "version": "1.4.14", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-import": "^1.7.0", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "axios": "1.3.4", "inquirer": "8.2.4", "mkdirp": "^1.0.4", @@ -23702,21 +23701,21 @@ "@contentstack/cli": { "version": "file:packages/contentstack", "requires": { - "@contentstack/cli-auth": "^1.3.12", - "@contentstack/cli-cm-bootstrap": "^1.4.13", - "@contentstack/cli-cm-branches": "^1.0.10", - "@contentstack/cli-cm-bulk-publish": "^1.3.10", - "@contentstack/cli-cm-clone": "^1.4.14", - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-export-to-csv": "^1.3.12", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-cm-migrate-rte": "^1.4.10", - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-config": "^1.4.10", - "@contentstack/cli-launch": "^1.0.10", - "@contentstack/cli-migration": "^1.3.10", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-auth": "~1.3.12", + "@contentstack/cli-cm-bootstrap": "~1.4.14", + "@contentstack/cli-cm-branches": "~1.0.10", + "@contentstack/cli-cm-bulk-publish": "~1.3.10", + "@contentstack/cli-cm-clone": "~1.4.15", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-export-to-csv": "~1.3.12", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-cm-migrate-rte": "~1.4.10", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-config": "~1.4.10", + "@contentstack/cli-launch": "~1.0.10", + "@contentstack/cli-migration": "~1.3.10", + "@contentstack/cli-utilities": "~1.5.1", "@contentstack/management": "~1.10.0", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", @@ -23759,8 +23758,8 @@ "@contentstack/cli-auth": { "version": "file:packages/contentstack-auth", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@fancy-test/nock": "^0.1.1", "@oclif/plugin-help": "^5.1.19", "@oclif/test": "^2.2.10", @@ -23793,9 +23792,9 @@ "@contentstack/cli-cm-bootstrap": { "version": "file:packages/contentstack-bootstrap", "requires": { - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^2.2.10", "@types/inquirer": "^9.0.3", "@types/mkdirp": "^1.0.1", @@ -23852,11 +23851,11 @@ "@contentstack/cli-cm-branches": { "version": "file:packages/contentstack-branches", "requires": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -23907,8 +23906,8 @@ "@contentstack/cli-cm-bulk-publish": { "version": "file:packages/contentstack-bulk-publish", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^1.2.6", "bluebird": "^3.7.2", "chai": "^4.2.0", @@ -23949,11 +23948,10 @@ "version": "file:packages/contentstack-clone", "requires": { "@colors/colors": "^1.5.0", - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/management": "~1.10.0", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^1.2.7", "async": "^3.2.4", "chai": "^4.2.0", @@ -23997,11 +23995,11 @@ "@contentstack/cli-cm-export": { "version": "file:packages/contentstack-export", "requires": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -24052,8 +24050,8 @@ "@contentstack/cli-cm-export-to-csv": { "version": "file:packages/contentstack-export-to-csv", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^2.2.10", "chai": "^4.2.0", "chalk": "^4.1.0", @@ -24241,8 +24239,8 @@ "@contentstack/cli-cm-import": { "version": "file:packages/contentstack-import", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@contentstack/management": "~1.10.0", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -24358,9 +24356,9 @@ "@contentstack/cli-cm-migrate-rte": { "version": "file:packages/contentstack-migrate-rte", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/json-rte-serializer": "^2.0.2", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", + "@contentstack/json-rte-serializer": "~2.0.2", "@oclif/test": "^2.2.10", "chai": "^4.3.4", "chalk": "^4.1.2", @@ -24392,9 +24390,9 @@ "@contentstack/cli-cm-seed": { "version": "file:packages/contentstack-seed", "requires": { - "@contentstack/cli-cm-import": "^1.7.0", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/plugin-help": "^5.1.19", "@types/inquirer": "^9.0.3", "@types/jest": "^26.0.15", @@ -24453,7 +24451,7 @@ "@contentstack/cli-command": { "version": "file:packages/contentstack-command", "requires": { - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^2.2.10", "@types/chai": "^4.2.18", "@types/mkdirp": "^1.0.1", @@ -24507,8 +24505,8 @@ "@contentstack/cli-config": { "version": "file:packages/contentstack-config", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/test": "^2.2.10", "@types/chai": "^4.2.18", "@types/inquirer": "^9.0.3", @@ -24739,8 +24737,8 @@ "version": "file:packages/contentstack-launch", "requires": { "@apollo/client": "^3.7.9", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/core": "^2.9.3", "@oclif/plugin-help": "^5", "@oclif/plugin-plugins": "^2.3.2", @@ -25083,8 +25081,8 @@ "@contentstack/cli-migration": { "version": "file:packages/contentstack-migration", "requires": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/test": "^2.2.10", diff --git a/packages/contentstack-auth/README.md b/packages/contentstack-auth/README.md index 3ca095069f..2f2c47b143 100644 --- a/packages/contentstack-auth/README.md +++ b/packages/contentstack-auth/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-auth/1.3.12 darwin-arm64 node-v20.3.1 +@contentstack/cli-auth/1.3.12 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-auth/package.json b/packages/contentstack-auth/package.json index a466e7ae2a..2b931ec9bc 100644 --- a/packages/contentstack-auth/package.json +++ b/packages/contentstack-auth/package.json @@ -22,8 +22,8 @@ "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"" }, "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.0.0", "debug": "^4.1.1", "inquirer": "8.2.4", diff --git a/packages/contentstack-bootstrap/README.md b/packages/contentstack-bootstrap/README.md index bbdf5da30c..c7a8e634fb 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.4.13 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-bootstrap/1.4.14 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index e546973d9b..ba1c090620 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.4.13", + "version": "1.4.14", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -17,9 +17,9 @@ "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, "dependencies": { - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "inquirer": "8.2.4", "mkdirp": "^1.0.4", "tar": "^6.1.13" @@ -73,4 +73,4 @@ } }, "repository": "contentstack/cli" -} +} \ No newline at end of file diff --git a/packages/contentstack-branches/README.md b/packages/contentstack-branches/README.md index e961926c4c..02187683e3 100755 --- a/packages/contentstack-branches/README.md +++ b/packages/contentstack-branches/README.md @@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-branches/1.0.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-branches/1.0.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-branches/package.json b/packages/contentstack-branches/package.json index 7b61549cc2..7ed7022517 100644 --- a/packages/contentstack-branches/package.json +++ b/packages/contentstack-branches/package.json @@ -5,8 +5,8 @@ "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -27,9 +27,9 @@ "winston": "^3.7.2" }, "devDependencies": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", "@oclif/plugin-help": "^5.1.19", "@oclif/test": "^1.2.6", "@types/flat": "^5.0.2", diff --git a/packages/contentstack-bulk-publish/README.md b/packages/contentstack-bulk-publish/README.md index c7295eccb1..bb323bc369 100644 --- a/packages/contentstack-bulk-publish/README.md +++ b/packages/contentstack-bulk-publish/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-cm-bulk-publish $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-bulk-publish/1.3.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-bulk-publish/1.3.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-bulk-publish/package.json b/packages/contentstack-bulk-publish/package.json index 7278094763..650f8ea220 100644 --- a/packages/contentstack-bulk-publish/package.json +++ b/packages/contentstack-bulk-publish/package.json @@ -5,8 +5,8 @@ "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "bluebird": "^3.7.2", "chalk": "^4.1.2", "dotenv": "^16.1.4", diff --git a/packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js b/packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js index 959b40755a..57a6206a08 100644 --- a/packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js +++ b/packages/contentstack-bulk-publish/src/commands/cm/stacks/unpublish.js @@ -70,6 +70,9 @@ class UnpublishCommand extends Command { if (await this.confirmFlags(updatedFlags)) { try { + if (process.env.NODE_ENV === 'test') { + return; + } if (!updatedFlags.retryFailed) { await start(updatedFlags, stack, config); } else { diff --git a/packages/contentstack-bulk-publish/src/util/command-helper.js b/packages/contentstack-bulk-publish/src/util/command-helper.js index ec5a8d7075..a405461782 100644 --- a/packages/contentstack-bulk-publish/src/util/command-helper.js +++ b/packages/contentstack-bulk-publish/src/util/command-helper.js @@ -16,7 +16,7 @@ const getSelectedCommand = async () => { name: 'selectedOption', loop: false, }]; - const { selectedOption } = await inquirer.prompt(inquirerOptions); + const selectedOption = await inquirer.prompt(inquirerOptions); return COMMAND_CODE_MAP[selectedOption]; }; diff --git a/packages/contentstack-bulk-publish/test/unit/commands/assets/publish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/assets/publish.test.js index c6d6d869e2..487d19e8b3 100644 --- a/packages/contentstack-bulk-publish/test/unit/commands/assets/publish.test.js +++ b/packages/contentstack-bulk-publish/test/unit/commands/assets/publish.test.js @@ -1,10 +1,9 @@ const { describe, it } = require('mocha'); -const AssetsPublish = require('../../../../src/commands/cm/assets/publish'); -const { cliux } = require('@contentstack/cli-utilities'); const sinon = require('sinon'); +const { expect } = require('chai'); const { config } = require('dotenv'); -const { stub } = sinon; +const AssetsPublish = require('../../../../src/commands/cm/assets/publish'); config(); @@ -12,11 +11,33 @@ const environments = process.env.ENVIRONMENTS.split(','); const locales = process.env.LOCALES.split(','); describe('AssetsPublish', () => { - it('Should run the command when all the flags are passed', async function () { + it('Should run the command when all the flags are passed', async () => { const args = ['--environments', environments[0], '--locales', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; - const inquireStub = stub(cliux, 'inquire'); + const assetPublishSpy = sinon.spy(AssetsPublish.prototype, 'run'); + await AssetsPublish.run(args); + expect(assetPublishSpy.calledOnce).to.be.true; + assetPublishSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--environments', environments[0], '--locales', locales[0], '--yes']; + const assetPublishSpy = sinon.spy(AssetsPublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await AssetsPublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(assetPublishSpy.calledOnce).to.be.true; + } + assetPublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--environments', environments[0], '--locales', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const assetPublishSpy = sinon.spy(AssetsPublish.prototype, 'run'); await AssetsPublish.run(args); - sinon.assert.notCalled(inquireStub); - inquireStub.restore(); + expect(assetPublishSpy.calledOnce).to.be.true; + assetPublishSpy.restore(); }); }); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/assets/unpublish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/assets/unpublish.test.js index 9f25b31856..1aae15ca7d 100644 --- a/packages/contentstack-bulk-publish/test/unit/commands/assets/unpublish.test.js +++ b/packages/contentstack-bulk-publish/test/unit/commands/assets/unpublish.test.js @@ -3,9 +3,8 @@ const { cliux } = require('@contentstack/cli-utilities'); const sinon = require('sinon'); const { config } = require('dotenv'); const { expect } = require('chai'); + const AssetsUnpublish = require('../../../../src/commands/cm/assets/unpublish'); -// const LogoutCommand = require('@contentstack/cli'); -const { test } = require('@oclif/test'); const { stub } = sinon; @@ -31,7 +30,7 @@ describe('AssetsUnpublish', () => { inquireStub.restore(); }); - it('Should fail when alias and stack api key flags are not passed', async function () { + it('Should fail when alias and stack api key flags are not passed', async () => { const args = ['--environment', environments[0], '--locale', locales[0], '--yes']; const inquireStub = stub(cliux, 'prompt'); const assetUnpublishSpy = sinon.spy(AssetsUnpublish.prototype, 'run'); diff --git a/packages/contentstack-bulk-publish/test/unit/commands/bulk-publish/cross-publish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/bulk-publish/cross-publish.test.js index e4283ecdcb..d8831a1990 100644 --- a/packages/contentstack-bulk-publish/test/unit/commands/bulk-publish/cross-publish.test.js +++ b/packages/contentstack-bulk-publish/test/unit/commands/bulk-publish/cross-publish.test.js @@ -3,6 +3,7 @@ const CrossPublish = require('../../../../src/commands/cm/bulk-publish/cross-pub const { cliux } = require('@contentstack/cli-utilities'); const sinon = require('sinon'); const { config } = require('dotenv'); +const { expect } = require('chai'); const { stub } = sinon; @@ -14,9 +15,59 @@ const locales = process.env.LOCALES.split(','); describe('CrossPublish', () => { it('Should run the command when all the flags are passed', async function () { const args = ['--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--delivery-token', process.env.DELIVERY_TOKEN, '--onlyAssets', '--yes']; - const inquireStub = stub(cliux, 'inquire'); + const inquireStub = stub(cliux, 'prompt'); await CrossPublish.run(args); sinon.assert.notCalled(inquireStub); inquireStub.restore(); }); + + it('Should ask for delivery token when the flag is not passed', async () => { + const args = ['--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--onlyAssets', '--yes']; + const inquireStub = stub(cliux, 'prompt').resolves(process.env.DELIVERY_TOKEN); + await CrossPublish.run(args); + sinon.assert.calledOnce(inquireStub); + inquireStub.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--locale', locales[0], '--delivery-token', process.env.DELIVERY_TOKEN, '--onlyAssets', '--yes']; + const inquireStub = stub(cliux, 'prompt'); + const crossPublishSpy = sinon.spy(CrossPublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await CrossPublish.run(args); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(crossPublishSpy.calledOnce).to.be.true; + } + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + crossPublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--locale', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--delivery-token', process.env.DELIVERY_TOKEN, '--onlyAssets', '--yes']; + const inquireStub = stub(cliux, 'prompt'); + await CrossPublish.run(args); + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + }); + + it('Should fail when onlyAssets and onlyEntries flags are passed together', async () => { + const args = ['--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--locale', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--delivery-token', process.env.DELIVERY_TOKEN, '--onlyAssets', '--onlyEntries', '--yes']; + const inquireStub = stub(cliux, 'prompt'); + const crossPublishSpy = sinon.spy(CrossPublish.prototype, 'run'); + const expectedError = 'The flags onlyAssets and onlyEntries need not be used at the same time. Unpublish command unpublishes entries and assts at the same time by default'; + try { + await CrossPublish.run(args); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(crossPublishSpy.calledOnce).to.be.true; + } + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + crossPublishSpy.restore(); + }); }); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-modified.test.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-modified.test.js new file mode 100644 index 0000000000..906d2f2c6e --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-modified.test.js @@ -0,0 +1,44 @@ +const { describe, it } = require('mocha'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { config } = require('dotenv'); + +const EntriesPublishModified = require('../../../../src/commands/cm/entries/publish-modified'); + +config(); + +const environments = process.env.ENVIRONMENTS.split(','); +const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('EntriesPublishModified', () => { + it('Should run the command when all the flags are passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '-e', process.env.DESTINATION_ENV, '--locales', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; + const entriesPublishedModifiedSpy = sinon.spy(EntriesPublishModified.prototype, 'run'); + await EntriesPublishModified.run(args); + expect(entriesPublishedModifiedSpy.calledOnce).to.be.true; + entriesPublishedModifiedSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '-e', process.env.DESTINATION_ENV, '--locales', locales[0], '--yes']; + const entriesPublishedModifiedSpy = sinon.spy(EntriesPublishModified.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesPublishModified.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesPublishedModifiedSpy.calledOnce).to.be.true; + } + entriesPublishedModifiedSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '-e', process.env.DESTINATION_ENV, '--locales', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const entriesPublishedModifiedSpy = sinon.spy(EntriesPublishModified.prototype, 'run'); + await EntriesPublishModified.run(args); + expect(entriesPublishedModifiedSpy.calledOnce).to.be.true; + entriesPublishedModifiedSpy.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-non-localized-fields.test.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-non-localized-fields.test.js new file mode 100644 index 0000000000..b78cf7b654 --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-non-localized-fields.test.js @@ -0,0 +1,44 @@ +const { describe, it } = require('mocha'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { config } = require('dotenv'); + +const EntriesPublishNonLocalizedFields = require('../../../../src/commands/cm/entries/publish-non-localized-fields'); + +config(); + +const environments = process.env.ENVIRONMENTS.split(','); +// const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('EntriesPublishNonLocalizedFields', () => { + it('Should run the command when all the flags are passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; + const entriesPublishNonLocalizedFieldsSpy = sinon.spy(EntriesPublishNonLocalizedFields.prototype, 'run'); + await EntriesPublishNonLocalizedFields.run(args); + expect(entriesPublishNonLocalizedFieldsSpy.calledOnce).to.be.true; + entriesPublishNonLocalizedFieldsSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--yes']; + const entriesPublishNonLocalizedFieldsSpy = sinon.spy(EntriesPublishNonLocalizedFields.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesPublishNonLocalizedFields.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesPublishNonLocalizedFieldsSpy.calledOnce).to.be.true; + } + entriesPublishNonLocalizedFieldsSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-types', contentTypes[0], '--source-env', environments[0], '--environments', process.env.DESTINATION_ENV, '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const entriesPublishNonLocalizedFieldsSpy = sinon.spy(EntriesPublishNonLocalizedFields.prototype, 'run'); + await EntriesPublishNonLocalizedFields.run(args); + expect(entriesPublishNonLocalizedFieldsSpy.calledOnce).to.be.true; + entriesPublishNonLocalizedFieldsSpy.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-only-unpublished.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-only-unpublished.js new file mode 100644 index 0000000000..2f2e881c5c --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish-only-unpublished.js @@ -0,0 +1,46 @@ +const { describe, it } = require('mocha'); +const EntriesPublishOnlyUnpublished = require('../../../../src/commands/cm/entries/publish-only-unpublished'); +const { cliux } = require('@contentstack/cli-utilities'); +const sinon = require('sinon'); +const { config } = require('dotenv'); +const { expect } = require('chai'); + +const { stub } = sinon; + +config(); + +const environments = process.env.ENVIRONMENTS.split(','); +const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('EntriesPublishOnlyUnpublished', () => { + it('Should run the command when all the flags are passed', async () => { + const args = ['-b', '--content-types', contentTypes[0], '--locales', locales[0], '--source-env', environments[0], '-a', process.env.MANAGEMENT_ALIAS, '--yes']; + const entriesPublishOnlyUnpublishedSpy = sinon.spy(EntriesPublishOnlyUnpublished.prototype, 'run'); + await EntriesPublishOnlyUnpublished.run(args); + expect(entriesPublishOnlyUnpublishedSpy.calledOnce).to.be.true; + entriesPublishOnlyUnpublishedSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['-b', '--content-types', contentTypes[0],'--locales', locales[0], '--environments', environments[0], '--yes']; + const entriesPublishOnlyUnpublishedSpy = sinon.spy(EntriesPublishOnlyUnpublished.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesPublishOnlyUnpublished.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesPublishOnlyUnpublishedSpy.calledOnce).to.be.true; + } + entriesPublishOnlyUnpublishedSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['-b', '--content-types', contentTypes[0],'--locales', locales[0], '--environments', environments[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const entriesPublishOnlyUnpublishedSpy = sinon.spy(EntriesPublishOnlyUnpublished.prototype, 'run'); + await EntriesPublishOnlyUnpublished.run(args); + expect(entriesPublishOnlyUnpublishedSpy.calledOnce).to.be.true; + entriesPublishOnlyUnpublishedSpy.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/publish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish.test.js index d8279903e6..3d9640b568 100644 --- a/packages/contentstack-bulk-publish/test/unit/commands/entries/publish.test.js +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/publish.test.js @@ -1,10 +1,9 @@ const { describe, it } = require('mocha'); -const EntriesPublish = require('../../../../src/commands/cm/entries/publish'); -const { cliux } = require('@contentstack/cli-utilities'); const sinon = require('sinon'); +const { expect } = require('chai'); const { config } = require('dotenv'); -const { stub } = sinon; +const EntriesPublish = require('../../../../src/commands/cm/entries/publish'); config(); @@ -13,11 +12,33 @@ const locales = process.env.LOCALES.split(','); const contentTypes = process.env.CONTENT_TYPES.split(','); describe('EntriesPublish', () => { - it('Should run the command when all the flags are passed', async function () { + it('Should run the command when all the flags are passed', async () => { const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; - const inquireStub = stub(cliux, 'inquire'); + const entriesPublishSpy = sinon.spy(EntriesPublish.prototype, 'run'); + await EntriesPublish.run(args); + expect(entriesPublishSpy.calledOnce).to.be.true; + entriesPublishSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--yes']; + const entriesPublishSpy = sinon.spy(EntriesPublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesPublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesPublishSpy.calledOnce).to.be.true; + } + entriesPublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const entriesPublishSpy = sinon.spy(EntriesPublish.prototype, 'run'); await EntriesPublish.run(args); - sinon.assert.notCalled(inquireStub); - inquireStub.restore(); + expect(entriesPublishSpy.calledOnce).to.be.true; + entriesPublishSpy.restore(); }); }); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/unpublish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/unpublish.test.js index d5b4cb4df3..d8232874e9 100644 --- a/packages/contentstack-bulk-publish/test/unit/commands/entries/unpublish.test.js +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/unpublish.test.js @@ -3,6 +3,7 @@ const EntriesUnpublish = require('../../../../src/commands/cm/entries/unpublish' const { cliux } = require('@contentstack/cli-utilities'); const sinon = require('sinon'); const { config } = require('dotenv'); +const { expect } = require('chai'); const { stub } = sinon; @@ -15,7 +16,40 @@ const contentTypes = process.env.CONTENT_TYPES.split(','); describe('EntriesUnpublish', () => { it('Should run the command when all the flags are passed', async function () { const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; - const inquireStub = stub(cliux, 'inquire'); + const inquireStub = stub(cliux, 'prompt'); + await EntriesUnpublish.run(args); + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + }); + + it('Should ask for delivery token when the flag is not passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; + const inquireStub = stub(cliux, 'prompt').resolves(process.env.DELIVERY_TOKEN); + await EntriesUnpublish.run(args); + sinon.assert.calledOnce(inquireStub); + inquireStub.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; + const inquireStub = stub(cliux, 'prompt'); + const entriesUnpublishSpy = sinon.spy(EntriesUnpublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesUnpublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesUnpublishSpy.calledOnce).to.be.true; + } + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + entriesUnpublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; + const inquireStub = stub(cliux, 'prompt'); await EntriesUnpublish.run(args); sinon.assert.notCalled(inquireStub); inquireStub.restore(); diff --git a/packages/contentstack-bulk-publish/test/unit/commands/entries/update-and-publish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/entries/update-and-publish.test.js new file mode 100644 index 0000000000..7a02410f49 --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/entries/update-and-publish.test.js @@ -0,0 +1,45 @@ +const { describe, it } = require('mocha'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { config } = require('dotenv'); + +const EntriesUpdateAndPublish = require('../../../../src/commands/cm/entries/update-and-publish'); + +config(); + + +const environments = process.env.ENVIRONMENTS.split(','); +const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('EntriesUpdateAndPublish', () => { + it('Should run the command when all the flags are passed', async () => { + const args = ['--content-types', contentTypes[0], '-e', environments[0], '--locales', locales[0], '-a', process.env.MANAGEMENT_ALIAS, '--yes']; + const entriesUpdateAndPublishSpy = sinon.spy(EntriesUpdateAndPublish.prototype, 'run'); + await EntriesUpdateAndPublish.run(args); + expect(entriesUpdateAndPublishSpy.calledOnce).to.be.true; + entriesUpdateAndPublishSpy.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-types', contentTypes[0], '-e', environments[0], '--locales', locales[0], '--yes']; + const entriesUpdateAndPublishSpy = sinon.spy(EntriesUpdateAndPublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await EntriesUpdateAndPublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(entriesUpdateAndPublishSpy.calledOnce).to.be.true; + } + entriesUpdateAndPublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-types', contentTypes[0], '-e', environments[0], '--locales', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const entriesUpdateAndPublishSpy = sinon.spy(EntriesUpdateAndPublish.prototype, 'run'); + await EntriesUpdateAndPublish.run(args); + expect(entriesUpdateAndPublishSpy.calledOnce).to.be.true; + entriesUpdateAndPublishSpy.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/stacks/publish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/stacks/publish.test.js new file mode 100644 index 0000000000..7162c82f54 --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/stacks/publish.test.js @@ -0,0 +1,52 @@ +const { describe, it } = require('mocha'); +const inquirer = require('inquirer'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { config } = require('dotenv'); +const StackPublish = require('../../../../src/commands/cm/stacks/publish'); +const { stub } = sinon; + +config(); + +const environments = process.env.ENVIRONMENTS.split(','); +const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('StackPublish', () => { + it('Should run the command when all the flags are passed', async () => { + const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; + const stackPublishSpy = sinon.spy(StackPublish.prototype, 'run'); + const inquireStub = stub(inquirer, 'prompt').resolves(process.env.STACK_PUBLISH_PROMPT_ANSWER.trim() || 'Publish Entries and Assets'); + await StackPublish.run(args); + expect(stackPublishSpy.calledOnce).to.be.true; + sinon.assert.calledOnce(inquireStub); + stackPublishSpy.restore(); + inquireStub.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--yes']; + const stackPublishSpy = sinon.spy(StackPublish.prototype, 'run'); + const inquireStub = stub(inquirer, 'prompt').resolves(process.env.STACK_PUBLISH_PROMPT_ANSWER.trim() || 'Publish Entries and Assets'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await StackPublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(stackPublishSpy.calledOnce).to.be.true; + } + stackPublishSpy.restore(); + inquireStub.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-types', contentTypes[0], '--environments', environments[0], '--locales', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--yes']; + const stackPublishSpy = sinon.spy(StackPublish.prototype, 'run'); + const inquireStub = stub(inquirer, 'prompt').resolves(process.env.STACK_PUBLISH_PROMPT_ANSWER.trim() || 'Publish Entries and Assets'); + await StackPublish.run(args); + expect(stackPublishSpy.calledOnce).to.be.true; + stackPublishSpy.restore(); + inquireStub.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-bulk-publish/test/unit/commands/stacks/unpublish.test.js b/packages/contentstack-bulk-publish/test/unit/commands/stacks/unpublish.test.js new file mode 100644 index 0000000000..e1f41aa019 --- /dev/null +++ b/packages/contentstack-bulk-publish/test/unit/commands/stacks/unpublish.test.js @@ -0,0 +1,57 @@ +const { describe, it } = require('mocha'); +const StackUnpublish = require('../../../../src/commands/cm/stacks/unpublish'); +const { cliux } = require('@contentstack/cli-utilities') +const sinon = require('sinon'); +const { config } = require('dotenv'); +const { expect } = require('chai'); + +const { stub } = sinon; + +config(); + +const environments = process.env.ENVIRONMENTS.split(','); +const locales = process.env.LOCALES.split(','); +const contentTypes = process.env.CONTENT_TYPES.split(','); + +describe('StackUnpublish', () => { + it('Should run the command when all the flags are passed', async function () { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; + const inquireStub = stub(cliux, 'prompt'); + await StackUnpublish.run(args); + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + }); + + it('Should ask for delivery token when the flag is not passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--alias', process.env.MANAGEMENT_ALIAS, '--yes']; + const inquireStub = stub(cliux, 'prompt').resolves(process.env.DELIVERY_TOKEN); + await StackUnpublish.run(args); + sinon.assert.calledOnce(inquireStub); + inquireStub.restore(); + }); + + it('Should fail when alias and stack api key flags are not passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; + const inquireStub = stub(cliux, 'prompt'); + const stacksUnpublishSpy = sinon.spy(StackUnpublish.prototype, 'run'); + const expectedError = 'Please use `--alias` or `--stack-api-key` to proceed.'; + try { + await StackUnpublish.run(args); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal(expectedError); + expect(stacksUnpublishSpy.calledOnce).to.be.true; + } + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + stacksUnpublishSpy.restore(); + }); + + it('Should run successfully when user is logged in and stack api key is passed', async () => { + const args = ['--content-type', contentTypes[0], '--environment', environments[0], '--locale', locales[0], '--stack-api-key', process.env.STACK_API_KEY, '--delivery-token', process.env.DELIVERY_TOKEN, '--yes']; + const inquireStub = stub(cliux, 'prompt'); + await StackUnpublish.run(args); + sinon.assert.notCalled(inquireStub); + inquireStub.restore(); + }); +}); \ No newline at end of file diff --git a/packages/contentstack-clone/README.md b/packages/contentstack-clone/README.md index 4174754a85..a18e49330c 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.4.14 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-clone/1.4.15 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index 0952846315..77ff385980 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -1,15 +1,14 @@ { "name": "@contentstack/cli-cm-clone", "description": "Contentstack stack clone plugin", - "version": "1.4.14", + "version": "1.4.15", "author": "Contentstack", "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues", "dependencies": { - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/management": "~1.10.0", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@colors/colors": "^1.5.0", "async": "^3.2.4", "chalk": "^4.1.0", @@ -71,4 +70,4 @@ "cm:stack-clone": "O-CLN" } } -} +} \ No newline at end of file diff --git a/packages/contentstack-command/package.json b/packages/contentstack-command/package.json index d5b6315651..d9788f9e50 100644 --- a/packages/contentstack-command/package.json +++ b/packages/contentstack-command/package.json @@ -17,7 +17,7 @@ "format": "eslint src/**/*.ts --fix" }, "dependencies": { - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-utilities": "~1.5.1", "contentstack": "^3.10.1" }, "devDependencies": { diff --git a/packages/contentstack-config/README.md b/packages/contentstack-config/README.md index 831a9ddd87..a8715d82b1 100644 --- a/packages/contentstack-config/README.md +++ b/packages/contentstack-config/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-config/1.4.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-config/1.4.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-config/package.json b/packages/contentstack-config/package.json index 1f07540eb9..c97ab376da 100644 --- a/packages/contentstack-config/package.json +++ b/packages/contentstack-config/package.json @@ -21,8 +21,8 @@ "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"" }, "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.0.0", "debug": "^4.1.1", "mkdirp": "^1.0.4", diff --git a/packages/contentstack-export-to-csv/package.json b/packages/contentstack-export-to-csv/package.json index 1ac48ef637..8dd894b7aa 100644 --- a/packages/contentstack-export-to-csv/package.json +++ b/packages/contentstack-export-to-csv/package.json @@ -5,8 +5,8 @@ "author": "Abhinav Gupta @abhinav-from-contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "chalk": "^4.1.0", "fast-csv": "^4.3.6", "inquirer": "8.2.4", diff --git a/packages/contentstack-export/README.md b/packages/contentstack-export/README.md index a974058efc..b78b5f62df 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.7.0 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-export/1.8.0 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-export/example_config/management_config.json b/packages/contentstack-export/example_config/management_config.json index 23cf19e331..d2dd367007 100644 --- a/packages/contentstack-export/example_config/management_config.json +++ b/packages/contentstack-export/example_config/management_config.json @@ -13,5 +13,6 @@ "locales" ], "useNewModuleStructure": true, - "securedAssets": false + "securedAssets": false, + "createBackupDir": "./temp" } \ No newline at end of file diff --git a/packages/contentstack-export/package.json b/packages/contentstack-export/package.json index e9a1149102..45aac8ab41 100644 --- a/packages/contentstack-export/package.json +++ b/packages/contentstack-export/package.json @@ -1,12 +1,12 @@ { "name": "@contentstack/cli-cm-export", "description": "Contentstack CLI plugin to export content from stack", - "version": "1.7.0", + "version": "1.8.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", @@ -26,9 +26,9 @@ "winston": "^3.7.2" }, "devDependencies": { - "@contentstack/cli-auth": "^1.3.11", - "@contentstack/cli-config": "^1.4.9", - "@contentstack/cli-dev-dependencies": "^1.2.3", + "@contentstack/cli-auth": "~1.3.11", + "@contentstack/cli-config": "~1.4.9", + "@contentstack/cli-dev-dependencies": "~1.2.3", "@oclif/plugin-help": "^5.1.19", "@oclif/test": "^1.2.6", "@types/mkdirp": "^1.0.2", @@ -62,6 +62,7 @@ "lint": "eslint src/**/*.ts", "format": "eslint src/**/*.ts --fix", "test:integration": "INTEGRATION_TEST=true mocha --config ./test/.mocharc.js --forbid-only \"test/run.test.js\"", + "test:integration:report": "INTEGRATION_TEST=true nyc --extension .js mocha --forbid-only \"test/run.test.js\"", "test:unit": "mocha --forbid-only \"test/unit/*.test.ts\"" }, "engines": { diff --git a/packages/contentstack-export/src/config/index.ts b/packages/contentstack-export/src/config/index.ts index c25532d772..52a0bd214b 100644 --- a/packages/contentstack-export/src/config/index.ts +++ b/packages/contentstack-export/src/config/index.ts @@ -137,6 +137,7 @@ const config: DefaultConfig = { // total no of entries fetched in each content type in a single call limit: 100, dependencies: ['locales', 'content-types'], + exportVersions: false, }, extensions: { dirName: 'extensions', @@ -378,6 +379,9 @@ const config: DefaultConfig = { 'custom-roles', 'global-fields', 'content-types', + 'entries', + 'workflows', + 'stack', ], apis: { userSession: '/user-session/', diff --git a/packages/contentstack-export/src/export/modules-js/entries.js b/packages/contentstack-export/src/export/modules-js/entries.js index ae6a402e38..e3aaf126e0 100644 --- a/packages/contentstack-export/src/export/modules-js/entries.js +++ b/packages/contentstack-export/src/export/modules-js/entries.js @@ -1,5 +1,6 @@ const path = require('path'); const chalk = require('chalk'); +const { values } = require('lodash'); const { executeTask, formatError, fileHelper, log } = require('../../utils'); class EntriesExport { @@ -59,7 +60,7 @@ class EntriesExport { `Started export versioned entries of type '${requestOption.content_type}' locale '${requestOption.locale}'`, 'info', ); - for (let entry of entries) { + for (let entry of values(entries)) { const versionedEntries = await this.getEntryByVersion( { ...requestOption, diff --git a/packages/contentstack-export/src/export/modules-js/stack.js b/packages/contentstack-export/src/export/modules-js/stack.js index 157b99b269..4f4e8329c4 100644 --- a/packages/contentstack-export/src/export/modules-js/stack.js +++ b/packages/contentstack-export/src/export/modules-js/stack.js @@ -49,8 +49,8 @@ class ExportStack { return self.getLocales(apiDetails); } else if (self.config.preserveStackVersion) { log(self.config, 'Exporting stack details', 'success'); - let stackFolderPath = path.resolve(self.config.data, stackConfig.dirName); - let stackContentsFile = path.resolve(stackFolderPath, stackConfig.fileName); + let stackFolderPath = path.resolve(self.config.data, this.stackConfig.dirName); + let stackContentsFile = path.resolve(stackFolderPath, this.stackConfig.fileName); mkdirp.sync(stackFolderPath); diff --git a/packages/contentstack-export/src/export/modules/base-class.ts b/packages/contentstack-export/src/export/modules/base-class.ts index 7fffb256bc..90cb015942 100644 --- a/packages/contentstack-export/src/export/modules/base-class.ts +++ b/packages/contentstack-export/src/export/modules/base-class.ts @@ -30,6 +30,7 @@ export type EnvType = { export type CustomPromiseHandlerInput = { index: number; batchIndex: number; + element?: Record; apiParams?: ApiOptions; isLastRequest: boolean; }; @@ -45,6 +46,7 @@ export type ApiModuleType = | 'content-type' | 'content-types' | 'stacks' + | 'versioned-entries' | 'download-asset'; export default abstract class BaseClass { @@ -98,6 +100,7 @@ export default abstract class BaseClass { if (promisifyHandler instanceof Function) { promise = promisifyHandler({ apiParams, + element, isLastRequest, index: Number(index), batchIndex: Number(batchIndex), diff --git a/packages/contentstack-export/src/export/modules/entries.ts b/packages/contentstack-export/src/export/modules/entries.ts new file mode 100644 index 0000000000..f795cee78c --- /dev/null +++ b/packages/contentstack-export/src/export/modules/entries.ts @@ -0,0 +1,233 @@ +import * as path from 'path'; +import { ContentstackClient, FsUtility } from '@contentstack/cli-utilities'; +import { log, formatError, fsUtil } from '../../utils'; +import { ExportConfig, ModuleClassParams } from '../../types'; +import BaseClass, { ApiOptions } from './base-class'; + +export default class EntriesExport extends BaseClass { + private stackAPIClient: ReturnType; + public exportConfig: ExportConfig; + private entriesConfig: { + dirName?: string; + fileName?: string; + invalidKeys?: string[]; + fetchConcurrency?: number; + writeConcurrency?: number; + limit?: number; + chunkFileSize?: number; + batchLimit?: number; + exportVersions: boolean; + }; + private entriesDirPath: string; + private localesFilePath: string; + private schemaFilePath: string; + private entriesFileHelper: FsUtility; + + constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { + super({ exportConfig, stackAPIClient }); + this.stackAPIClient = stackAPIClient; + this.exportConfig = exportConfig; + this.entriesConfig = exportConfig.modules.entries; + this.entriesDirPath = path.resolve(exportConfig.data, exportConfig.branchName || '', this.entriesConfig.dirName); + this.localesFilePath = path.resolve( + exportConfig.data, + exportConfig.branchName || '', + exportConfig.modules.locales.dirName, + exportConfig.modules.locales.fileName, + ); + this.schemaFilePath = path.resolve( + exportConfig.data, + exportConfig.branchName || '', + exportConfig.modules.content_types.dirName, + 'schema.json', + ); + } + + async start() { + try { + log(this.exportConfig, 'Starting entries export', 'info'); + const locales = fsUtil.readFile(this.localesFilePath) as Array>; + const contentTypes = fsUtil.readFile(this.schemaFilePath) as Array>; + if (contentTypes.length === 0) { + log(this.exportConfig, 'No content types found to export entries', 'info'); + return; + } + const entryRequestOptions = this.createRequestObjects(locales, contentTypes); + for (let entryRequestOption of entryRequestOptions) { + log( + this.exportConfig, + `Starting export of entries of content type - ${entryRequestOption.contentType} locale - ${entryRequestOption.locale}`, + 'info', + ); + await this.getEntries(entryRequestOption); + this.entriesFileHelper?.completeFile(true); + log( + this.exportConfig, + `Exported entries of type '${entryRequestOption.contentType}' locale '${entryRequestOption.locale}'`, + 'success', + ); + } + log(this.exportConfig, 'Entries exported successfully', 'success'); + } catch (error) { + log(this.exportConfig, `Failed to export entries ${formatError(error)}`, 'error'); + throw new Error('Failed to export entries'); + } + } + + createRequestObjects( + locales: Array>, + contentTypes: Array>, + ): Array> { + let requestObjects: Array> = []; + contentTypes.forEach((contentType) => { + if (Object.keys(locales).length !== 0) { + for (let locale in locales) { + requestObjects.push({ + contentType: contentType.uid, + locale: locales[locale].code, + }); + } + } + requestObjects.push({ + contentType: contentType.uid, + locale: this.exportConfig.master_locale.code, + }); + }); + + return requestObjects; + } + + async getEntries(options: Record): Promise { + options.skip = options.skip || 0; + let requestObject = { + locale: options.locale, + skip: options.skip, + limit: this.entriesConfig.limit, + include_count: true, + include_publish_details: true, + query: { + locale: options.locale, + }, + }; + + const entriesSearchResponse = await this.stackAPIClient + .contentType(options.contentType) + .entry() + .query(requestObject) + .find(); + + if (Array.isArray(entriesSearchResponse.items) && entriesSearchResponse.items.length > 0) { + if (options.skip === 0) { + const entryBasePath = path.join(this.entriesDirPath, options.contentType, options.locale); + await fsUtil.makeDirectory(entryBasePath); + this.entriesFileHelper = new FsUtility({ + moduleName: 'entries', + indexFileName: 'index.json', + basePath: entryBasePath, + chunkFileSize: this.entriesConfig.chunkFileSize, + keepMetadata: false, + omitKeys: this.entriesConfig.invalidKeys, + }); + } + this.entriesFileHelper.writeIntoFile(entriesSearchResponse.items, { mapKeyVal: true }); + if (this.entriesConfig.exportVersions) { + let versionedEntryPath = path.join(this.entriesDirPath, options.contentType, options.locale, 'versions'); + fsUtil.makeDirectory(versionedEntryPath); + await this.fetchEntriesVersions(entriesSearchResponse.items, { + locale: options.locale, + contentType: options.contentType, + versionedEntryPath, + }); + } + options.skip += this.entriesConfig.limit || 100; + if (options.skip >= entriesSearchResponse.count) { + return Promise.resolve(true); + } + return await this.getEntries(options); + } + } + + async fetchEntriesVersions( + entries: any, + options: { locale: string; contentType: string; versionedEntryPath: string }, + ): Promise { + const onSuccess = ({ response, apiData: entry }: any) => { + fsUtil.writeFile(path.join(options.versionedEntryPath, `${entry.uid}.json`), response); + log( + this.exportConfig, + `Exported versioned entries of type '${options.contentType}' locale '${options.locale}'`, + 'success', + ); + }; + const onReject = ({ error, apiData: { uid } = undefined }: any) => { + log(this.exportConfig, `failed to export versions of entry ${uid}`, 'error'); + log(this.exportConfig, formatError(error), 'error'); + }; + + return await this.makeConcurrentCall( + { + apiBatches: [entries], + module: 'versioned-entries', + totalCount: entries.length, + concurrencyLimit: this.entriesConfig.batchLimit, + apiParams: { + module: 'versioned-entries', + queryParam: options, + resolve: onSuccess, + reject: onReject, + }, + }, + this.entryVersionHandler.bind(this), + ); + } + + async entryVersionHandler({ + apiParams, + element: entry, + }: { + apiParams: ApiOptions; + element: Record; + isLastRequest: boolean; + }) { + return new Promise(async (resolve, reject) => { + return await this.getEntryByVersion(apiParams.queryParam, entry) + .then((response) => { + apiParams.resolve({ + response, + apiData: entry, + }); + resolve(true); + }) + .catch((error) => { + apiParams.reject({ + error, + apiData: entry, + }); + reject(true); + }); + }); + } + + async getEntryByVersion( + options: any, + entry: Record, + entries: Array> = [], + ): Promise { + const queryRequestObject = { + locale: options.locale, + except: { + BASE: this.entriesConfig.invalidKeys, + }, + version: entry._version, + }; + const entryResponse = await this.stackAPIClient + .contentType(options.contentType) + .entry(entry.uid) + .fetch(queryRequestObject); + entries.push(entryResponse); + if (--entry._version > 0) { + return await this.getEntryByVersion(options, entry, entries); + } + return entries; + } +} diff --git a/packages/contentstack-export/src/export/modules/locales.ts b/packages/contentstack-export/src/export/modules/locales.ts index 365efc78d4..5043882215 100644 --- a/packages/contentstack-export/src/export/modules/locales.ts +++ b/packages/contentstack-export/src/export/modules/locales.ts @@ -81,7 +81,8 @@ export default class LocaleExport extends BaseClass { delete locale[key]; } } - if (locale.code === this.exportConfig.master_locale.code) { + + if (locale?.code === this.exportConfig?.master_locale?.code) { this.masterLocale[locale.uid] = locale; } else { this.locales[locale.uid] = locale; diff --git a/packages/contentstack-export/src/export/modules/stack.ts b/packages/contentstack-export/src/export/modules/stack.ts new file mode 100644 index 0000000000..e05ddffcee --- /dev/null +++ b/packages/contentstack-export/src/export/modules/stack.ts @@ -0,0 +1,98 @@ +import find from 'lodash/find'; +import { resolve as pResolve } from 'node:path'; +import { isAuthenticated, managementSDKClient } from '@contentstack/cli-utilities'; + +import config from '../../config'; +import BaseClass from './base-class'; +import { log, formatError, fsUtil } from '../../utils'; +import { StackConfig, ModuleClassParams } from '../../types'; + +export default class ExportStack extends BaseClass { + private stackConfig: StackConfig; + private stackFolderPath: string; + private qs: { + include_count: boolean; + skip?: number; + }; + + constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { + super({ exportConfig, stackAPIClient }); + this.stackConfig = config.modules.stack; + this.qs = { include_count: true }; + this.stackFolderPath = pResolve(this.exportConfig.data, this.stackConfig.dirName); + } + + async start(): Promise { + if (isAuthenticated()) { + const stackData = await this.getStack(); + if (stackData?.org_uid) { + this.exportConfig.org_uid = stackData.org_uid; + this.exportConfig.sourceStackName = stackData.name; + } + } + if (!this.exportConfig.preserveStackVersion && !this.exportConfig.hasOwnProperty('master_locale')) { + //fetch master locale details + return this.getLocales(); + } else if (this.exportConfig.preserveStackVersion) { + return this.exportStack(); + } + } + + async getStack(): Promise { + const tempAPIClient = await managementSDKClient({ host: this.exportConfig.host }); + return await tempAPIClient + .stack({ api_key: this.exportConfig.source_stack }) + .fetch() + .catch((error: any) => { + log(this.exportConfig, `Failed to export stack. ${formatError(error)}`, 'error'); + }); + } + + async getLocales(skip: number = 0) { + if (skip) { + this.qs.skip = skip; + } + return await this.stack + .locale() + .query(this.qs) + .find() + .then(async (data: any) => { + const { items, count } = data; + if (items?.length) { + skip += this.stackConfig.limit || 100; + const masterLocalObj = find(items, (locale: any) => { + if (locale.fallback_locale === null) { + return locale; + } + }); + if (masterLocalObj) { + return masterLocalObj; + } else if (skip >= count) { + log(this.exportConfig, 'Master locale not found', 'error'); + return; + } else { + return await this.getLocales(skip); + } + } + }) + .catch((error: any) => { + log(this.exportConfig, `Failed to export locales. ${formatError(error)}`, 'error'); + log(this.exportConfig, error, 'error'); + }); + } + + async exportStack(): Promise { + log(this.exportConfig, 'Exporting stack details', 'success'); + await fsUtil.makeDirectory(this.stackFolderPath); + return this.stack + .fetch() + .then((resp: any) => { + fsUtil.writeFile(pResolve(this.stackFolderPath, this.stackConfig.fileName), resp); + log(this.exportConfig, 'Exported stack details successfully!', 'success'); + return resp; + }) + .catch((error: any) => { + log(this.exportConfig, `Failed to export stack. ${formatError(error)}`, 'error'); + }); + } +} diff --git a/packages/contentstack-export/src/export/modules/webhooks.ts b/packages/contentstack-export/src/export/modules/webhooks.ts index f7662dd186..77b7b99e4f 100644 --- a/packages/contentstack-export/src/export/modules/webhooks.ts +++ b/packages/contentstack-export/src/export/modules/webhooks.ts @@ -62,7 +62,7 @@ export default class ExportWebhooks extends BaseClass { return await this.getWebhooks(skip); } }) - .catch(({ error }: any) => { + .catch((error: any) => { log(this.exportConfig, `Failed to export webhooks.${formatError(error)}`, 'error'); log(this.exportConfig, error, 'error'); }); diff --git a/packages/contentstack-export/src/export/modules/workflows.ts b/packages/contentstack-export/src/export/modules/workflows.ts new file mode 100644 index 0000000000..cdce55e359 --- /dev/null +++ b/packages/contentstack-export/src/export/modules/workflows.ts @@ -0,0 +1,100 @@ +import omit from 'lodash/omit'; +import isEmpty from 'lodash/isEmpty'; +import { resolve as pResolve } from 'node:path'; + +import config from '../../config'; +import BaseClass from './base-class'; +import { log, formatError, fsUtil } from '../../utils'; +import { WorkflowConfig, ModuleClassParams } from '../../types'; + +export default class ExportWorkFlows extends BaseClass { + private workflows: Record>; + private workflowConfig: WorkflowConfig; + public webhooksFolderPath: string; + private qs: { + include_count: boolean; + skip?: number; + }; + + constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { + super({ exportConfig, stackAPIClient }); + this.workflows = {}; + this.workflowConfig = config.modules.workflows; + this.qs = { include_count: true }; + } + + async start(): Promise { + log(this.exportConfig, 'Starting workflows export', 'info'); + + this.webhooksFolderPath = pResolve( + this.exportConfig.data, + this.exportConfig.branchName || '', + this.workflowConfig.dirName, + ); + + await fsUtil.makeDirectory(this.webhooksFolderPath); + await this.getWorkflows(); + + if (this.workflows === undefined || isEmpty(this.workflows)) { + log(this.exportConfig, 'No workflows found', 'info'); + } else { + fsUtil.writeFile(pResolve(this.webhooksFolderPath, this.workflowConfig.fileName), this.workflows); + log(this.exportConfig, 'All the workflows have been exported successfully!', 'success'); + } + } + + async getWorkflows(skip = 0): Promise { + if (skip) { + this.qs.skip = skip; + } + + await this.stack + .workflow() + .fetchAll(this.qs) + .then(async (data: any) => { + const { items, count } = data; + if (items?.length) { + await this.sanitizeAttribs(items); + skip += this.workflowConfig.limit || 100; + if (skip >= count) { + return; + } + return await this.getWorkflows(skip); + } + }) + .catch((error: any) => { + log(this.exportConfig, `Failed to export workflows.${formatError(error)}`, 'error'); + log(this.exportConfig, error, 'error'); + }); + } + + async sanitizeAttribs(workflows: Record[]) { + for (let index = 0; index < workflows?.length; index++) { + await this.getWorkflowRoles(workflows[index]); + const workflowUid = workflows[index].uid; + const workflowName = workflows[index]?.name || ''; + this.workflows[workflowUid] = omit(workflows[index], this.workflowConfig.invalidKeys); + log(this.exportConfig, `'${workflowName}' workflow was exported successfully`, 'success'); + } + } + + async getWorkflowRoles(workflow: Record) { + for (const stage of workflow?.workflow_stages) { + for (let i = 0; i < stage?.SYS_ACL?.roles?.uids?.length; i++) { + const roleUid = stage.SYS_ACL.roles.uids[i]; + const roleData = await this.getRoles(roleUid); + stage.SYS_ACL.roles.uids[i] = roleData; + } + } + } + + async getRoles(roleUid: number): Promise { + return await this.stack + .role(roleUid) + .fetch({ include_rules: true, include_permissions: true }) + .then((data: any) => data) + .catch((err: any) => + log(this.exportConfig, `Failed to fetch roles.${formatError(err)}`, 'error'), + ); + } +} diff --git a/packages/contentstack-export/src/types/default-config.ts b/packages/contentstack-export/src/types/default-config.ts index 56187de12f..918786bdac 100644 --- a/packages/contentstack-export/src/types/default-config.ts +++ b/packages/contentstack-export/src/types/default-config.ts @@ -113,6 +113,7 @@ export default interface DefaultConfig { // total no of entries fetched in each content type in a single call limit: number; dependencies?: Modules[]; + exportVersions: boolean; }; extensions: { dirName: string; diff --git a/packages/contentstack-export/src/types/export-config.ts b/packages/contentstack-export/src/types/export-config.ts index 5aa1e7de24..a56db980e6 100644 --- a/packages/contentstack-export/src/types/export-config.ts +++ b/packages/contentstack-export/src/types/export-config.ts @@ -27,6 +27,7 @@ export default interface ExportConfig extends DefaultConfig { access_token?: string; org_uid?: string; source_stack?: string; + sourceStackName?:string; } type branch = { diff --git a/packages/contentstack-export/src/types/index.ts b/packages/contentstack-export/src/types/index.ts index 3839a6de98..ccf95c0918 100644 --- a/packages/contentstack-export/src/types/index.ts +++ b/packages/contentstack-export/src/types/index.ts @@ -114,5 +114,12 @@ export interface CustomRoleConfig{ dependencies?: Modules[]; } +export interface StackConfig{ + dirName: string; + fileName: string; + dependencies?: Modules[]; + limit?: number; +} + export { default as DefaultConfig } from './default-config'; export { default as ExportConfig } from './export-config'; diff --git a/packages/contentstack-export/test/integration/assets.test.js b/packages/contentstack-export/test/integration/assets.test.js index 7f00c19c1c..9359718e81 100644 --- a/packages/contentstack-export/test/integration/assets.test.js +++ b/packages/contentstack-export/test/integration/assets.test.js @@ -1,11 +1,10 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const uniqBy = require('lodash/uniqBy'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getAssetAndFolderCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -29,11 +28,12 @@ module.exports = (region) => { describe('cm:stacks:export assets [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -105,9 +105,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/content-types.test.js b/packages/contentstack-export/test/integration/content-types.test.js index 336694ab32..20524d8394 100644 --- a/packages/contentstack-export/test/integration/content-types.test.js +++ b/packages/contentstack-export/test/integration/content-types.test.js @@ -1,11 +1,10 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const { promises: fsPromises } = fs; const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getContentTypesCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -28,11 +27,12 @@ module.exports = (region) => { describe('cm:stacks:export content-types [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -92,9 +92,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/custom-roles.test.js b/packages/contentstack-export/test/integration/custom-roles.test.js index 824805dc9f..eab4871ebc 100644 --- a/packages/contentstack-export/test/integration/custom-roles.test.js +++ b/packages/contentstack-export/test/integration/custom-roles.test.js @@ -1,10 +1,9 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getCustomRolesCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -18,20 +17,17 @@ module.exports = (region) => { : path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`); const customRolesBasePath = path.join(exportBasePath, modules.customRoles.dirName); const customRolesJson = path.join(customRolesBasePath, modules.customRoles.fileName); - const messageFilePath = path.join(__dirname, '..', '..', 'messages/index.json'); - - messageHandler.init({ messageFilePath }); - const { promptMessageList } = require(messageFilePath); describe('ContentStack-Export custom-roles', () => { describe('cm:stacks:export custom-roles [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -86,9 +82,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/global-fields.test.js b/packages/contentstack-export/test/integration/global-fields.test.js index 8d7ada352e..a6a0fa3323 100644 --- a/packages/contentstack-export/test/integration/global-fields.test.js +++ b/packages/contentstack-export/test/integration/global-fields.test.js @@ -1,10 +1,9 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getGlobalFieldsCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -18,20 +17,17 @@ module.exports = (region) => { : path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`); const globalFieldsBasePath = path.join(exportBasePath, modules.globalfields.dirName); const globalFieldsJson = path.join(globalFieldsBasePath, modules.globalfields.fileName); - const messageFilePath = path.join(__dirname, '..', '..', 'messages/index.json'); - - messageHandler.init({ messageFilePath }); - const { promptMessageList } = require(messageFilePath); describe('ContentStack-Export global-fields', () => { describe('cm:stacks:export global-fields [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -85,9 +81,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/labels.test.js b/packages/contentstack-export/test/integration/labels.test.js index 9b599925ba..907667de58 100644 --- a/packages/contentstack-export/test/integration/labels.test.js +++ b/packages/contentstack-export/test/integration/labels.test.js @@ -1,10 +1,9 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getLabelsCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -27,11 +26,12 @@ module.exports = (region) => { describe('cm:stacks:export labels [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -85,9 +85,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/locales.test.js b/packages/contentstack-export/test/integration/locales.test.js index ea8c5dbfc0..d0e95fd217 100644 --- a/packages/contentstack-export/test/integration/locales.test.js +++ b/packages/contentstack-export/test/integration/locales.test.js @@ -1,10 +1,9 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getLocalesCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -19,19 +18,17 @@ module.exports = (region) => { const localeBasePath = path.join(exportBasePath, modules.locales.dirName); const localeJson = path.join(localeBasePath, modules.locales.fileName); - const messageFilePath = path.join(__dirname, '..', '..', 'messages/index.json'); - messageHandler.init({ messageFilePath }); - const { promptMessageList } = require(messageFilePath); describe('ContentStack-Export locales', () => { describe('cm:stacks:export locales [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -87,9 +84,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-export/test/integration/marketplace-apps.test.js b/packages/contentstack-export/test/integration/marketplace-apps.test.js index 5ee81acf39..2c224d9d1e 100644 --- a/packages/contentstack-export/test/integration/marketplace-apps.test.js +++ b/packages/contentstack-export/test/integration/marketplace-apps.test.js @@ -1,7 +1,7 @@ const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); -const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); +const { cliux: cliUX } = require('@contentstack/cli-utilities'); const { default: config } = require('../../lib/config'); const modules = config.modules; diff --git a/packages/contentstack-export/test/integration/webhooks.test.js b/packages/contentstack-export/test/integration/webhooks.test.js index f1705a8af9..14fc5ccac3 100644 --- a/packages/contentstack-export/test/integration/webhooks.test.js +++ b/packages/contentstack-export/test/integration/webhooks.test.js @@ -17,10 +17,6 @@ module.exports = (region) => { : path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`); const webhooksBasePath = path.join(exportBasePath, modules.webhooks.dirName); const webhooksJson = path.join(webhooksBasePath, modules.webhooks.fileName); - const messageFilePath = path.join(__dirname, '..', '..', 'messages/index.json'); - - messageHandler.init({ messageFilePath }); - const { promptMessageList } = require(messageFilePath); describe('ContentStack-Export webhooks', () => { describe('cm:stacks:export webhooks [auth-token]', () => { diff --git a/packages/contentstack-export/test/integration/workflows.test.js b/packages/contentstack-export/test/integration/workflows.test.js index 4fc624915c..f9e40564e3 100644 --- a/packages/contentstack-export/test/integration/workflows.test.js +++ b/packages/contentstack-export/test/integration/workflows.test.js @@ -1,10 +1,9 @@ -let defaultConfig = require('../../src/config/default'); const fs = require('fs'); const path = require('path'); const { test } = require('@oclif/test'); const { cliux: cliUX, messageHandler } = require('@contentstack/cli-utilities'); -const { default: config } = require('../../src/config'); +const { default: config } = require('../../lib/config'); const modules = config.modules; const { getStackDetailsByRegion, getWorkflowsCount, cleanUp, checkCounts } = require('./utils/helper'); const { EXPORT_PATH, DEFAULT_TIMEOUT } = require('./config.json'); @@ -18,20 +17,17 @@ module.exports = (region) => { : path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`); const workflowsBasePath = path.join(exportBasePath, modules.workflows.dirName); const workflowsJson = path.join(workflowsBasePath, modules.workflows.fileName); - const messageFilePath = path.join(__dirname, '..', '..', 'messages/index.json'); - - messageHandler.init({ messageFilePath }); - const { promptMessageList } = require(messageFilePath); describe('ContentStack-Export workflows', () => { describe('cm:stacks:export workfows [auth-token]', () => { test .timeout(DEFAULT_TIMEOUT || 600000) // NOTE setting default timeout as 10 minutes - .stub(cliUX, 'prompt', async (name) => { + .stub(cliUX, 'inquire', async (input) => { + const { name } = input; switch (name) { - case promptMessageList.promptSourceStack: + case 'apiKey': return stackDetails[stack].STACK_API_KEY; - case promptMessageList.promptPathStoredData: + case 'dir': return `${EXPORT_PATH}_${stack}`; } }) @@ -85,9 +81,9 @@ module.exports = (region) => { afterEach(async () => { await cleanUp(path.join(__dirname, '..', '..', `${EXPORT_PATH}_${stack}`)); - defaultConfig.management_token = undefined; - defaultConfig.branch = undefined; - defaultConfig.branches = []; + config.management_token = undefined; + config.branch = undefined; + config.branches = []; }); } }; diff --git a/packages/contentstack-import/README.md b/packages/contentstack-import/README.md index 187e6276d4..b88d1ff985 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.7.1 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-import/1.8.0 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-import/example_config/auth_config.json b/packages/contentstack-import/example_config/auth_config.json index 8a595ff0d3..fb329ee1f2 100644 --- a/packages/contentstack-import/example_config/auth_config.json +++ b/packages/contentstack-import/example_config/auth_config.json @@ -12,5 +12,6 @@ "fetchConcurrency": 5, "writeConcurrency": 5, "securedAssets":false, - "developerHubBaseUrl": "" + "developerHubBaseUrl": "", + "createBackupDir": "./temp" } diff --git a/packages/contentstack-import/package.json b/packages/contentstack-import/package.json index 9b4b6bde71..74317fabf4 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.7.1", + "version": "1.8.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/management": "~1.10.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", + "@contentstack/management": "~1.10.0", "@oclif/config": "^1.18.3", "@oclif/core": "^2.9.3", "big-json": "^3.2.0", @@ -96,4 +96,4 @@ } }, "repository": "https://github.com/contentstack/cli" -} +} \ No newline at end of file diff --git a/packages/contentstack-import/src/config/index.ts b/packages/contentstack-import/src/config/index.ts index 6fa1596759..63fb403e5f 100644 --- a/packages/contentstack-import/src/config/index.ts +++ b/packages/contentstack-import/src/config/index.ts @@ -377,6 +377,8 @@ const config: DefaultConfig = { 'content-types', 'webhooks', 'custom-roles', + 'workflows', + 'entries', ], rateLimit: 5, preserveStackVersion: false, diff --git a/packages/contentstack-import/src/import/module-importer.ts b/packages/contentstack-import/src/import/module-importer.ts old mode 100644 new mode 100755 index 7fa13bc78b..1ce2c624ca --- a/packages/contentstack-import/src/import/module-importer.ts +++ b/packages/contentstack-import/src/import/module-importer.ts @@ -1,4 +1,4 @@ -import { ContentstackClient } from '@contentstack/cli-utilities'; +import { ContentstackClient, HttpClient } from '@contentstack/cli-utilities'; import { ImportConfig, Modules } from '../types'; import { backupHandler, log, validateBranch, masterLocalDetails, sanitizeStack } from '../utils'; import startModuleImport from './modules'; @@ -22,6 +22,25 @@ class ModuleImporter { if (this.importConfig.branchName) { await validateBranch(this.stackAPIClient, this.importConfig, this.importConfig.branchName); } + + // Temporarily adding this api call to verify management token has read and write permissions + // TODO: CS-40354 - CLI | import rewrite | Migrate HTTP call to SDK call once fix is ready from SDK side + + const httpClient = new HttpClient({ + headers: { api_key: this.importConfig.apiKey, authorization: this.importConfig.management_token }, + }); + + const { data } = await httpClient.post('https://api.contentstack.io/v3/locales', { + locale: { + name: 'English', + code: 'en-us', + }, + }); + + if (data.error_code === 161) { + throw new Error(data.error_message); + } + if (!this.importConfig.master_locale) { let masterLocalResponse = await masterLocalDetails(this.stackAPIClient); this.importConfig['master_locale'] = { code: masterLocalResponse.code }; diff --git a/packages/contentstack-import/src/import/modules-js/custom-roles.js b/packages/contentstack-import/src/import/modules-js/custom-roles.js index 9d42aaa3fd..c5a1c768e9 100644 --- a/packages/contentstack-import/src/import/modules-js/custom-roles.js +++ b/packages/contentstack-import/src/import/modules-js/custom-roles.js @@ -97,7 +97,7 @@ module.exports = class ImportCustomRoles { self.fails.push(customRole); if (((error && error.errors && error.errors.name) || '').includes('is not a unique.')) { - log(self.config, `custom-role ${customRole.name} already exists`, 'info'); + log(self.config, `custom-role '${customRole.name}' already exists`, 'info'); } else { if (!(error && error.errors && error.errors.name)) { log(self.config, `custom-role: ${customRole.name} already exists`, 'error'); diff --git a/packages/contentstack-import/src/import/modules-js/entries.js b/packages/contentstack-import/src/import/modules-js/entries.js index 0d1e2c22eb..de2f8a7361 100755 --- a/packages/contentstack-import/src/import/modules-js/entries.js +++ b/packages/contentstack-import/src/import/modules-js/entries.js @@ -396,7 +396,7 @@ module.exports = class ImportEntries { } }) .catch((error) => { - if (error.hasOwnProperty('error_code') && error.error_code === 119) { + if (error.hasOwnProperty('errorCode') && error.errorCode === 119) { if (error.errors.title) { log( this.config, @@ -404,7 +404,13 @@ module.exports = class ImportEntries { 'error', ); } else { - log(this.config, `Failed to create an entry ${eUid} ${formatError(error)}`, 'error'); + log( + this.config, + `Failed to create an entry '${eUid}' ${formatError(error)} Title of the failed entry: '${ + entries[eUid].title + }'`, + 'error', + ); } self.createdEntriesWOUid.push({ content_type: ctUid, @@ -417,7 +423,13 @@ module.exports = class ImportEntries { } // TODO: if status code: 422, check the reason // 429 for rate limit - log(this.config, `Failed to create an entry ${eUid} ${formatError(error)}`, 'error'); + log( + this.config, + `Failed to create an entry '${eUid}' ${formatError(error)}. Title of the failed entry: '${ + entries[eUid].title + }'`, + 'error', + ); self.fails.push({ content_type: ctUid, locale: lang, @@ -486,7 +498,18 @@ module.exports = class ImportEntries { return resolve(); }) .catch((error) => { - addlogs(this.config, chalk.red("Failed to create entries in '" + lang + "' language"), 'error'); + let title = JSON.parse(error?.request?.data ||"{}").entry?.title + addlogs( + this.config, + chalk.red( + "Failed to create entries in '" + + lang + + "' language. " + + 'Title of the failed entry: ' + + `'${title||""}'`, + ), + 'error', + ); return reject(error); }); }); @@ -584,7 +607,7 @@ module.exports = class ImportEntries { } catch (error) { addlogs( this.config, - `Failed to update the entry ${uid} references while reposting ${formatError(error)}`, + `Failed to update the entry '${uid}' references while reposting ${formatError(error)}`, 'error', ); } @@ -681,7 +704,7 @@ module.exports = class ImportEntries { }) .catch((error) => { // error while updating entries with references - addlogs(this.config, `Failed re-post entries of content type ${ctUid} locale ${lang}`, 'error'); + addlogs(this.config, `Failed re-post entries of content type '${ctUid}' locale '${lang}'`, 'error'); addlogs(this.config, formatError(error), 'error'); // throw error; }); @@ -773,7 +796,7 @@ module.exports = class ImportEntries { }) .catch((_error) => { addlogs(this.config, formatError(_error), 'error'); - reject(`Failed suppress content type ${schema.uid} reference fields`); + reject(`Failed suppress content type '${schema.uid}' reference fields`); }); // update 5 content types at a time }, diff --git a/packages/contentstack-import/src/import/modules-js/environments.js b/packages/contentstack-import/src/import/modules-js/environments.js index b379f1e1dc..d56650fe4e 100755 --- a/packages/contentstack-import/src/import/modules-js/environments.js +++ b/packages/contentstack-import/src/import/modules-js/environments.js @@ -82,11 +82,7 @@ module.exports = class ImportEnvironments { }); } else { // the environment has already been created - log( - config, - `The environment ${env.name} already exists. Skipping it to avoid duplicates!`, - 'success', - ); + log(config, `The environment '${env.name}' already exists. Skipping it to avoid duplicates!`, 'success'); } }, { concurrency: self.fetchConcurrency }, diff --git a/packages/contentstack-import/src/import/modules-js/extensions.js b/packages/contentstack-import/src/import/modules-js/extensions.js index aa2081003d..42145aec2d 100644 --- a/packages/contentstack-import/src/import/modules-js/extensions.js +++ b/packages/contentstack-import/src/import/modules-js/extensions.js @@ -67,7 +67,7 @@ module.exports = class ImportExtensions { if (error.errors.title) { log(self.config, `Extension '${ext.title}' already exists`, 'error'); } else { - log(self.config, "Extension: '" + ext.title + "' failed to be import" + formatError(error), 'error'); + log(self.config, "Extension: '" + ext.title + "' failed to import" + formatError(error), 'error'); } }); } diff --git a/packages/contentstack-import/src/import/modules-js/global-fields.js b/packages/contentstack-import/src/import/modules-js/global-fields.js index d96e3598f8..368b57803a 100644 --- a/packages/contentstack-import/src/import/modules-js/global-fields.js +++ b/packages/contentstack-import/src/import/modules-js/global-fields.js @@ -83,12 +83,13 @@ module.exports = class ImportGlobalFields { log(self.config, chalk.green('Global field ' + global_field_uid + ' created successfully'), 'success'); }) .catch(function (err) { + console.log let error = JSON.parse(err.message); if (error.errors.title) { // eslint-disable-next-line no-undef log(self.config, `Globalfield '${snip.uid} already exists'`, 'error'); } else { - log(self.config, chalk.red(`Globalfield failed to import ${formatError(error)}`), 'error'); + log(self.config, chalk.red(`Globalfield '${snip.title}' failed to import. ${formatError(error)}`), 'error'); } self.fails.push(snip); @@ -113,10 +114,7 @@ module.exports = class ImportGlobalFields { return resolve(); }) .catch(function (err) { - // error while importing globalfields let error = JSON.parse(err); - // error while importing globalfields - log(self.config, err, 'error'); fileHelper.writeFileSync(globalfieldsFailsPath, self.fails); log(self.config, `Globalfields import failed. ${formatError(err)}`, 'error'); return reject(error); diff --git a/packages/contentstack-import/src/import/modules-js/marketplace-apps.js b/packages/contentstack-import/src/import/modules-js/marketplace-apps.js index 068741449a..0537576c01 100644 --- a/packages/contentstack-import/src/import/modules-js/marketplace-apps.js +++ b/packages/contentstack-import/src/import/modules-js/marketplace-apps.js @@ -27,7 +27,7 @@ const { getDeveloperHubUrl, getAllStackSpecificApps } = require('../../utils/mar module.exports = class ImportMarketplaceApps { client; httpClient; - appOrginalName; + appOriginalName; appUidMapping = {}; appNameMapping = {}; marketplaceApps = []; @@ -90,7 +90,7 @@ module.exports = class ImportMarketplaceApps { console.log(error); }); - if (tempStackData && tempStackData.org_uid) { + if (tempStackData?.org_uid) { this.config.org_uid = tempStackData.org_uid; } } @@ -180,14 +180,15 @@ module.exports = class ImportMarketplaceApps { async generateUidMapper() { const listOfNewMeta = []; const listOfOldMeta = []; - const extensionUidMapp = {}; - const allInstalledApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config); + const extensionUidMap = {}; + const allInstalledApps = + (await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config)) || []; for (const app of this.marketplaceApps) { - listOfOldMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat()); + listOfOldMeta.push(..._.map(app?.ui_location?.locations, 'meta').flat()); } for (const app of allInstalledApps) { - listOfNewMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat()); + listOfNewMeta.push(..._.map(app?.ui_location?.locations, 'meta').flat()); } for (const { extension_uid, name, path, uid, data_type } of _.filter(listOfOldMeta, 'name')) { const meta = @@ -196,11 +197,11 @@ module.exports = class ImportMarketplaceApps { _.find(listOfNewMeta, { name, uid, data_type }); if (meta) { - extensionUidMapp[extension_uid] = meta.extension_uid; + extensionUidMap[extension_uid] = meta.extension_uid; } } - return extensionUidMapp; + return extensionUidMap; } /** @@ -222,7 +223,7 @@ module.exports = class ImportMarketplaceApps { for (let app of privateApps) { // NOTE keys can be passed to install new app in the developer hub app.manifest = _.pick(app.manifest, ['uid', 'name', 'description', 'icon', 'target_type', 'webhook', 'oauth']); - this.appOrginalName = app.manifest.name; + this.appOriginalName = app.manifest.name; await this.createPrivateApps({ oauth: app.oauth, webhook: app.webhook, @@ -231,7 +232,7 @@ module.exports = class ImportMarketplaceApps { }); } - this.appOrginalName = undefined; + this.appOriginalName = undefined; } async getConfirmationToCreateApps(privateApps) { @@ -268,7 +269,7 @@ module.exports = class ImportMarketplaceApps { } async createPrivateApps(app, uidCleaned = false, appSuffix = 1) { - let locations = app.ui_location && app.ui_location.locations; + let locations = app?.ui_location?.locations; if (!uidCleaned && !_.isEmpty(locations)) { app.ui_location.locations = this.updateManifestUILocations(locations, 'uid'); @@ -316,7 +317,7 @@ module.exports = class ImportMarketplaceApps { // NOTE new app installation log(this.config, `${response.name} app created successfully.!`, 'success'); this.appUidMapping[app.uid] = response.uid; - this.appNameMapping[this.appOrginalName] = response.name; + this.appNameMapping[this.appOriginalName] = response.name; } } @@ -352,8 +353,8 @@ module.exports = class ImportMarketplaceApps { if (meta.name) { const name = `${_.first(_.split(meta.name, '◈'))}◈${appSuffix}`; - if (!this.appNameMapping[this.appOrginalName]) { - this.appNameMapping[this.appOrginalName] = name; + if (!this.appNameMapping[this.appOriginalName]) { + this.appNameMapping[this.appOriginalName] = name; } meta.name = name; @@ -378,8 +379,10 @@ module.exports = class ImportMarketplaceApps { /** * @method installApps - * @param {Object} options - * @returns {Void} + * + * @param {Record} app + * @param {Record[]} installedApps + * @returns {Promise} */ async installApps(app, installedApps) { let updateParam; diff --git a/packages/contentstack-import/src/import/modules-js/workflows.js b/packages/contentstack-import/src/import/modules-js/workflows.js index 610a13a355..89a6e0c38b 100644 --- a/packages/contentstack-import/src/import/modules-js/workflows.js +++ b/packages/contentstack-import/src/import/modules-js/workflows.js @@ -117,7 +117,7 @@ module.exports = class importWorkflows { .catch(function (error) { self.fails.push(workflow); if (error.errors.name) { - log(self.config, `workflow ${workflow.name} already exist`, 'error'); + log(self.config, `workflow '${workflow.name}' already exist`, 'error'); } else if (error.errors['workflow_stages.0.users']) { log( self.config, @@ -125,7 +125,7 @@ module.exports = class importWorkflows { 'error', ); } else { - log(self.config, `workflow ${workflow.name} failed.`, 'error'); + log(self.config, `Workflow '${workflow.name}' failed.`, 'error'); } }); } else { diff --git a/packages/contentstack-import/src/import/modules/base-class.ts b/packages/contentstack-import/src/import/modules/base-class.ts index 1ce3b2eb50..f2efc86e3c 100644 --- a/packages/contentstack-import/src/import/modules/base-class.ts +++ b/packages/contentstack-import/src/import/modules/base-class.ts @@ -43,7 +43,11 @@ export type ApiModuleType = | 'update-labels' | 'create-webhooks' | 'create-workflows' - | 'create-custom-role'; + | 'create-custom-role' + | 'create-entries' + | 'update-entries' + | 'publish-entries' + | 'delete-entries'; export type ApiOptions = { uid?: string; @@ -231,7 +235,7 @@ export default abstract class BaseClass { apiOptions = apiOptions.serializeData(apiOptions); } - const { uid, entity, reject, resolve, apiData, additionalInfo, includeParamOnCompletion } = apiOptions; + const { uid, entity, reject, resolve, apiData, additionalInfo = {}, includeParamOnCompletion } = apiOptions; const onSuccess = (response: any) => resolve({ @@ -295,15 +299,18 @@ export default abstract class BaseClass { case 'create-cts': return this.stack.contentType().create(apiData).then(onSuccess).catch(onReject); case 'update-cts': + if (additionalInfo.skip) { + return Promise.resolve(onSuccess(apiData)); + } return apiData.update().then(onSuccess).catch(onReject); case 'update-gfs': return apiData.update().then(onSuccess).catch(onReject); case 'create-environments': - // return this.stack - // .environment() - // .create({ environment: omit(apiData, ['uid']) as EnvironmentData }) - // .then(onSuccess) - // .catch(onReject); + // return this.stack + // .environment() + // .create({ environment: omit(apiData, ['uid']) as EnvironmentData }) + // .then(onSuccess) + // .catch(onReject); case 'create-labels': return this.stack .label() @@ -337,7 +344,32 @@ export default abstract class BaseClass { .create({ role: apiData as RoleData }) .then(onSuccess) .catch(onReject); - + case 'create-entries': + return this.stack + .contentType(additionalInfo.cTUid) + .entry() + .create({ entry: apiData }, { locale: additionalInfo.locale }) + .then(onSuccess) + .catch(onReject); + case 'update-entries': + return apiData.update({ locale: additionalInfo.locale }).then(onSuccess).catch(onReject); + case 'publish-entries': + if (additionalInfo.skip) { + return Promise.resolve(onSuccess(apiData)); + } + return this.stack + .contentType(additionalInfo.cTUid) + .entry(additionalInfo.entryUid) + .publish({ publishDetails: apiData, locale: additionalInfo.locale }) + .then(onSuccess) + .catch(onReject); + case 'delete-entries': + return this.stack + .contentType(apiData.cTUid) + .entry(apiData.entryUid) + .delete({ locale: this.importConfig?.master_locale?.code }) + .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 2bc64dff6c..850c638376 100644 --- a/packages/contentstack-import/src/import/modules/content-types.ts +++ b/packages/contentstack-import/src/import/modules/content-types.ts @@ -201,7 +201,7 @@ export default class ContentTypesImport extends BaseClass { log(this.importConfig, `Updated the global field ${uid} with content type references`, 'info'); }; const onReject = ({ error, apiData: { uid } = undefined }: any) => { - log(this.importConfig, `failed to update the global field ${uid} ${formatError(error)}`, 'error'); + log(this.importConfig, `failed to update the global field '${uid}' ${formatError(error)}`, 'error'); }; return await this.makeConcurrentCall({ processName: 'Update pending global fields', diff --git a/packages/contentstack-import/src/import/modules/entries.ts b/packages/contentstack-import/src/import/modules/entries.ts new file mode 100644 index 0000000000..cf84edfe08 --- /dev/null +++ b/packages/contentstack-import/src/import/modules/entries.ts @@ -0,0 +1,674 @@ +/* eslint-disable no-prototype-builtins */ +/*! + * Contentstack Import + * Copyright (c) 2019 Contentstack LLC + * MIT Licensed + */ + +import * as path from 'path'; +import { isEmpty, values, cloneDeep, find, indexOf, forEach } from 'lodash'; +import { ContentType, FsUtility } from '@contentstack/cli-utilities'; +import { + fsUtil, + log, + formatError, + lookupExtension, + suppressSchemaReference, + removeUidsFromJsonRteFields, + removeEntryRefsFromJSONRTE, + restoreJsonRteEntryRefs, + lookupEntries, + lookupAssets, + fileHelper, +} from '../../utils'; +import { ModuleClassParams } from '../../types'; +import BaseClass, { ApiOptions } from './base-class'; + +export default class EntriesImport extends BaseClass { + private assetUidMapperPath: string; + private assetUidMapper: Record; + private assetUrlMapper: Record; + private assetUrlMapperPath: string; + private entriesMapperPath: string; + private envPath: string; + private entriesUIDMapperPath: string; + private uniqueUidMapperPath: string; + private modifiedCTsPath: string; + private marketplaceAppMapperPath: string; + private entriesConfig: Record; + private cTsPath: string; + private localesPath: string; + private importConcurrency: number; + private entriesPath: string; + private cTs: Record[]; + private modifiedCTs: Record[]; + private refCTs: string[]; + private jsonRteCTs: Record; + private jsonRteCTsWithRef: Record; + private jsonRteEntries: Record; + private installedExtensions: Record[]; + private createdEntries: Record[]; + private failedEntries: Record[]; + private locales: Record[]; + private entriesUidMapper: Record; + private envs: Record; + private autoCreatedEntries: Record[]; + + constructor({ importConfig, stackAPIClient }: ModuleClassParams) { + super({ importConfig, stackAPIClient }); + this.assetUidMapperPath = path.resolve(importConfig.data, 'mapper', 'assets', 'uid-mapping.json'); + this.assetUrlMapperPath = path.resolve(importConfig.data, 'mapper', 'assets', 'url-mapping.json'); + this.entriesMapperPath = path.resolve(importConfig.data, 'mapper', 'entries'); + this.envPath = path.resolve(importConfig.data, 'environments', 'environments.json'); + this.entriesUIDMapperPath = path.join(this.entriesMapperPath, 'uid-mapping.json'); + 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.entriesConfig = importConfig.modules.entries; + this.entriesPath = path.resolve(importConfig.data, this.entriesConfig.dirName); + this.cTsPath = path.resolve(importConfig.data, importConfig.modules['content-types'].dirName); + this.localesPath = path.resolve( + importConfig.data, + importConfig.modules.locales.dirName, + importConfig.modules.locales.fileName, + ); + this.importConcurrency = this.entriesConfig.importConcurrency || importConfig.importConcurrency; + this.entriesUidMapper = {}; + this.modifiedCTs = []; + this.refCTs = []; + this.jsonRteCTs = []; + this.jsonRteCTsWithRef = []; + this.envs = {}; + this.autoCreatedEntries = []; + } + + async start(): Promise { + try { + this.cTs = fsUtil.readFile(path.join(this.cTsPath, 'schema.json')) as Record[]; + if (!this.cTs || isEmpty(this.cTs)) { + log(this.importConfig, 'No content type found', 'info'); + return; + } + this.installedExtensions = ( + ((await fsUtil.readFile(this.marketplaceAppMapperPath)) as any) || { extension_uid: {} } + ).extension_uid; + + this.assetUidMapper = (fsUtil.readFile(this.assetUidMapperPath) as Record) || {}; + this.assetUrlMapper = (fsUtil.readFile(this.assetUrlMapperPath) as Record) || {}; + + fsUtil.makeDirectory(this.entriesMapperPath); + await this.disableMandatoryCTReferences(); + this.locales = values(fsUtil.readFile(this.localesPath) as Record[]); + this.locales.unshift(this.importConfig.master_locale); // adds master locale to the list + + //Create Entries + const entryRequestOptions = this.populateEntryCreatePayload(); + for (let entryRequestOption of entryRequestOptions) { + await this.createEntries(entryRequestOption); + } + await fileHelper.writeLargeFile(path.join(this.entriesMapperPath, 'uid-mapping.json'), this.entriesUidMapper); // TBD: manages mapper in one file, should find an alternative + fsUtil.writeFile(path.join(this.entriesMapperPath, 'failed-entries.json'), this.failedEntries); + + // Update entries with references + const entryUpdateRequestOptions = this.populateEntryUpdatePayload(); + for (let entryUpdateRequestOption of entryUpdateRequestOptions) { + await this.updateEntriesWithReferences(entryUpdateRequestOption).catch((error) => { + log( + this.importConfig, + `Error while updating entries references of ${entryUpdateRequestOption.cTUid} in locale ${entryUpdateRequestOption.locale}`, + 'error', + ); + log(this.importConfig, formatError(error), 'error'); + }); + } + fsUtil.writeFile(path.join(this.entriesMapperPath, 'failed-entries.json'), this.failedEntries); + + log(this.importConfig, 'Restoring content type changes', 'info'); + await this.enableMandatoryCTReferences().catch((error) => { + log(this.importConfig, `Error while updating content type references ${formatError(error)}`, 'error'); + }); + + if (this.autoCreatedEntries.length > 0) { + log(this.importConfig, 'Removing entries from master language which got created by default', 'info'); + await this.removeAutoCreatedEntries().catch((error) => { + log( + this.importConfig, + `Error while removing auto created entries in master locale ${formatError(error)}`, + 'error', + ); + }); + } + // Update field rule of content types which are got removed earlier + log(this.importConfig, 'Updating the field rules of content type', 'info'); + await this.updateFieldRules().catch((error) => { + log(this.importConfig, `Error while updating field rules of content type ${formatError(error)}`, 'error'); + }); + log(this.importConfig, 'Entries imported successfully', 'success'); + + // Publishing entries + if (this.importConfig.entriesPublish) { + log(this.importConfig, 'Publishing entries', 'info'); + this.envs = fileHelper.readFileSync(this.envPath); + for (let entryRequestOption of entryRequestOptions) { + await this.publishEntries(entryRequestOption).catch((error) => { + log( + this.importConfig, + `Error in publishing entries of ${entryRequestOption.cTUid} in locale ${ + entryRequestOption.locale + } ${formatError(error)}`, + 'error', + ); + }); + } + log(this.importConfig, 'All the entries have been published successfully', 'success'); + } + } catch (error) { + log(this.importConfig, formatError(error), 'error'); + throw new Error('Error while importing entries'); + } + } + + async disableMandatoryCTReferences() { + const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { + log(this.importConfig, `${uid} content type references removed temporarily`, 'success'); + }; + const onReject = ({ error, apiData: { uid } }: any) => { + log(this.importConfig, formatError(error), 'error'); + throw new Error(`${uid} content type references removal failed`); + }; + return await this.makeConcurrentCall({ + processName: 'Update content types (removing mandatory references temporarily)', + apiContent: this.cTs, + apiParams: { + serializeData: this.serializeUpdateCTs.bind(this), + reject: onReject.bind(this), + resolve: onSuccess.bind(this), + entity: 'update-cts', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConcurrency, + }).then(() => { + fsUtil.writeFile(this.modifiedCTsPath, this.modifiedCTs); + }); + } + + /** + * @method serializeUpdateCTs + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeUpdateCTs(apiOptions: ApiOptions): ApiOptions { + const { apiData: contentType } = apiOptions; + if (contentType.field_rules) { + delete contentType.field_rules; + } + const flag = { + suppressed: false, + references: false, + jsonRte: false, + jsonRteEmbeddedEntries: false, + }; + suppressSchemaReference(contentType.schema, flag); + // Check if suppress modified flag + if (flag.suppressed) { + this.modifiedCTs.push(find(this.cTs, { uid: contentType.uid })); + } else { + // Note: Skips the content type from update if no reference found + apiOptions.additionalInfo = { skip: true }; + return apiOptions; + } + + if (flag.references) { + this.refCTs.push(contentType.uid); + } + + if (flag.jsonRte) { + this.jsonRteCTs.push(contentType.uid); + if (flag.jsonRteEmbeddedEntries) { + this.jsonRteCTsWithRef.push(contentType.uid); + if (this.refCTs.indexOf(contentType.uid) === -1) { + this.refCTs.push(contentType.uid); + } + } + } + lookupExtension( + this.importConfig, + contentType.schema, + this.importConfig.preserveStackVersion, + this.installedExtensions, + ); + const contentTypePayload = this.stack.contentType(contentType.uid); + Object.assign(contentTypePayload, cloneDeep(contentType)); + apiOptions.apiData = contentTypePayload; + return apiOptions; + } + + populateEntryCreatePayload(): { cTUid: string; locale: string }[] { + const requestOptions: { cTUid: string; locale: string }[] = []; + for (let locale of this.locales) { + for (let contentType of this.cTs) { + requestOptions.push({ + cTUid: contentType.uid, + locale: locale.code, + }); + } + } + return requestOptions; + } + + async createEntries({ cTUid, locale }: { cTUid: string; locale: string }): Promise { + const processName = 'Create Entries'; + const indexFileName = 'index.json'; + const basePath = path.join(this.entriesPath, cTUid, locale); + const fs = new FsUtility({ basePath, indexFileName }); + const indexer = fs.indexFileContent; + const indexerCount = values(indexer).length; + if (indexerCount === 0) { + return Promise.resolve(); + } + log(this.importConfig, `Starting to create entries for ${cTUid} in locale ${locale}`, 'info'); + const isMasterLocale = locale === this.importConfig?.master_locale?.code; + // Write created entries + const entriesCreateFileHelper = new FsUtility({ + moduleName: 'created-entries', + indexFileName: 'index.json', + basePath: path.join(this.entriesMapperPath, cTUid, locale), + chunkFileSize: this.entriesConfig.chunkFileSize, + keepMetadata: false, + omitKeys: this.entriesConfig.invalidKeys, + }); + const contentType = find(this.cTs, { uid: cTUid }); + + const onSuccess = ({ response, apiData: entry, additionalInfo: { entryFileName } }: any) => { + log(this.importConfig, `Created entry: '${entry.title}' of content type ${cTUid} in locale ${locale}`, 'info'); + this.entriesUidMapper[entry.uid] = response.uid; + entry.sourceEntryFilePath = path.join(basePath, entryFileName); // stores source file path temporarily + entry.entryOldUid = entry.uid; // stores old uid temporarily + if (!isMasterLocale) { + this.autoCreatedEntries.push({ cTUid, locale, entryUid: response.uid }); + } + entriesCreateFileHelper.writeIntoFile({ [response.uid]: entry } as any, { mapKeyVal: true }); + }; + const onReject = ({ error, apiData: { uid, title } }: any) => { + log(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to create`, 'error'); + log(this.importConfig, formatError(error), 'error'); + this.failedEntries.push({ content_type: cTUid, locale, entry: { uid, title } }); + }; + + for (const index in indexer) { + const chunk = await fs.readChunkFiles.next().catch((error) => { + log(this.importConfig, formatError(error), 'error'); + }); + + if (chunk) { + let apiContent = values(chunk as Record[]); + await this.makeConcurrentCall({ + apiContent, + processName, + indexerCount, + currentIndexer: +index, + apiParams: { + reject: onReject, + resolve: onSuccess, + entity: 'create-entries', + includeParamOnCompletion: true, + serializeData: this.serializeEntries.bind(this), + additionalInfo: { contentType, locale, cTUid, entryFileName: indexer[index] }, + }, + concurrencyLimit: this.importConcurrency, + }).then(() => { + entriesCreateFileHelper?.completeFile(true); + log(this.importConfig, `Created entries for content type ${cTUid} in locale ${locale}`, 'success'); + }); + } + } + } + + /** + * @method serializeEntries + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeEntries(apiOptions: ApiOptions): ApiOptions { + let { + apiData: entry, + additionalInfo: { cTUid, locale, contentType }, + } = apiOptions; + + if (this.jsonRteCTs.indexOf(cTUid) > -1) { + entry = removeUidsFromJsonRteFields(entry, contentType.schema); + } + // remove entry references from json-rte fields + if (this.jsonRteCTsWithRef.indexOf(cTUid) > -1) { + entry = removeEntryRefsFromJSONRTE(entry, contentType.schema); + } + // will replace all old asset uid/urls with new ones + entry = lookupAssets( + { + content_type: contentType, + entry: entry, + }, + this.assetUidMapper, + this.assetUrlMapper, + path.join(this.entriesPath, cTUid), + this.installedExtensions, + ); + delete entry.publish_details; + apiOptions.apiData = entry; + return apiOptions; + } + + populateEntryUpdatePayload(): { cTUid: string; locale: string }[] { + const requestOptions: { cTUid: string; locale: string }[] = []; + for (let locale of this.locales) { + for (let cTUid of this.refCTs) { + requestOptions.push({ + cTUid, + locale: locale.code, + }); + } + } + return requestOptions; + } + + async updateEntriesWithReferences({ cTUid, locale }: { cTUid: string; locale: string }): Promise { + const processName = 'Update Entries'; + const indexFileName = 'index.json'; + const basePath = path.join(this.entriesMapperPath, cTUid, locale); + const fs = new FsUtility({ basePath, indexFileName }); + const indexer = fs.indexFileContent; + const indexerCount = values(indexer).length; + if (indexerCount === 0) { + return Promise.resolve(); + } + log(this.importConfig, `Starting to update entries with references for ${cTUid} in locale ${locale}`, 'info'); + + const contentType = find(this.cTs, { uid: cTUid }); + + const onSuccess = ({ response, apiData: { uid, url, title } }: any) => { + log(this.importConfig, `Updated entry: '${title}' of content type ${cTUid} in locale ${locale}`, 'info'); + }; + const onReject = ({ error, apiData: { uid, title } }: any) => { + log(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to update`, 'error'); + log(this.importConfig, formatError(error), 'error'); + this.failedEntries.push({ content_type: cTUid, locale, entry: { uid: this.entriesUidMapper[uid], title } }); + }; + + for (const index in indexer) { + const chunk = await fs.readChunkFiles.next().catch((error) => { + log(this.importConfig, formatError(error), 'error'); + }); + + if (chunk) { + let apiContent = values(chunk as Record[]); + await this.makeConcurrentCall({ + apiContent, + processName, + indexerCount, + currentIndexer: +index, + apiParams: { + reject: onReject, + resolve: onSuccess, + entity: 'update-entries', + includeParamOnCompletion: true, + serializeData: this.serializeUpdateEntries.bind(this), + additionalInfo: { contentType, locale, cTUid }, + }, + concurrencyLimit: this.importConcurrency, + }).then(() => { + log(this.importConfig, `Updated entries for content type ${cTUid} in locale ${locale}`, 'success'); + }); + } + } + } + + /** + * @method serializeUpdateEntries + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeUpdateEntries(apiOptions: ApiOptions): ApiOptions { + let { + apiData: entry, + additionalInfo: { cTUid, locale, contentType }, + } = apiOptions; + + const sourceEntryFilePath = entry.sourceEntryFilePath; + const sourceEntry = ((fsUtil.readFile(sourceEntryFilePath) || {}) as Record)[entry.entryOldUid]; + // Removing temp values + delete entry.sourceEntryFilePath; + delete entry.entryOldUid; + if (this.jsonRteCTs.indexOf(cTUid) > -1) { + // the entries stored in eSuccessFilePath, have the same uids as the entries from source data + entry = restoreJsonRteEntryRefs(entry, sourceEntry, contentType.schema, { + mappedAssetUids: this.assetUidMapper, + mappedAssetUrls: this.assetUrlMapper, + }); + } + + entry = lookupEntries( + { + content_type: contentType, + entry, + }, + this.entriesUidMapper, + path.join(this.entriesMapperPath, cTUid, locale), + ); + + const entryResponse = this.stack.contentType(contentType.uid).entry(this.entriesUidMapper[entry.uid]); + Object.assign(entryResponse, cloneDeep(entry)); + delete entryResponse.publish_details; + apiOptions.apiData = entryResponse; + return apiOptions; + } + + async enableMandatoryCTReferences(): Promise { + const onSuccess = ({ response: contentType, apiData: { uid } }: any) => { + log(this.importConfig, `${uid} content type references updated`, 'success'); + }; + const onReject = ({ error, apiData: { uid } }: any) => { + log(this.importConfig, formatError(error), 'error'); + throw new Error(`Failed to update references of content type ${uid}`); + }; + return await this.makeConcurrentCall({ + processName: 'Update content type references', + apiContent: this.modifiedCTs, + apiParams: { + serializeData: this.serializeUpdateCTsWithRef.bind(this), + reject: onReject.bind(this), + resolve: onSuccess.bind(this), + entity: 'update-cts', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConcurrency, + }); + } + + /** + * @method serializeUpdateCTsWithRef + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeUpdateCTsWithRef(apiOptions: ApiOptions): ApiOptions { + const { apiData: contentType } = apiOptions; + if (contentType.field_rules) { + delete contentType.field_rules; + } + lookupExtension( + this.importConfig, + contentType.schema, + this.importConfig.preserveStackVersion, + this.installedExtensions, + ); + const contentTypePayload = this.stack.contentType(contentType.uid); + Object.assign(contentTypePayload, cloneDeep(contentType)); + apiOptions.apiData = contentTypePayload; + return apiOptions; + } + + async removeAutoCreatedEntries(): Promise { + const onSuccess = ({ response, apiData: { entryUid } }: any) => { + log(this.importConfig, `Auto created entry in master locale removed - entry uid ${entryUid} `, 'success'); + }; + const onReject = ({ error, apiData: { entryUid } }: any) => { + log( + this.importConfig, + `Failed to remove auto created entry in master locale - entry uid ${entryUid} \n ${formatError(error)}`, + 'error', + ); + }; + return await this.makeConcurrentCall({ + processName: 'Remove auto created entry in master locale', + apiContent: this.autoCreatedEntries, + apiParams: { + reject: onReject.bind(this), + resolve: onSuccess.bind(this), + entity: 'delete-entries', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConcurrency, + }); + } + + async updateFieldRules(): Promise { + let cTsWithFieldRules = fsUtil.readFile(path.join(this.cTsPath + '/field_rules_uid.json')) as Record[]; + if (!cTsWithFieldRules || cTsWithFieldRules?.length === 0) { + return; + } + for (let cTUid of cTsWithFieldRules) { + const contentType = find(this.cTs, { uid: cTUid }); + if (contentType.field_rules) { + let fieldRuleLength = contentType.field_rules.length; + for (let k = 0; k < fieldRuleLength; k++) { + let fieldRuleConditionLength = contentType.field_rules[k].conditions.length; + for (let i = 0; i < fieldRuleConditionLength; i++) { + if (contentType.field_rules[k].conditions[i].operand_field === 'reference') { + let fieldRulesValue = contentType.field_rules[k].conditions[i].value; + let fieldRulesArray = fieldRulesValue.split('.'); + let updatedValue = []; + for (const element of fieldRulesArray) { + let splittedFieldRulesValue = element; + if (this.entriesUidMapper.hasOwnProperty(splittedFieldRulesValue)) { + updatedValue.push(this.entriesUidMapper[splittedFieldRulesValue]); + } else { + updatedValue.push(element); + } + } + contentType.field_rules[k].conditions[i].value = updatedValue.join('.'); + } + } + } + const contentTypeResponse: any = await this.stack + .contentType(contentType.uid) + .fetch() + .catch((error) => { + log(this.importConfig, `failed to update the field rules of ${cTUid} ${formatError(error)}`, 'error'); + }); + if (!contentTypeResponse) { + continue; + } + contentTypeResponse.field_rules = contentType.field_rules; + await contentTypeResponse.update().catch((error: Error) => { + log(this.importConfig, `failed to update the field rules of ${cTUid} ${formatError(error)}`, 'error'); + }); + log(this.importConfig, `Updated the field rules of ${cTUid}`, 'info'); + } else { + log(this.importConfig, `No field rules found in content type ${cTUid} to update`, 'error'); + } + } + } + + async publishEntries({ cTUid, locale }: { cTUid: string; locale: string }): Promise { + const processName = 'Publish Entries'; + const indexFileName = 'index.json'; + const basePath = path.join(this.entriesPath, cTUid, locale); + const fs = new FsUtility({ basePath, indexFileName }); + const indexer = fs.indexFileContent; + const indexerCount = values(indexer).length; + const contentType = find(this.cTs, { uid: cTUid }); + + if (indexerCount === 0) { + return Promise.resolve(); + } + log(this.importConfig, `Starting publish entries for ${cTUid} in locale ${locale}`, 'info'); + + const onSuccess = ({ response, apiData: { environments }, additionalInfo: { entryUid } }: any) => { + log( + this.importConfig, + `Published entry: '${entryUid}' of content type ${cTUid} and locale ${locale} in ${environments?.join( + ',', + )} environments`, + 'info', + ); + }; + const onReject = ({ error, apiData, additionalInfo: { entryUid } }: any) => { + log( + this.importConfig, + `${entryUid} entry of content type ${cTUid} in locale ${locale} failed to publish`, + 'error', + ); + log(this.importConfig, formatError(error), 'error'); + }; + + for (const index in indexer) { + const chunk = await fs.readChunkFiles.next().catch((error) => { + log(this.importConfig, formatError(error), 'error'); + }); + + if (chunk) { + let apiContent = values(chunk as Record[]); + await this.makeConcurrentCall({ + apiContent, + processName, + indexerCount, + currentIndexer: +index, + apiParams: { + reject: onReject, + resolve: onSuccess, + entity: 'publish-entries', + includeParamOnCompletion: true, + serializeData: this.serializePublishEntries.bind(this), + additionalInfo: { contentType, locale, cTUid }, + }, + concurrencyLimit: this.importConcurrency, + }).then(() => { + log(this.importConfig, `Published entries for content type ${cTUid} in locale ${locale}`, 'success'); + }); + } + } + } + + /** + * @method serializeEntries + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializePublishEntries(apiOptions: ApiOptions): ApiOptions { + let { apiData: entry, additionalInfo } = apiOptions; + additionalInfo.entryUid = this.entriesUidMapper[entry.uid]; + const requestObject: { + environments: Array; + locales: Array; + } = { + environments: [], + locales: [], + }; + if (entry.publish_details && entry.publish_details.length > 0) { + forEach(entry.publish_details, (pubObject) => { + if ( + this.envs.hasOwnProperty(pubObject.environment) && + indexOf(requestObject.environments, this.envs[pubObject.environment].name) === -1 + ) { + requestObject.environments.push(this.envs[pubObject.environment].name); + } + if (pubObject.locale && indexOf(requestObject.locales, pubObject.locale) === -1) { + requestObject.locales.push(pubObject.locale); + } + }); + } else { + additionalInfo.skip = true; + } + apiOptions.apiData = requestObject; + return apiOptions; + } +} diff --git a/packages/contentstack-import/src/import/modules/marketplace-apps.ts b/packages/contentstack-import/src/import/modules/marketplace-apps.ts index 447f339c95..0e9ad0cb43 100644 --- a/packages/contentstack-import/src/import/modules/marketplace-apps.ts +++ b/packages/contentstack-import/src/import/modules/marketplace-apps.ts @@ -1,13 +1,14 @@ -import isEmpty from 'lodash/isEmpty'; -import find from 'lodash/find'; +import chalk from 'chalk'; import map from 'lodash/map'; +import find from 'lodash/find'; import omit from 'lodash/omit'; +import pick from 'lodash/pick'; import first from 'lodash/first'; import split from 'lodash/split'; -import toLower from 'lodash/toLower'; +import { join } from 'node:path'; import filter from 'lodash/filter'; -import pick from 'lodash/pick'; -import { join, resolve } from 'node:path'; +import isEmpty from 'lodash/isEmpty'; +import toLower from 'lodash/toLower'; import { isAuthenticated, cliux, @@ -16,11 +17,13 @@ import { HttpClientDecorator, managementSDKClient, NodeCrypto, - ContentstackClient + ContentstackClient, } from '@contentstack/cli-utilities'; -import chalk from 'chalk'; import config from '../../config'; +import BaseClass from './base-class'; +import { askEncryptionKey } from '../../utils/interactive'; +import { ModuleClassParams, MarketplaceAppsConfig } from '../../types'; import { log, formatError, @@ -37,9 +40,6 @@ import { fsUtil, fileHelper, } from '../../utils'; -import BaseClass, { ApiOptions } from './base-class'; -import { ModuleClassParams, MarketplaceAppsConfig } from '../../types'; -import { askEncryptionKey } from '../../utils/interactive'; export default class ImportMarketplaceApps extends BaseClass { private mapperDirPath: string; @@ -52,7 +52,7 @@ export default class ImportMarketplaceApps extends BaseClass { private appUidMapping: Record; private installationUidMapping: Record; private installedApps: Record[]; - private appOrginalName: string; + private appOriginalName: string; public developerHubBaseUrl: string; public sdkClient: ContentstackClient; public nodeCrypto: NodeCrypto; @@ -67,7 +67,7 @@ export default class ImportMarketplaceApps extends BaseClass { this.httpClient = new HttpClient(); this.appNameMapping = {}; this.appUidMapping = {}; - this.appOrginalName = undefined; + this.appOriginalName = undefined; this.installedApps = []; this.installationUidMapping = {}; } @@ -162,11 +162,8 @@ export default class ImportMarketplaceApps extends BaseClass { const listOfNewMeta = []; const listOfOldMeta = []; const extensionUidMap: Record = {}; - this.installedApps = await getAllStackSpecificApps( - this.developerHubBaseUrl, - this.httpClient as HttpClient, - this.importConfig, - ); + this.installedApps = + (await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient as HttpClient, this.importConfig)) || []; for (const app of this.marketplaceApps) { listOfOldMeta.push(...map(app?.ui_location?.locations, 'meta').flat()); @@ -242,7 +239,7 @@ export default class ImportMarketplaceApps extends BaseClass { for (let app of privateApps) { // NOTE keys can be passed to install new app in the developer hub app.manifest = pick(app.manifest, ['uid', 'name', 'description', 'icon', 'target_type', 'webhook', 'oauth']); - this.appOrginalName = app.manifest.name; + this.appOriginalName = app.manifest.name; const obj = { oauth: app.oauth, webhook: app.webhook, @@ -254,11 +251,11 @@ export default class ImportMarketplaceApps extends BaseClass { }); } - this.appOrginalName = undefined; + this.appOriginalName = undefined; } async createPrivateApps(app: any, uidCleaned = false, appSuffix = 1) { - let locations = app.ui_location && app.ui_location.locations; + let locations = app?.ui_location?.locations; if (!uidCleaned && !isEmpty(locations)) { app.ui_location.locations = await this.updateManifestUILocations(locations, 'uid'); @@ -290,8 +287,8 @@ export default class ImportMarketplaceApps extends BaseClass { if (meta.name) { const name = `${first(split(meta.name, '◈'))}◈${appSuffix}`; - if (!this.appNameMapping[this.appOrginalName]) { - this.appNameMapping[this.appOrginalName] = name; + if (!this.appNameMapping[this.appOriginalName]) { + this.appNameMapping[this.appOriginalName] = name; } meta.name = name; @@ -334,13 +331,16 @@ export default class ImportMarketplaceApps extends BaseClass { // NOTE new app installation log(this.importConfig, `${response.name} app created successfully.!`, 'success'); this.appUidMapping[app.uid] = response.uid; - this.appNameMapping[this.appOrginalName] = response.name; + this.appNameMapping[this.appOriginalName] = response.name; } } /** * @method installApps - * @returns {Void} + * + * @param {Record} app + * @param {Record[]} installedApps + * @returns {Promise} */ async installApps(app: any): Promise { let updateParam; diff --git a/packages/contentstack-import/src/import/modules/workflows.ts b/packages/contentstack-import/src/import/modules/workflows.ts new file mode 100644 index 0000000000..4d2cb8e419 --- /dev/null +++ b/packages/contentstack-import/src/import/modules/workflows.ts @@ -0,0 +1,249 @@ +import chalk from 'chalk'; +import isEmpty from 'lodash/isEmpty'; +import values from 'lodash/values'; +import find from 'lodash/find'; +import findIndex from 'lodash/findIndex'; +import { join, resolve } from 'node:path'; + +import config from '../../config'; +import BaseClass, { ApiOptions } from './base-class'; +import { log, formatError, fsUtil, fileHelper } from '../../utils'; +import { ModuleClassParams, WorkflowConfig } from '../../types'; + +export default class ImportWorkflows extends BaseClass { + private mapperDirPath: string; + private workflowsFolderPath: string; + private workflowUidMapperPath: string; + private createdWorkflowsPath: string; + private failedWorkflowsPath: string; + private workflowsConfig: WorkflowConfig; + private workflows: Record; + private workflowUidMapper: Record; + private createdWorkflows: Record[]; + private failedWebhooks: Record[]; + private roleNameMap: Record; + + constructor({ importConfig, stackAPIClient }: ModuleClassParams) { + super({ importConfig, stackAPIClient }); + this.workflowsConfig = config.modules.workflows; + this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'workflows'); + this.workflowsFolderPath = join(this.importConfig.backupDir, this.workflowsConfig.dirName); + this.workflowUidMapperPath = join(this.mapperDirPath, 'uid-mapping.json'); + this.createdWorkflowsPath = join(this.mapperDirPath, 'success.json'); + this.failedWorkflowsPath = join(this.mapperDirPath, 'fails.json'); + this.workflows = {}; + this.failedWebhooks = []; + this.createdWorkflows = []; + this.workflowUidMapper = {}; + this.roleNameMap = {}; + } + + /** + * @method start + * @returns {Promise} Promise + */ + async start(): Promise { + log(this.importConfig, 'Migrating workflows', 'info'); + + //Step1 check folder exists or not + if (fileHelper.fileExistsSync(this.workflowsFolderPath)) { + this.workflows = fsUtil.readFile(join(this.workflowsFolderPath, this.workflowsConfig.fileName), true) as Record; + } else { + log(this.importConfig, `No such file or directory - '${this.workflowsFolderPath}'`, 'error'); + return; + } + + //create workflows in mapper directory + await fsUtil.makeDirectory(this.mapperDirPath); + this.workflowUidMapper = fileHelper.fileExistsSync(this.workflowUidMapperPath) + ? (fsUtil.readFile(join(this.workflowUidMapperPath), true) as Record) + : {}; + + if (this.workflows === undefined || isEmpty(this.workflows)) { + log(this.importConfig, 'No Workflow Found', 'info'); + return; + } + + //fetch all roles + await this.getRoles(); + await this.importWorkflows(); + + if (this.createdWorkflows?.length) { + fsUtil.writeFile(this.createdWorkflowsPath, this.createdWorkflows); + } + + if (this.failedWebhooks?.length) { + fsUtil.writeFile(this.failedWorkflowsPath, this.failedWebhooks); + } + + log(this.importConfig, 'Workflows have been imported successfully!', 'success'); + } + + async getRoles(): Promise { + const roles = await this.stack + .role() + .fetchAll() + .then((data: any) => data) + .catch((err: any) => log(this.importConfig, `Failed to fetch roles. ${formatError(err)}`, 'error')); + + for (const role of roles?.items) { + this.roleNameMap[role.name] = role.uid; + } + } + + async importWorkflows() { + const apiContent = values(this.workflows); + + //check and create custom roles if not exists + for (const workflow of values(this.workflows)) { + if (!this.workflowUidMapper.hasOwnProperty(workflow.uid)) { + await this.createCustomRoleIfNotExists(workflow); + } + } + + const onSuccess = ({ response, apiData: { uid, name } = { uid: null, name: '' } }: any) => { + this.createdWorkflows.push(response); + this.workflowUidMapper[uid] = response.uid; + log(this.importConfig, `Workflow '${name}' imported successfully`, 'success'); + fsUtil.writeFile(this.workflowUidMapperPath, this.workflowUidMapper); + }; + + const onReject = ({ error, apiData }: any) => { + const err = error?.message ? JSON.parse(error.message) : error; + const { name } = apiData; + if (err?.errors?.name) { + log(this.importConfig, `Workflow '${name}' already exists`, 'info'); + } else { + this.failedWebhooks.push(apiData); + if (error.errors['workflow_stages.0.users']) { + log( + this.importConfig, + "Failed to import Workflows as you've specified certain roles in the Stage transition and access rules section. We currently don't import roles to the stack.", + 'error', + ); + } else { + log(this.importConfig, `Workflow '${name}' failed to be import. ${formatError(error)}`, 'error'); + } + } + }; + + await this.makeConcurrentCall( + { + apiContent, + processName: 'create workflows', + apiParams: { + serializeData: this.serializeWorkflows.bind(this), + reject: onReject, + resolve: onSuccess, + entity: 'create-workflows', + includeParamOnCompletion: true, + }, + concurrencyLimit: config.fetchConcurrency || 1, + }, + undefined, + false, + ); + } + + /** + * @method serializeWorkflows + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeWorkflows(apiOptions: ApiOptions): ApiOptions { + let { apiData: workflow } = apiOptions; + + if (this.workflowUidMapper.hasOwnProperty(workflow.uid)) { + log(this.importConfig, `Workflow '${workflow.name}' already exists. Skipping it to avoid duplicates!`, 'info'); + apiOptions.entity = undefined; + } else { + if (workflow.admin_users !== undefined) { + log(this.importConfig, chalk.yellow('We are skipping import of `Workflow superuser(s)` from workflow'), 'info'); + delete workflow.admin_users; + } + // One branch is required to create workflow. + if (!workflow.branches) { + workflow.branches = ['main']; + } + apiOptions.apiData = workflow; + } + return apiOptions; + } + + async createCustomRoleIfNotExists(workflow: Record) { + const onSuccess = ({ response, apiData, additionalInfo: { workflowUid, stageIndex } }: any) => { + const { name } = apiData; + this.updateRoleData({ workflowUid, stageIndex, roleData: apiData }); + this.roleNameMap[name] = response?.uid; + }; + + const onReject = ({ error, apiData: { name } = { name: '' } }: any) => { + log(this.importConfig, `Failed to create custom roles '${name}'.${formatError(error)}`, 'error'); + }; + + const workflowStages = workflow.workflow_stages; + let stageIndex = 0; + for (const stage of workflowStages) { + if (stage?.SYS_ACL?.users?.uids?.length && stage?.SYS_ACL?.users?.uids[0] !== '$all') { + stage.SYS_ACL.users.uids = ['$all']; + } + if (stage?.SYS_ACL?.roles?.uids?.length) { + const apiContent = stage.SYS_ACL.roles.uids; + await this.makeConcurrentCall( + { + apiContent, + processName: 'create custom role', + apiParams: { + serializeData: this.serializeCustomRoles.bind(this), + reject: onReject, + resolve: onSuccess, + entity: 'create-custom-role', + includeParamOnCompletion: true, + additionalInfo: { workflowUid: workflow.uid, stageIndex }, + }, + concurrencyLimit: config.fetchConcurrency || 1, + }, + undefined, + false, + ); + } + stageIndex++; + } + } + + /** + * @method serializeCustomRoles + * @param {ApiOptions} apiOptions ApiOptions + * @returns {ApiOptions} ApiOptions + */ + serializeCustomRoles(apiOptions: ApiOptions): ApiOptions { + let { + apiData: roleData, + additionalInfo: { workflowUid, stageIndex }, + } = apiOptions; + if (!this.roleNameMap[roleData.name]) { + // rules.branch is required to create custom roles. + const branchRuleExists = find(roleData.rules, (rule: any) => rule.module === 'branch'); + if (!branchRuleExists) { + roleData.rules.push({ + module: 'branch', + branches: ['main'], + acl: { read: true }, + }); + } + apiOptions = roleData; + } else { + apiOptions.entity = undefined; + this.updateRoleData({ workflowUid, stageIndex, roleData }); + } + return apiOptions; + } + + updateRoleData(params: { workflowUid: string; stageIndex: number; roleData: any }) { + const { workflowUid, stageIndex, roleData } = params; + const workflowStage = this.workflows[workflowUid].workflow_stages; + const roles = workflowStage[stageIndex].SYS_ACL.roles.uids; + const index = findIndex(roles, ['uid', roleData.uid]); + roles[index >= 0 ? index : roles.length] = this.roleNameMap[roleData.name]; + } +} diff --git a/packages/contentstack-import/src/types/default-config.ts b/packages/contentstack-import/src/types/default-config.ts index 4c30c98ba9..b833ed90fa 100644 --- a/packages/contentstack-import/src/types/default-config.ts +++ b/packages/contentstack-import/src/types/default-config.ts @@ -141,4 +141,5 @@ export default interface DefaultConfig { marketplaceAppEncryptionKey: string; getEncryptionKeyMaxRetry: number; useNewModuleStructure: boolean; + createBackupDir?: string; } diff --git a/packages/contentstack-import/src/utils/asset-helper.ts b/packages/contentstack-import/src/utils/asset-helper.ts index 0832824d88..c8fa8cb1ea 100644 --- a/packages/contentstack-import/src/utils/asset-helper.ts +++ b/packages/contentstack-import/src/utils/asset-helper.ts @@ -53,11 +53,11 @@ export const uploadAssetHelper = function (config: ImportConfig, req: any, fsPat // get assets object export const lookupAssets = function ( - data: any, - mappedAssetUids: string[], - mappedAssetUrls: string[], - assetUidMapperPath: string[], - installedExtensions: string[], + data: Record, + mappedAssetUids: Record, + mappedAssetUrls: Record, + assetUidMapperPath: string, + installedExtensions: Record[], ) { if ( !_.has(data, 'entry') || diff --git a/packages/contentstack-import/src/utils/backup-handler.ts b/packages/contentstack-import/src/utils/backup-handler.ts index 47ca3fbe18..bc87f1b5bf 100755 --- a/packages/contentstack-import/src/utils/backup-handler.ts +++ b/packages/contentstack-import/src/utils/backup-handler.ts @@ -1,13 +1,25 @@ import * as path from 'path'; import ncp from 'ncp'; + import { ImportConfig } from '../types'; +import { fileHelper } from './index'; export default function setupBackupDir(importConfig: ImportConfig): Promise { return new Promise(async (resolve, reject) => { if (importConfig.hasOwnProperty('useBackedupDir')) { return resolve(importConfig.useBackedupDir); } - const backupDirPath = path.join(process.cwd(), '_backup_' + Math.floor(Math.random() * 1000)); + + //NOTE: If the backup folder's directory is provided, create it at that location; otherwise, the default path (working directory). + let backupDirPath = path.join(process.cwd(), '_backup_' + Math.floor(Math.random() * 1000)); + if (importConfig.createBackupDir) { + if (fileHelper.fileExistsSync(importConfig.createBackupDir)) { + fileHelper.removeDirSync(importConfig.createBackupDir); + } + fileHelper.makeDirectory(importConfig.createBackupDir); + backupDirPath = importConfig.createBackupDir; + } + const limit = importConfig.backupConcurrency || 16; if (path.isAbsolute(importConfig.contentDir)) { return ncp(importConfig.contentDir, backupDirPath, { limit }, (error) => { diff --git a/packages/contentstack-import/src/utils/entries-helper.ts b/packages/contentstack-import/src/utils/entries-helper.ts index e432fe01eb..acca676921 100644 --- a/packages/contentstack-import/src/utils/entries-helper.ts +++ b/packages/contentstack-import/src/utils/entries-helper.ts @@ -8,7 +8,7 @@ import config from '../config'; import * as fileHelper from './file-helper'; // update references in entry object -export const lookupEntries = function (data: any, mappedUids: string[], uidMapperPath: string) { +export const lookupEntries = function (data: any, mappedUids: Record, uidMapperPath: string) { let parent: string[] = []; let uids: string[] = []; let unmapped: string[] = []; @@ -90,7 +90,7 @@ export const lookupEntries = function (data: any, mappedUids: string[], uidMappe } } }; - const find = function (schema: any, _entry: any) { + const find = function (schema: any = [], _entry: any) { for (let i = 0, _i = schema.length; i < _i; i++) { switch (schema[i].data_type) { case 'reference': @@ -244,3 +244,342 @@ function findUidsInNewRefFields(entry: any, uids: string[]) { } } } + +export const removeUidsFromJsonRteFields = ( + entry: Record, + ctSchema: Record[], +): Record => { + for (const element of ctSchema) { + switch (element.data_type) { + case 'blocks': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any) => { + let key = Object.keys(e).pop(); + let subBlock = element.blocks.filter((block: any) => block.uid === key).pop(); + e[key] = removeUidsFromJsonRteFields(e[key], subBlock.schema); + return e; + }); + } + } + break; + } + case 'global_field': + case 'group': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any) => { + e = removeUidsFromJsonRteFields(e, element.schema); + return e; + }); + } else { + entry[element.uid] = removeUidsFromJsonRteFields(entry[element.uid], element.schema); + } + } + break; + } + case 'json': { + if (entry[element.uid] && element.field_metadata.rich_text_type) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((jsonRteData: any) => { + delete jsonRteData.uid; // remove uid + + if (_.isObject(jsonRteData.attrs)) { + jsonRteData.attrs.dirty = true; + } + + if (!_.isEmpty(jsonRteData.children)) { + jsonRteData.children = _.map(jsonRteData.children, (child) => removeUidsFromChildren(child)); + } + + return jsonRteData; + }); + } else { + delete entry[element.uid].uid; // remove uid + if (entry[element.uid] && _.isObject(entry[element.uid].attrs)) { + entry[element.uid].attrs.dirty = true; + } + if (entry[element.uid] && !_.isEmpty(entry[element.uid].children)) { + entry[element.uid].children = _.map(entry[element.uid].children, (child) => + removeUidsFromChildren(child), + ); + } + } + } + break; + } + } + } + return entry; +}; + +function removeUidsFromChildren(children: Record[] | any) { + if (children.length && children.length > 0) { + return children.map((child: any) => { + if (child.type && child.type.length > 0) { + delete child.uid; // remove uid + + if (_.isObject(child.attrs)) { + child.attrs.dirty = true; + } + } + if (child.children && child.children.length > 0) { + child.children = removeUidsFromChildren(child.children); + } + return child; + }); + } else { + if (children.type && children.type.length > 0) { + delete children.uid; // remove uid + if (_.isObject(children.attrs)) { + children.attrs.dirty = true; + } + } + if (children.children && children.children.length > 0) { + children.children = removeUidsFromChildren(children.children); + } + return children; + } +} + +export const removeEntryRefsFromJSONRTE = (entry: Record, ctSchema: Record[]) => { + for (const element of ctSchema) { + switch (element.data_type) { + case 'blocks': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any) => { + let key = Object.keys(e).pop(); + let subBlock = element.blocks.filter((block: any) => block.uid === key).pop(); + e[key] = removeEntryRefsFromJSONRTE(e[key], subBlock.schema); + return e; + }); + } + } + break; + } + case 'global_field': + case 'group': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any) => { + e = removeEntryRefsFromJSONRTE(e, element.schema); + return e; + }); + } else { + entry[element.uid] = removeEntryRefsFromJSONRTE(entry[element.uid], element.schema); + } + } + break; + } + case 'json': { + if (entry[element.uid] && element.field_metadata.rich_text_type) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((jsonRteData: any) => { + // repeated code from else block, will abstract later + let entryReferences = jsonRteData.children.filter((e: any) => doEntryReferencesExist(e)); + if (entryReferences.length > 0) { + jsonRteData.children = jsonRteData.children.filter((e: any) => !doEntryReferencesExist(e)); + return jsonRteData; // return jsonRteData without entry references + } else { + return jsonRteData; // return jsonRteData as it is, because there are no entry references + } + }); + } else { + let entryReferences = entry[element.uid].children.filter((e: any) => doEntryReferencesExist(e)); + if (entryReferences.length > 0) { + entry[element.uid].children = entry[element.uid].children.filter((e: any) => !doEntryReferencesExist(e)); + } + } + } + break; + } + } + } + return entry; +}; + +function doEntryReferencesExist(element: Record[] | any): boolean { + // checks if the children of p element contain any references + // only checking one level deep, not recursive + + if (element.length) { + for (const item of element) { + if ((item.type === 'p' || item.type === 'a') && item.children && item.children.length > 0) { + return doEntryReferencesExist(item.children); + } else if (isEntryRef(item)) { + return true; + } + } + } else { + if (isEntryRef(element)) { + return true; + } + + if ((element.type === 'p' || element.type === 'a') && element.children && element.children.length > 0) { + return doEntryReferencesExist(element.children); + } + } + return false; +} + +function isEntryRef(element: any) { + return element.type === 'reference' && element.attrs.type === 'entry'; +} + +export const restoreJsonRteEntryRefs = ( + entry: Record, + sourceStackEntry: any, + ctSchema: any, + { mappedAssetUids, mappedAssetUrls }: any, +) => { + // let mappedAssetUids = fileHelper.readFileSync(this.mappedAssetUidPath) || {}; + // let mappedAssetUrls = fileHelper.readFileSync(this.mappedAssetUrlPath) || {}; + for (const element of ctSchema) { + switch (element.data_type) { + case 'blocks': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any, eIndex: number) => { + let key = Object.keys(e).pop(); + let subBlock = element.blocks.filter((block: any) => block.uid === key).pop(); + let sourceStackElement = sourceStackEntry[element.uid][eIndex][key]; + e[key] = restoreJsonRteEntryRefs(e[key], sourceStackElement, subBlock.schema, { + mappedAssetUids, + mappedAssetUrls, + }); + return e; + }); + } + } + break; + } + case 'global_field': + case 'group': { + if (entry[element.uid]) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((e: any, eIndex: number) => { + let sourceStackElement = sourceStackEntry[element.uid][eIndex]; + e = restoreJsonRteEntryRefs(e, sourceStackElement, element.schema, { mappedAssetUids, mappedAssetUrls }); + return e; + }); + } else { + let sourceStackElement = sourceStackEntry[element.uid]; + entry[element.uid] = restoreJsonRteEntryRefs(entry[element.uid], sourceStackElement, element.schema, { + mappedAssetUids, + mappedAssetUrls, + }); + } + } + break; + } + case 'json': { + if (entry[element.uid] && element.field_metadata.rich_text_type) { + if (element.multiple) { + entry[element.uid] = entry[element.uid].map((field: any, index: number) => { + // i am facing a Maximum call stack exceeded issue, + // probably because of this loop operation + + let entryRefs = sourceStackEntry[element.uid][index].children + .map((e: any, i: number) => { + return { index: i, value: e }; + }) + .filter((e: any) => doEntryReferencesExist(e.value)) + .map((e: any) => { + // commenting the line below resolved the maximum call stack exceeded issue + // e.value = this.setDirtyTrue(e.value) + setDirtyTrue(e.value); + return e; + }) + .map((e: any) => { + // commenting the line below resolved the maximum call stack exceeded issue + // e.value = this.resolveAssetRefsInEntryRefsForJsonRte(e, mappedAssetUids, mappedAssetUrls) + resolveAssetRefsInEntryRefsForJsonRte(e.value, mappedAssetUids, mappedAssetUrls); + return e; + }); + + if (entryRefs.length > 0) { + entryRefs.forEach((entryRef: any) => { + field.children.splice(entryRef.index, 0, entryRef.value); + }); + } + return field; + }); + } else { + let entryRefs = sourceStackEntry[element.uid].children + .map((e: any, index: number) => { + return { index: index, value: e }; + }) + .filter((e: any) => doEntryReferencesExist(e.value)) + .map((e: any) => { + setDirtyTrue(e.value); + return e; + }) + .map((e: any) => { + resolveAssetRefsInEntryRefsForJsonRte(e.value, mappedAssetUids, mappedAssetUrls); + return e; + }); + + if (entryRefs.length > 0) { + entryRefs.forEach((entryRef: any) => { + if (!_.isEmpty(entry[element.uid]) && entry[element.uid].children) { + entry[element.uid].children.splice(entryRef.index, 0, entryRef.value); + } + }); + } + } + } + break; + } + } + } + return entry; +}; + +function setDirtyTrue(jsonRteChild: any) { + // also removing uids in this function + if (jsonRteChild.type) { + if (_.isObject(jsonRteChild.attrs)) { + jsonRteChild.attrs['dirty'] = true; + } + delete jsonRteChild.uid; + + if (jsonRteChild.children && jsonRteChild.children.length > 0) { + jsonRteChild.children = jsonRteChild.children.map((subElement: any) => this.setDirtyTrue(subElement)); + } + } + return jsonRteChild; +} + +function resolveAssetRefsInEntryRefsForJsonRte(jsonRteChild: any, mappedAssetUids: any, mappedAssetUrls: any) { + if (jsonRteChild.type) { + if (jsonRteChild.attrs.type === 'asset') { + let assetUrl; + if (mappedAssetUids[jsonRteChild.attrs['asset-uid']]) { + jsonRteChild.attrs['asset-uid'] = mappedAssetUids[jsonRteChild.attrs['asset-uid']]; + } + + if (jsonRteChild.attrs['display-type'] !== 'link') { + assetUrl = jsonRteChild.attrs['asset-link']; + } else { + assetUrl = jsonRteChild.attrs['href']; + } + + if (mappedAssetUrls[assetUrl]) { + if (jsonRteChild.attrs['display-type'] !== 'link') { + jsonRteChild.attrs['asset-link'] = mappedAssetUrls[assetUrl]; + } else { + jsonRteChild.attrs['href'] = mappedAssetUrls[assetUrl]; + } + } + } + + if (jsonRteChild.children && jsonRteChild.children.length > 0) { + jsonRteChild.children = jsonRteChild.children.map((subElement: any) => + resolveAssetRefsInEntryRefsForJsonRte(subElement, mappedAssetUids, mappedAssetUrls), + ); + } + } + + return jsonRteChild; +} diff --git a/packages/contentstack-import/src/utils/file-helper.ts b/packages/contentstack-import/src/utils/file-helper.ts index 5e6b8af985..dd97ea0dc4 100644 --- a/packages/contentstack-import/src/utils/file-helper.ts +++ b/packages/contentstack-import/src/utils/file-helper.ts @@ -131,4 +131,8 @@ export const fileExistsSync = function (path: string) { return fs.existsSync(path); }; +export const removeDirSync = function(path: string){ + fs.rmdirSync(path, { recursive: true}); +} + export const fsUtil = new FsUtility(); diff --git a/packages/contentstack-import/src/utils/import-config-handler.ts b/packages/contentstack-import/src/utils/import-config-handler.ts index 6bab5151ef..f02681ce4b 100644 --- a/packages/contentstack-import/src/utils/import-config-handler.ts +++ b/packages/contentstack-import/src/utils/import-config-handler.ts @@ -56,7 +56,7 @@ const setupConfig = async (importCmdFlags: any): Promise => { //Note to support the old key config.source_stack = config.apiKey; - config.importWebhookStatus = importCmdFlags.importWebhookStatus; + config.importWebhookStatus = importCmdFlags['import-webhook-status']; config.forceStopMarketplaceAppsPrompt = importCmdFlags.yes; if (importCmdFlags['branch']) { diff --git a/packages/contentstack-import/src/utils/index.ts b/packages/contentstack-import/src/utils/index.ts index 138e589111..19cfca611e 100644 --- a/packages/contentstack-import/src/utils/index.ts +++ b/packages/contentstack-import/src/utils/index.ts @@ -16,9 +16,14 @@ export { confirmToCloseProcess, getAllStackSpecificApps, ifAppAlreadyExist, - updateAppConfig + updateAppConfig, } from './marketplace-app-helper'; export { schemaTemplate, suppressSchemaReference, removeReferenceFields } from './content-type-helper'; export { lookupExtension } from './extension-helper'; -export { lookupEntries } from './entries-helper'; +export { + lookupEntries, + removeUidsFromJsonRteFields, + removeEntryRefsFromJSONRTE, + restoreJsonRteEntryRefs, +} from './entries-helper'; export * from './common-helper'; diff --git a/packages/contentstack-launch/README.md b/packages/contentstack-launch/README.md index 02c7a6cc48..018e169057 100755 --- a/packages/contentstack-launch/README.md +++ b/packages/contentstack-launch/README.md @@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-launch $ csdx COMMAND running command... $ csdx (--version|-v) -@contentstack/cli-launch/1.0.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-launch/1.0.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-launch/package.json b/packages/contentstack-launch/package.json index 30ea0a730f..d4858f0546 100755 --- a/packages/contentstack-launch/package.json +++ b/packages/contentstack-launch/package.json @@ -18,8 +18,8 @@ ], "dependencies": { "@apollo/client": "^3.7.9", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/plugin-help": "^5", "@oclif/plugin-plugins": "^2.3.2", "@oclif/core": "^2.9.3", diff --git a/packages/contentstack-migrate-rte/README.md b/packages/contentstack-migrate-rte/README.md index 77f51af25e..dfd4e10f39 100644 --- a/packages/contentstack-migrate-rte/README.md +++ b/packages/contentstack-migrate-rte/README.md @@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-migrate-rte $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-migrate-rte/1.4.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-cm-migrate-rte/1.4.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-migrate-rte/package.json b/packages/contentstack-migrate-rte/package.json index 42c3d3cbb5..6420093053 100644 --- a/packages/contentstack-migrate-rte/package.json +++ b/packages/contentstack-migrate-rte/package.json @@ -5,9 +5,9 @@ "author": "contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", - "@contentstack/json-rte-serializer": "^2.0.2", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", + "@contentstack/json-rte-serializer": "~2.0.2", "collapse-whitespace": "^1.1.7", "chalk": "^4.1.2", "jsdom": "^20.0.3", diff --git a/packages/contentstack-migration/README.md b/packages/contentstack-migration/README.md index fb6fdebcb9..05ed4d7c8a 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.10 darwin-arm64 node-v20.3.1 +@contentstack/cli-migration/1.3.10 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-migration/package.json b/packages/contentstack-migration/package.json index 8e0bdbc9b4..f0f02f3961 100644 --- a/packages/contentstack-migration/package.json +++ b/packages/contentstack-migration/package.json @@ -4,8 +4,8 @@ "author": "@contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "@oclif/command": "^1.8.16", "@oclif/config": "^1.18.3", "async": "^3.2.4", diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index fdf6c95164..5a3af51d0b 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -1,13 +1,13 @@ { "name": "@contentstack/cli-cm-seed", "description": "create a Stack from existing content types, entries, assets, etc.", - "version": "1.4.13", + "version": "1.4.14", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-cm-import": "^1.7.0", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-utilities": "~1.5.1", "axios": "1.3.4", "inquirer": "8.2.4", "mkdirp": "^1.0.4", @@ -73,4 +73,4 @@ "version": "oclif readme && git add README.md", "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo" } -} +} \ No newline at end of file diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 5241d7b2e2..371575eed8 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.7.12 darwin-arm64 node-v20.3.1 +@contentstack/cli/1.8.0 darwin-arm64 node-v18.11.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index 3c23a47762..42cbaa6ab8 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli", "description": "Command-line tool (CLI) to interact with Contentstack", - "version": "1.7.12", + "version": "1.8.0", "author": "Contentstack", "bin": { "csdx": "./bin/run" @@ -22,22 +22,22 @@ "prepack": "pnpm compile && oclif manifest && oclif readme" }, "dependencies": { - "@contentstack/cli-auth": "^1.3.12", - "@contentstack/cli-cm-bootstrap": "^1.4.13", - "@contentstack/cli-cm-bulk-publish": "^1.3.10", - "@contentstack/cli-cm-clone": "^1.4.14", - "@contentstack/cli-cm-export": "^1.7.0", - "@contentstack/cli-cm-export-to-csv": "^1.3.12", - "@contentstack/cli-cm-import": "^1.7.1", - "@contentstack/cli-cm-migrate-rte": "^1.4.10", - "@contentstack/cli-cm-seed": "^1.4.13", - "@contentstack/cli-command": "^1.2.11", - "@contentstack/cli-config": "^1.4.10", - "@contentstack/cli-launch": "^1.0.10", - "@contentstack/cli-migration": "^1.3.10", - "@contentstack/cli-utilities": "^1.5.1", + "@contentstack/cli-auth": "~1.3.12", + "@contentstack/cli-cm-bootstrap": "~1.4.14", + "@contentstack/cli-cm-bulk-publish": "~1.3.10", + "@contentstack/cli-cm-clone": "~1.4.15", + "@contentstack/cli-cm-export": "~1.8.0", + "@contentstack/cli-cm-export-to-csv": "~1.3.12", + "@contentstack/cli-cm-import": "~1.8.0", + "@contentstack/cli-cm-migrate-rte": "~1.4.10", + "@contentstack/cli-cm-seed": "~1.4.14", + "@contentstack/cli-command": "~1.2.11", + "@contentstack/cli-config": "~1.4.10", + "@contentstack/cli-launch": "~1.0.10", + "@contentstack/cli-migration": "~1.3.10", + "@contentstack/cli-utilities": "~1.5.1", "@contentstack/management": "~1.10.0", - "@contentstack/cli-cm-branches": "^1.0.10", + "@contentstack/cli-cm-branches": "~1.0.10", "@oclif/plugin-help": "^5", "@oclif/plugin-not-found": "^2.3.9", "@oclif/plugin-plugins": "^2.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a4d1e945d..e2cead0850 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,21 +10,21 @@ importers: packages/contentstack: specifiers: - '@contentstack/cli-auth': ^1.3.12 - '@contentstack/cli-cm-bootstrap': ^1.4.13 - '@contentstack/cli-cm-branches': ^1.0.10 - '@contentstack/cli-cm-bulk-publish': ^1.3.10 - '@contentstack/cli-cm-clone': ^1.4.14 - '@contentstack/cli-cm-export': ^1.7.0 - '@contentstack/cli-cm-export-to-csv': ^1.3.12 - '@contentstack/cli-cm-import': ^1.7.1 - '@contentstack/cli-cm-migrate-rte': ^1.4.10 - '@contentstack/cli-cm-seed': ^1.4.13 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-config': ^1.4.10 - '@contentstack/cli-launch': ^1.0.10 - '@contentstack/cli-migration': ^1.3.10 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-auth': ~1.3.12 + '@contentstack/cli-cm-bootstrap': ~1.4.14 + '@contentstack/cli-cm-branches': ~1.0.10 + '@contentstack/cli-cm-bulk-publish': ~1.3.10 + '@contentstack/cli-cm-clone': ~1.4.15 + '@contentstack/cli-cm-export': ~1.8.0 + '@contentstack/cli-cm-export-to-csv': ~1.3.12 + '@contentstack/cli-cm-import': ~1.8.0 + '@contentstack/cli-cm-migrate-rte': ~1.4.10 + '@contentstack/cli-cm-seed': ~1.4.14 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-config': ~1.4.10 + '@contentstack/cli-launch': ~1.0.10 + '@contentstack/cli-migration': ~1.3.10 + '@contentstack/cli-utilities': ~1.5.1 '@contentstack/management': ~1.10.0 '@oclif/core': ^2.9.3 '@oclif/plugin-help': ^5 @@ -119,8 +119,8 @@ importers: packages/contentstack-auth: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@fancy-test/nock': ^0.1.1 '@oclif/plugin-help': ^5.1.19 '@oclif/test': ^2.2.10 @@ -182,9 +182,9 @@ importers: packages/contentstack-bootstrap: specifiers: - '@contentstack/cli-cm-seed': ^1.4.13 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-cm-seed': ~1.4.14 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^2.2.10 '@types/inquirer': ^9.0.3 '@types/mkdirp': ^1.0.1 @@ -233,11 +233,11 @@ importers: packages/contentstack-branches: specifiers: - '@contentstack/cli-auth': ^1.3.11 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-config': ^1.4.9 - '@contentstack/cli-dev-dependencies': ^1.2.3 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-auth': ~1.3.11 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-config': ~1.4.9 + '@contentstack/cli-dev-dependencies': ~1.2.3 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/command': ^1.8.16 '@oclif/config': ^1.18.3 '@oclif/core': ^2.9.3 @@ -316,8 +316,8 @@ importers: packages/contentstack-bulk-publish: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^1.2.6 bluebird: ^3.7.2 chai: ^4.2.0 @@ -360,11 +360,10 @@ importers: packages/contentstack-clone: specifiers: '@colors/colors': ^1.5.0 - '@contentstack/cli-cm-export': ^1.7.0 - '@contentstack/cli-cm-import': ^1.7.1 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 - '@contentstack/management': ~1.10.0 + '@contentstack/cli-cm-export': ~1.8.0 + '@contentstack/cli-cm-import': ~1.8.0 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^1.2.7 async: ^3.2.4 chai: ^4.2.0 @@ -390,7 +389,6 @@ importers: '@contentstack/cli-cm-import': link:../contentstack-import '@contentstack/cli-command': link:../contentstack-command '@contentstack/cli-utilities': link:../contentstack-utilities - '@contentstack/management': 1.10.0 async: 3.2.4 chalk: 4.1.2 child_process: 1.0.2 @@ -414,7 +412,7 @@ importers: packages/contentstack-command: specifiers: - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^2.2.10 '@types/chai': ^4.2.18 '@types/mkdirp': ^1.0.1 @@ -455,8 +453,8 @@ importers: packages/contentstack-config: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^2.2.10 '@types/chai': ^4.2.18 '@types/inquirer': ^9.0.3 @@ -539,11 +537,11 @@ importers: packages/contentstack-export: specifiers: - '@contentstack/cli-auth': ^1.3.11 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-config': ^1.4.9 - '@contentstack/cli-dev-dependencies': ^1.2.3 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-auth': ~1.3.11 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-config': ~1.4.9 + '@contentstack/cli-dev-dependencies': ~1.2.3 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/command': ^1.8.16 '@oclif/config': ^1.18.3 '@oclif/core': ^2.9.3 @@ -622,8 +620,8 @@ importers: packages/contentstack-export-to-csv: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/test': ^2.2.10 chai: ^4.2.0 chalk: ^4.1.0 @@ -659,8 +657,8 @@ importers: packages/contentstack-import: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@contentstack/management': ~1.10.0 '@oclif/config': ^1.18.3 '@oclif/core': ^2.9.3 @@ -743,8 +741,8 @@ importers: packages/contentstack-launch: specifiers: '@apollo/client': ^3.7.9 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/core': ^2.9.3 '@oclif/plugin-help': ^5 '@oclif/plugin-plugins': ^2.3.2 @@ -819,9 +817,9 @@ importers: packages/contentstack-migrate-rte: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 - '@contentstack/json-rte-serializer': ^2.0.2 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 + '@contentstack/json-rte-serializer': ~2.0.2 '@oclif/test': ^2.2.10 chai: ^4.3.4 chalk: ^4.1.2 @@ -866,8 +864,8 @@ importers: packages/contentstack-migration: specifiers: - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/command': ^1.8.16 '@oclif/config': ^1.18.3 '@oclif/test': ^2.2.10 @@ -917,9 +915,9 @@ importers: packages/contentstack-seed: specifiers: - '@contentstack/cli-cm-import': ^1.7.0 - '@contentstack/cli-command': ^1.2.11 - '@contentstack/cli-utilities': ^1.5.1 + '@contentstack/cli-cm-import': ~1.8.0 + '@contentstack/cli-command': ~1.2.11 + '@contentstack/cli-utilities': ~1.5.1 '@oclif/plugin-help': ^5.1.19 '@types/inquirer': ^9.0.3 '@types/jest': ^26.0.15 @@ -1503,18 +1501,6 @@ packages: uuid: 8.3.2 dev: false - /@contentstack/management/1.10.0: - resolution: {integrity: sha512-wnmVS19n3cZeh6T2PbBXGCr4irktunVmuECTyPF5++Rsc6GDO+FoQGCZdvzHpRZE0GLiPFIID50z2TZuNArEOg==} - engines: {node: '>=8.0.0'} - dependencies: - axios: 0.27.2 - form-data: 3.0.1 - lodash: 4.17.21 - qs: 6.11.2 - transitivePeerDependencies: - - debug - dev: false - /@contentstack/management/1.10.0_debug@4.3.4: resolution: {integrity: sha512-wnmVS19n3cZeh6T2PbBXGCr4irktunVmuECTyPF5++Rsc6GDO+FoQGCZdvzHpRZE0GLiPFIID50z2TZuNArEOg==} engines: {node: '>=8.0.0'} @@ -4200,15 +4186,6 @@ packages: xml2js: 0.5.0 dev: true - /axios/0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - transitivePeerDependencies: - - debug - dev: false - /axios/0.27.2_debug@4.3.4: resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: