From c741316e866bfc24b87440dfd6829a526f97c938 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Fri, 22 May 2026 12:34:10 +0530 Subject: [PATCH 1/3] feat: migrate RTE plugin to cli-plugins monorepo - Moved the `@contentstack/cli-cm-migrate-rte` plugin from the standalone repository to the `cli-plugins` monorepo. - Updated documentation in `AGENTS.md` to include the new migrate RTE plugin and its purpose. - Created `MIGRATE-RTE-MIGRATION.md` to document the migration process and repository changes. - Added new commands, configuration files, and tests for the migrate RTE plugin. - Updated CI workflows to include testing and publishing for the migrate RTE plugin. --- .github/config/release.json | 3 +- .github/workflows/release-v2-beta-plugins.yml | 9 + .github/workflows/unit-test.yml | 4 + AGENTS.md | 9 +- MIGRATE-RTE-MIGRATION.md | 40 + MIGRATION.md | 2 + packages/contentstack-migrate-rte/.eslintrc | 3 + packages/contentstack-migrate-rte/.gitignore | 16 + packages/contentstack-migrate-rte/.nycrc | 12 + packages/contentstack-migrate-rte/LICENSE | 21 + packages/contentstack-migrate-rte/README.md | 93 + packages/contentstack-migrate-rte/SECURITY.md | 27 + packages/contentstack-migrate-rte/bin/run.cmd | 3 + packages/contentstack-migrate-rte/bin/run.js | 7 + .../contentstack-migrate-rte/package.json | 87 + .../commands/cm/entries/migrate-html-rte.js | 164 + .../src/hooks/init/load-chalk.js | 8 + .../src/lib/util/config_schema.json | 272 ++ .../src/lib/util/index.js | 722 ++++ .../test/commands/json-migration.test.js | 1286 +++++++ .../config/config-for-images-in-rte.json | 12 + .../test/dummy/config/config-locale-2.json | 13 + .../test/dummy/config/config-locale.json | 13 + .../test/dummy/config/config.json | 12 + .../dummy/config/configForGlobalField.json | 12 + .../config/configForInvalidContentType.json | 12 + .../dummy/config/configForMultipleRte.json | 12 + .../dummy/config/configWithEmptyPath.json | 7 + .../test/dummy/config/config_locale.json | 13 + .../test/dummy/config/invalidConfig.json | 12 + .../test/dummy/contentTypeResponse.json | 3014 +++++++++++++++++ .../test/dummy/defaultConfig.json | 5 + .../test/dummy/entriesResponse.json | 444 +++ .../test/dummy/expectedEntriesResponse.json | 663 ++++ .../test/dummy/globalFieldResponse.json | 257 ++ .../contentstack-migrate-rte/test/mocha.opts | 4 + .../test/nock-setup.js | 114 + .../contentstack-migrate-rte/test/setup.js | 44 + .../test/utils/index.js | 140 + skills/dev-workflow/SKILL.md | 10 +- 40 files changed, 7598 insertions(+), 3 deletions(-) create mode 100644 MIGRATE-RTE-MIGRATION.md create mode 100644 packages/contentstack-migrate-rte/.eslintrc create mode 100644 packages/contentstack-migrate-rte/.gitignore create mode 100644 packages/contentstack-migrate-rte/.nycrc create mode 100644 packages/contentstack-migrate-rte/LICENSE create mode 100644 packages/contentstack-migrate-rte/README.md create mode 100644 packages/contentstack-migrate-rte/SECURITY.md create mode 100644 packages/contentstack-migrate-rte/bin/run.cmd create mode 100755 packages/contentstack-migrate-rte/bin/run.js create mode 100644 packages/contentstack-migrate-rte/package.json create mode 100644 packages/contentstack-migrate-rte/src/commands/cm/entries/migrate-html-rte.js create mode 100644 packages/contentstack-migrate-rte/src/hooks/init/load-chalk.js create mode 100644 packages/contentstack-migrate-rte/src/lib/util/config_schema.json create mode 100644 packages/contentstack-migrate-rte/src/lib/util/index.js create mode 100644 packages/contentstack-migrate-rte/test/commands/json-migration.test.js create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/config-for-images-in-rte.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/config-locale-2.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/config-locale.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/config.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/configForGlobalField.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/configForInvalidContentType.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/configForMultipleRte.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/configWithEmptyPath.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/config_locale.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/config/invalidConfig.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/contentTypeResponse.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/defaultConfig.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/entriesResponse.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/expectedEntriesResponse.json create mode 100644 packages/contentstack-migrate-rte/test/dummy/globalFieldResponse.json create mode 100644 packages/contentstack-migrate-rte/test/mocha.opts create mode 100644 packages/contentstack-migrate-rte/test/nock-setup.js create mode 100644 packages/contentstack-migrate-rte/test/setup.js create mode 100644 packages/contentstack-migrate-rte/test/utils/index.js diff --git a/.github/config/release.json b/.github/config/release.json index f43492a73..07a492d25 100755 --- a/.github/config/release.json +++ b/.github/config/release.json @@ -10,6 +10,7 @@ "seed": false, "bootstrap": false, "branches": false, - "apps-cli": false + "apps-cli": false, + "migrate-rte": false } } diff --git a/.github/workflows/release-v2-beta-plugins.yml b/.github/workflows/release-v2-beta-plugins.yml index fa5a42c6e..d1eb7a29f 100644 --- a/.github/workflows/release-v2-beta-plugins.yml +++ b/.github/workflows/release-v2-beta-plugins.yml @@ -150,3 +150,12 @@ jobs: token: ${{ secrets.NPM_TOKEN }} package: ./packages/contentstack-apps-cli/package.json tag: beta + + # Migrate RTE + - name: Publishing migrate-rte (Beta) + uses: JS-DevTools/npm-publish@v3 + with: + token: ${{ secrets.NPM_TOKEN }} + package: ./packages/contentstack-migrate-rte/package.json + access: public + tag: beta diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 853bb54e9..f58c8c712 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -70,3 +70,7 @@ jobs: - name: Run tests for Contentstack Apps CLI working-directory: ./packages/contentstack-apps-cli run: npm run test:unit:report:json + + - name: Run tests for Contentstack Migrate RTE + working-directory: ./packages/contentstack-migrate-rte + run: npm test diff --git a/AGENTS.md b/AGENTS.md index cb3fd24d1..41662f964 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ | Field | Detail | | --- | --- | | **Name:** | Contentstack CLI plugins (pnpm monorepo; root package name `csdx`) | -| **Purpose:** | OCLIF plugins that extend the Contentstack CLI (import/export, clone, migration, seed, audit, variants, Developer Hub apps, etc.). | +| **Purpose:** | OCLIF plugins that extend the Contentstack CLI (import/export, clone, migration, migrate RTE, seed, audit, variants, Developer Hub apps, etc.). | | **Out of scope (if any):** | The **core** CLI aggregation lives in the separate `cli` monorepo; this repo ships plugin packages only. | ## Tech stack (at a glance) @@ -48,6 +48,13 @@ CI: [.github/workflows/unit-test.yml](.github/workflows/unit-test.yml) and other - **v1 / v2:** Maintain on `v1-dev` (1.x CLI deps) and `v2-dev` / `v2-beta` (2.x beta deps) branches; align `@contentstack/cli-command` and `@contentstack/cli-utilities` versions with the target CLI line. - **Docs:** OCLIF / `app:*` commands → [contentstack-cli](skills/contentstack-cli/SKILL.md#apps-cli-commands-app); SDK, manifests, GraphQL, HTTP → [framework](skills/framework/SKILL.md#apps-cli-plugin-contentstackapps-cli) +## Migrate RTE plugin (`@contentstack/cli-cm-migrate-rte`) + +- **Package path:** [packages/contentstack-migrate-rte](packages/contentstack-migrate-rte) +- **npm name:** `@contentstack/cli-cm-migrate-rte` (unchanged) +- **Migrated from:** [contentstack/cli-cm-migrate-rte](https://github.com/contentstack/cli-cm-migrate-rte) — see [MIGRATE-RTE-MIGRATION.md](MIGRATE-RTE-MIGRATION.md) +- **Command:** `csdx cm:entries:migrate-html-rte` — JS sources in `src/`; `pnpm --filter @contentstack/cli-cm-migrate-rte run build` (`oclif manifest`) and `test` (see [dev-workflow](skills/dev-workflow/SKILL.md)) + ## Using Cursor (optional) If you use **Cursor**, [.cursor/rules/README.md](.cursor/rules/README.md) only points to **`AGENTS.md`**—same docs as everyone else. diff --git a/MIGRATE-RTE-MIGRATION.md b/MIGRATE-RTE-MIGRATION.md new file mode 100644 index 000000000..d46ea6917 --- /dev/null +++ b/MIGRATE-RTE-MIGRATION.md @@ -0,0 +1,40 @@ +# Migrate RTE migration: standalone repo → cli-plugins monorepo + +## Summary + +**@contentstack/cli-cm-migrate-rte** moved from [contentstack/cli-cm-migrate-rte](https://github.com/contentstack/cli-cm-migrate-rte) into [contentstack/cli-plugins](https://github.com/contentstack/cli-plugins) at **`packages/contentstack-migrate-rte`**. + +The npm package name and command **`csdx cm:entries:migrate-html-rte`** are unchanged. + +## Repository and issues + +| Before | After | +| --- | --- | +| `github.com/contentstack/cli-cm-migrate-rte` | `github.com/contentstack/cli-plugins` → `packages/contentstack-migrate-rte` | +| Issues on standalone repo | [cli-plugins issues](https://github.com/contentstack/cli-plugins/issues) | + +## Version lines (1.x vs 2.x) + +| CLI line | cli-plugins branch | Plugin notes | +| --- | --- | --- | +| **1.x** | `v1-dev` / `v1-beta` | 1.x-compatible `cli-command` / `cli-utilities` | +| **2.x beta** | `v2-dev` / `v2-beta` | e.g. `2.0.0-beta.x`; uses `@contentstack/json-rte-serializer`, jsdom | + +## Install + +```bash +csdx plugins:install @contentstack/cli-cm-migrate-rte +# or +npm install -g @contentstack/cli-cm-migrate-rte +``` + +## Local development + +```bash +cd cli-dev-workspace +pnpm install +pnpm --filter @contentstack/cli-cm-migrate-rte run build +pnpm --filter @contentstack/cli-cm-migrate-rte test +``` + +Core CLI: add `@contentstack/cli-cm-migrate-rte` to `cli/packages/contentstack` dependencies and `oclif.plugins` (use `workspace:*` in cli-dev-workspace). diff --git a/MIGRATION.md b/MIGRATION.md index d9fe2acfe..41922466f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -158,6 +158,8 @@ npm install -g @contentstack/cli-cm-migrate-rte csdx plugins:install @contentstack/cli-cm-migrate-rte@2.0.0-beta ``` +**Source repository:** Plugin code lives in [cli-plugins](https://github.com/contentstack/cli-plugins) at `packages/contentstack-migrate-rte` (formerly [cli-cm-migrate-rte](https://github.com/contentstack/cli-cm-migrate-rte)). See [MIGRATE-RTE-MIGRATION.md](./MIGRATE-RTE-MIGRATION.md). + **Usage:** After installation, RTE migration commands will be available through the CLI: ```bash diff --git a/packages/contentstack-migrate-rte/.eslintrc b/packages/contentstack-migrate-rte/.eslintrc new file mode 100644 index 000000000..e56091ba6 --- /dev/null +++ b/packages/contentstack-migrate-rte/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "oclif" +} diff --git a/packages/contentstack-migrate-rte/.gitignore b/packages/contentstack-migrate-rte/.gitignore new file mode 100644 index 000000000..2560a9991 --- /dev/null +++ b/packages/contentstack-migrate-rte/.gitignore @@ -0,0 +1,16 @@ +*-debug.log +*-error.log +/.nyc_output +/dist +/tmp +/logs +/yarn.lock +node_modules +/coverage +.DS_Store +# Snyk Security Extension - AI Rules (auto-generated) +.cursor/rules/snyk_rules.mdc +oclif.manifest.json +# Husky 9 internal folder (created by "husky" on install; do not commit) +.husky/_ +*.log \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/.nycrc b/packages/contentstack-migrate-rte/.nycrc new file mode 100644 index 000000000..408d6d488 --- /dev/null +++ b/packages/contentstack-migrate-rte/.nycrc @@ -0,0 +1,12 @@ +{ + "all": true, + "extension": [".js"], + "include": ["src/**/*.js"], + "exclude": ["test/**", "node_modules/**"], + "reporter": ["text"], + "check-coverage": true, + "statements": 80, + "branches": 60, + "functions": 80, + "lines": 80 +} diff --git a/packages/contentstack-migrate-rte/LICENSE b/packages/contentstack-migrate-rte/LICENSE new file mode 100644 index 000000000..aff1142ee --- /dev/null +++ b/packages/contentstack-migrate-rte/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/contentstack-migrate-rte/README.md b/packages/contentstack-migrate-rte/README.md new file mode 100644 index 000000000..9615f3a76 --- /dev/null +++ b/packages/contentstack-migrate-rte/README.md @@ -0,0 +1,93 @@ +> **Source of truth:** [cli-plugins](https://github.com/contentstack/cli-plugins) — `packages/contentstack-migrate-rte`. Migrated from [cli-cm-migrate-rte](https://github.com/contentstack/cli-cm-migrate-rte). See [MIGRATE-RTE-MIGRATION.md](../../MIGRATE-RTE-MIGRATION.md). + +# @contentstack/cli-cm-migrate-rte + +It is Contentstack’s CLI plugin to migrate rte. Using this command, you can copy existing value of HTML RTE into JSON RTE. + + +* [@contentstack/cli-cm-migrate-rte](#contentstackcli-cm-migrate-rte) +* [Usage](#usage) +* [Commands](#commands) + + +# Usage + + +```sh-session +$ npm install -g @contentstack/cli-cm-migrate-rte +$ csdx COMMAND +running command... +$ csdx (--version) +@contentstack/cli-cm-migrate-rte/2.0.0-beta.4 darwin-arm64 node-v24.11.1 +$ csdx --help [COMMAND] +USAGE + $ csdx COMMAND +... +``` + + +# Commands + + +* [`csdx cm:entries:migrate-html-rte`](#csdx-cmentriesmigrate-html-rte) + +## `csdx cm:entries:migrate-html-rte` + +Migration script to migrate content from HTML RTE to JSON RTE + +``` +USAGE + $ csdx cm:entries:migrate-html-rte [-c ] [-a ] [--stack-api-key ] [--content-type ] + [--global-field] [-y] [--branch ] [--html-path --json-path ] [--delay ] [--locale + ] [--batch-limit ] + +FLAGS + -a, --alias= Enter the alias name. You must use either the --alias flag or the --stack-api-key flag. + -c, --config-path= Specify the path where your config file is located. + -y, --yes Avoids reconfirmation of your configuration. + --batch-limit= [default: 50] Provide batch limit for updating entries (default: 50). + --branch= The name of the branch to be used. + --content-type= Specify the UID of the content type for which you want to migrate HTML RTE content. + --delay= [default: 1000] To set the interval time between the migration of HTML RTE to JSON RTE in + subsequent entries of a content type. The default value is 1,000 milliseconds. + --global-field Checks whether the specified UID belongs to a content type or a global field. This flag + is set to false by default. + --html-path= Enter the path to the HTML RTE whose content you want to migrate. + --json-path= Enter the path to the JSON RTE to which you want to migrate the HTML RTE content. + --locale= The locale from which entries will be migrated. + --stack-api-key= API key of the source stack. You must use either the --stack-api-key flag or the --alias + flag. + +DESCRIPTION + Migration script to migrate content from HTML RTE to JSON RTE + +EXAMPLES + General Usage + + $ csdx cm:entries:migrate-html-rte --config-path path/to/config.json + + + + Using Flags + + $ csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path html-path --json-path json-path + + + + Nested RTE + + $ csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path modular_block_uid.block_uid.html_rte_uid --json-path modular_block_uid.block_uid.json_rte_uid + + + + $ csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path group_uid.html_rte_uid --json-path group_uid.json_rte_uid + + + + Global Field + + $ csdx cm:entries:migrate-html-rte --alias alias --content-type global_field_uid --global-field --html-path html-path --json-path json-path +``` + +_See code: [src/commands/cm/entries/migrate-html-rte.js](./src/commands/cm/entries/migrate-html-rte.js)_ + diff --git a/packages/contentstack-migrate-rte/SECURITY.md b/packages/contentstack-migrate-rte/SECURITY.md new file mode 100644 index 000000000..1f44e3424 --- /dev/null +++ b/packages/contentstack-migrate-rte/SECURITY.md @@ -0,0 +1,27 @@ +## Security + +Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send email to [security@contentstack.com](mailto:security@contentstack.com). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/) diff --git a/packages/contentstack-migrate-rte/bin/run.cmd b/packages/contentstack-migrate-rte/bin/run.cmd new file mode 100644 index 000000000..968fc3075 --- /dev/null +++ b/packages/contentstack-migrate-rte/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/packages/contentstack-migrate-rte/bin/run.js b/packages/contentstack-migrate-rte/bin/run.js new file mode 100755 index 000000000..8baf30239 --- /dev/null +++ b/packages/contentstack-migrate-rte/bin/run.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core'); + await oclif.execute({ development: false, dir: __dirname }); +})(); diff --git a/packages/contentstack-migrate-rte/package.json b/packages/contentstack-migrate-rte/package.json new file mode 100644 index 000000000..4facb8875 --- /dev/null +++ b/packages/contentstack-migrate-rte/package.json @@ -0,0 +1,87 @@ +{ + "name": "@contentstack/cli-cm-migrate-rte", + "description": "Contentstack CLI plugin to migrate HTML RTE to JSON RTE", + "version": "2.0.0-beta.7", + "author": "contentstack", + "bugs": { + "url": "https://github.com/contentstack/cli-plugins/issues" + }, + "dependencies": { + "@contentstack/cli-command": "~2.0.0-beta.7", + "@contentstack/cli-utilities": "~2.0.0-beta.8", + "@contentstack/json-rte-serializer": "~2.1.0", + "@oclif/core": "^4.3.0", + "@oclif/plugin-help": "^6.2.48", + "chalk": "^5.6.2", + "collapse-whitespace": "^1.1.7", + "jsdom": "^23.2.0", + "jsonschema": "^1.5.0", + "lodash": "^4.18.1", + "omit-deep-lodash": "^1.1.7" + }, + "devDependencies": { + "@oclif/test": "^4.1.18", + "chai": "^4.5.0", + "eslint": "^8.57.1", + "eslint-config-oclif": "^6.0.164", + "fancy-test": "^2.0.42", + "mocha": "^10.8.2", + "nock": "^13.5.6", + "nyc": "^15.1.0", + "oclif": "^4.17.46", + "querystring": "^0.2.1", + "sinon": "^21.1.2", + "husky": "^9.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "overrides": { + "tmp": "0.2.5", + "uuid": "14.0.0", + "follow-redirects": "1.16.0", + "omit-deep-lodash": { + "lodash": "4.18.1" + } + }, + "files": [ + "/npm-shrinkwrap.json", + "/oclif.manifest.json", + "/src" + ], + "homepage": "https://github.com/contentstack/cli-plugins/tree/main/packages/contentstack-migrate-rte", + "keywords": [ + "contentstack", + "cli", + "plugin", + "JSON RTE" + ], + "license": "MIT", + "oclif": { + "commands": "./src/commands", + "bin": "csdx", + "hooks": { + "init": "./src/hooks/init/load-chalk.js" + }, + "repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-migrate-rte/<%- commandPath %>" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/contentstack/cli-plugins.git", + "directory": "packages/contentstack-migrate-rte" + }, + "scripts": { + "build": "oclif manifest", + "prepare": "npx husky && chmod +x .husky/pre-commit", + "postpack": "rm -f oclif.manifest.json", + "prepack": "oclif manifest && oclif readme", + "test": "nyc --check-coverage=false mocha --require test/setup.js --forbid-only \"test/**/*.test.js\"", + "version": "oclif readme && git add README.md", + "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo" + }, + "csdxConfig": { + "shortCommandName": { + "cm:entries:migrate-html-rte": "MGRTRTE" + } + } +} diff --git a/packages/contentstack-migrate-rte/src/commands/cm/entries/migrate-html-rte.js b/packages/contentstack-migrate-rte/src/commands/cm/entries/migrate-html-rte.js new file mode 100644 index 000000000..a74f2c022 --- /dev/null +++ b/packages/contentstack-migrate-rte/src/commands/cm/entries/migrate-html-rte.js @@ -0,0 +1,164 @@ +const { Command } = require("@contentstack/cli-command"); +const { flags, getChalk } = require("@contentstack/cli-utilities"); +const { isEmpty } = require("lodash"); +let { + getStack, + getConfig, + getToken, + updateSingleContentTypeEntries, + updateContentTypeForGlobalField, + normalizeFlags, +} = require("../../../lib/util"); + +class JsonMigrationCommand extends Command { + async run() { + const { flags: migrateRteFlags } = await this.parse(JsonMigrationCommand); + try { + const normalizedFlags = normalizeFlags(migrateRteFlags); + let config = await getConfig(normalizedFlags); + if (isEmpty(config.paths)) { + throw new Error( + 'No value provided for the "paths" property in config.' + ); + } + const stackOptions = { host: this.cmaHost }; + if (config.alias) { + stackOptions.token = getToken(config.alias); + } + if (config["stack-api-key"]) { + stackOptions.stackApiKey = config["stack-api-key"]; + } + if (config.branch) stackOptions.branch = config.branch; + let stack = await getStack(stackOptions); + config.entriesCount = 0; + config.contentTypeCount = 0; + config.errorEntriesUid = {}; + if (config["global-field"]) { + await updateContentTypeForGlobalField( + stack, + config["content-type"], + config + ); + } else { + await updateSingleContentTypeEntries( + stack, + config["content-type"], + config + ); + } + const chalk = getChalk(); + console.log( + chalk.green( + `\nUpdated ${config.contentTypeCount} Content Type(s) and ${config.entriesCount} Entrie(s)` + ) + ); + if ( + config.errorEntriesUid && + Object.keys(config.errorEntriesUid).length > 0 + ) { + const failedCTs = Object.keys(config.errorEntriesUid); + for (const failedCT of failedCTs) { + const locales = Object.keys(config.errorEntriesUid[failedCT]); + for (const locale of locales) { + console.log( + chalk.red( + `Faced issue while migrating some entrie(s) for "${failedCT}" Content-type in "${locale}" locale,"${config.errorEntriesUid[ + failedCT + ][locale].join(", ")}"` + ) + ); + } + } + } + } catch (error) { + this.error(error.message, { exit: 2 }); + } + } +} + +JsonMigrationCommand.description = + "Migration script to migrate content from HTML RTE to JSON RTE"; + +JsonMigrationCommand.flags = { + "config-path": flags.string({ + char: "c", + description: "Specify the path where your config file is located.", + required: false, + }), + alias: flags.string({ + char: "a", + description: + "Enter the alias name. You must use either the --alias flag or the --stack-api-key flag.", + required: false, + }), + "stack-api-key": flags.string({ + description: + "API key of the source stack. You must use either the --stack-api-key flag or the --alias flag.", + required: false, + }), + "content-type": flags.string({ + description: + "Specify the UID of the content type for which you want to migrate HTML RTE content.", + required: false, + }), + "global-field": flags.boolean({ + description: + "Checks whether the specified UID belongs to a content type or a global field. This flag is set to false by default.", + default: false, + required: false, + }), + yes: flags.boolean({ + char: "y", + description: "Avoids reconfirmation of your configuration.", + default: false, + required: false, + }), + branch: flags.string({ + description: "The name of the branch to be used.", + required: false, + }), + "html-path": flags.string({ + description: + "Enter the path to the HTML RTE whose content you want to migrate.", + dependsOn: ["json-path"], + required: false, + }), + "json-path": flags.string({ + description: + "Enter the path to the JSON RTE to which you want to migrate the HTML RTE content.", + dependsOn: ["html-path"], + required: false, + }), + delay: flags.integer({ + description: + "To set the interval time between the migration of HTML RTE to JSON RTE in subsequent entries of a content type. The default value is 1,000 milliseconds.", + default: 1000, + required: false, + }), + locale: flags.string({ + description: "The locale from which entries will be migrated.", + required: false, + }), + "batch-limit": flags.integer({ + description: "Provide batch limit for updating entries (default: 50).", + default: 50, + }), +}; + +JsonMigrationCommand.examples = [ + "General Usage", + "csdx cm:entries:migrate-html-rte --config-path path/to/config.json", + "", + "Using Flags", + "csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path html-path --json-path json-path", + "", + "Nested RTE", + "csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path modular_block_uid.block_uid.html_rte_uid --json-path modular_block_uid.block_uid.json_rte_uid", + "", + "csdx cm:entries:migrate-html-rte --alias alias --content-type content_type_uid --html-path group_uid.html_rte_uid --json-path group_uid.json_rte_uid", + "", + "Global Field", + "csdx cm:entries:migrate-html-rte --alias alias --content-type global_field_uid --global-field --html-path html-path --json-path json-path", +]; + +module.exports = JsonMigrationCommand; diff --git a/packages/contentstack-migrate-rte/src/hooks/init/load-chalk.js b/packages/contentstack-migrate-rte/src/hooks/init/load-chalk.js new file mode 100644 index 000000000..344c6e659 --- /dev/null +++ b/packages/contentstack-migrate-rte/src/hooks/init/load-chalk.js @@ -0,0 +1,8 @@ +const { loadChalk } = require('@contentstack/cli-utilities'); + +/** + * Ensure cli-utilities chalk singleton is ready before command execution. + */ +module.exports = async function loadChalkHook() { + await loadChalk(); +}; diff --git a/packages/contentstack-migrate-rte/src/lib/util/config_schema.json b/packages/contentstack-migrate-rte/src/lib/util/config_schema.json new file mode 100644 index 000000000..81410c98a --- /dev/null +++ b/packages/contentstack-migrate-rte/src/lib/util/config_schema.json @@ -0,0 +1,272 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "The root schema", + "description": "The root schema comprises the entire JSON document.", + "default": {}, + "examples": [ + { + "alias": "test1", + "content-type": "Migration Test", + "global-field": false, + "paths": [ + { + "from": "rich_text", + "to": "supercharged_rte" + } + ] + } + ], + "required": ["content-type", "paths"], + "properties": { + "alias": { + "$id": "#/properties/alias", + "type": "string", + "title": "The alias schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["test1"] + }, + "branch": { + "$id": "#/properties/branch", + "type": "string", + "title": "The branch schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["test1"] + }, + "stack-api-key": { + "$id": "#/properties/stack-api-key", + "type": "string", + "title": "The Stack api key", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["Migration Test"] + }, + "content-type": { + "$id": "#/properties/content-type", + "type": "string", + "title": "The content-type schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["Migration Test"] + }, + "global-field": { + "$id": "#/properties/global-field", + "type": "boolean", + "title": "The global-field schema", + "description": "An explanation about the purpose of this instance.", + "default": false, + "examples": [false] + }, + "paths": { + "$id": "#/properties/paths", + "type": "array", + "title": "The paths schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + { + "from": "rich_text", + "to": "supercharged_rte" + } + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/paths/items", + "anyOf": [ + { + "$id": "html-path and json-path are required", + "type": "object", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "from": "rich_text", + "to": "supercharged_rte" + } + ], + "required": ["from", "to"], + "properties": { + "from": { + "$id": "#/properties/paths/items/anyOf/0/properties/from", + "type": "string", + "title": "The from schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["rich_text"] + }, + "to": { + "$id": "#/properties/paths/items/anyOf/0/properties/to", + "type": "string", + "title": "The to schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["supercharged_rte"] + } + }, + "additionalProperties": true + } + ] + } + }, + "delay": { + "$id": "#/properties/delay", + "type": "integer", + "title": "The delay schema", + "description": "Provide delay in ms between two entry update", + "default": 1000, + "examples": [100] + }, + "failed-entries": { + "type": "array", + "default": [], + "title": "The failed-entries Schema", + "items": { + "type": "string", + "default": "", + "title": "A Schema", + "examples": [ + "blt1c37c346218e033e, blt6ed6d545cdc2064f, blt0594909c6f0a2e82, blt07fe0dc0d2f89e71, blt6d0d993a944947e8" + ] + }, + "examples": [ + ["blt1c37c346218e033e, blt6ed6d545cdc2064f, blt0594909c6f0a2e82, blt07fe0dc0d2f89e71, blt6d0d993a944947e8"] + ] + }, + "locale": { + "type": "array", + "default": [], + "title": "The locale Schema", + "items": { + "type": "string", + "default": "", + "title": "A Schema", + "examples": ["en-in"] + }, + "paths": { + "$id": "#/properties/paths", + "type": "array", + "title": "The paths schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + { + "from": "rich_text", + "to": "supercharged_rte" + } + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/paths/items", + "anyOf": [ + { + "$id": "#/properties/paths/items/anyOf/0", + "type": "object", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "from": "rich_text", + "to": "supercharged_rte" + } + ], + "required": ["from", "to"], + "properties": { + "from": { + "$id": "#/properties/paths/items/anyOf/0/properties/from", + "type": "string", + "title": "The from schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["rich_text"] + }, + "to": { + "$id": "#/properties/paths/items/anyOf/0/properties/to", + "type": "string", + "title": "The to schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": ["supercharged_rte"] + } + }, + "additionalProperties": true + } + ] + } + }, + "delay": { + "$id": "#/properties/delay", + "type": "integer", + "title": "The delay schema", + "description": "Provide delay in ms between two entry update", + "default": 1000, + "examples": [100] + }, + "failed-entries": { + "type": "array", + "default": [], + "title": "The failed-entries Schema", + "items": { + "type": "string", + "default": "", + "title": "A Schema", + "examples": [ + "blt1c37c346218e033e, blt6ed6d545cdc2064f, blt0594909c6f0a2e82, blt07fe0dc0d2f89e71, blt6d0d993a944947e8" + ] + }, + "examples": [ + ["blt1c37c346218e033e, blt6ed6d545cdc2064f, blt0594909c6f0a2e82, blt07fe0dc0d2f89e71, blt6d0d993a944947e8"] + ] + }, + "locale": { + "type": "array", + "default": [], + "title": "The locale Schema", + "items": { + "type": "string", + "default": "", + "title": "A Schema", + "examples": ["en-in"] + }, + "examples": [["en-in"]] + }, + "batch-limit": { + "type": "integer", + "default": 50, + "title": "The batch-limit Schema", + "examples": [30], + "maximum": 100, + "minimum": 1 + } + }, + "batch-limit": { + "type": "integer", + "default": 50, + "title": "The batch-limit Schema", + "examples": [30], + "maximum": 100, + "minimum": 1 + } + }, + "oneOf": [ + { + "type": "object", + "title": "stack-api-key", + "required": ["stack-api-key"] + }, + { + "type": "object", + "title": "alias", + "required": ["alias"] + } + ], + "additionalProperties": true +} diff --git a/packages/contentstack-migrate-rte/src/lib/util/index.js b/packages/contentstack-migrate-rte/src/lib/util/index.js new file mode 100644 index 000000000..c547950cb --- /dev/null +++ b/packages/contentstack-migrate-rte/src/lib/util/index.js @@ -0,0 +1,722 @@ +const { Command } = require('@contentstack/cli-command'); +const command = new Command(); +const { + isEmpty, + find, + get, + isArray, + isUndefined, + set, + flatten, + cloneDeep, + isNil, + isNull, + isPlainObject, +} = require('lodash'); +const Validator = require('jsonschema').Validator; +const configSchema = require('./config_schema.json'); +const { JSDOM } = require('jsdom'); +const collapseWithSpace = require('collapse-whitespace'); +const { htmlToJson } = require('@contentstack/json-rte-serializer'); +const nodePath = require('path'); +const { + cliux, + managementSDKClient, + isAuthenticated, + doesBranchExist, + pathValidator, + getChalk, +} = require('@contentstack/cli-utilities'); +const packageValue = require('../../../package.json'); +const isBlank = (variable) => { + return isNil(variable) || isEmpty(variable); +}; + +async function getStack(data) { + const stackOptions = {}; + const options = { + host: data.host, + application: `json-rte-migration/${packageValue.version}`, + timeout: 120000, + }; + if (data.token) { + const tokenDetails = data.token; + stackOptions['api_key'] = tokenDetails.apiKey; + options['management_token'] = tokenDetails.token; // need to pass management token so that the sdk doesn't get configured with authtoken (throws error in case of oauth, if the provided stack doesn't belong to the org selected while logging in with oauth) + stackOptions['management_token'] = tokenDetails.token; + } + if (data.stackApiKey) { + if (!isAuthenticated()) { + throw new Error( + 'Please login to proceed further. Or use `--alias` instead of `--stack-api-key` to proceed without logging in.', + ); + } + stackOptions['api_key'] = data.stackApiKey; + } + if (data.branch) options.branchName = data.branch; + const client = await managementSDKClient(options); + const stack = client.stack(stackOptions); + + stack.host = data.host; + if (data.branch) { + let branchData = await doesBranchExist(stack, data.branch); + if (branchData && branchData.errorCode) { + throw new Error(branchData.errorMessage); + } + } + return stack; +} + +const deprecatedFields = { + configPath: 'config-path', + content_type: 'content-type', + isGlobalField: 'global-field', + htmlPath: 'html-path', + jsonPath: 'json-path', +}; +function normalizeFlags(config) { + let normalizedConfig = cloneDeep(config); + Object.keys(deprecatedFields).forEach((key) => { + if (normalizedConfig.hasOwnProperty(key)) { + normalizedConfig[deprecatedFields[key]] = normalizedConfig[key]; + delete normalizedConfig[key]; + } + }); + return normalizedConfig; +} + +const customBar = cliux.progress({ + format: '{title} ' + '| {bar} | {value}/{total} Entries', + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + stream: process.stdout, +}); +async function getConfig(flags) { + try { + let config; + if (flags['config-path']) { + const configPath = flags['config-path']; + config = require(pathValidator(configPath)); + } else { + config = { + 'content-type': flags['content-type'], + 'global-field': flags['global-field'], + paths: [ + { + from: flags['html-path'] || flags.htmlPath, + to: flags['json-path'] || flags.jsonPath, + }, + ], + delay: flags.delay, + 'batch-limit': flags['batch-limit'], + }; + if (flags.locale) { + config.locale = [flags.locale]; + } + if (flags.branch) { + config.branch = flags['branch']; + } + if (flags.alias) { + config.alias = flags.alias; + } + if (flags['stack-api-key']) { + config['stack-api-key'] = flags['stack-api-key']; + } + } + if (checkConfig(config)) { + let confirmed = await confirmConfig(config, flags.yes); + if (confirmed) { + return config; + } + throw new Error('User aborted the command.'); + } + } catch (error) { + if (error.code === 'ENOENT' || error.code === 'MODULE_NOT_FOUND') { + throw new Error('The specified path to config file does not exist.'); + } + if (error.schema && error.errors && error.errors[0]) { + throwConfigError(error.errors[0]); + } + throw error; + } +} +function getToken(alias) { + try { + return command.getToken(alias); + } catch (error) { + throw new Error('Invalid alias provided for the management token.'); + } +} +function getContentType(stack, contentTypeUid) { + return stack + .contentType(contentTypeUid) + .fetch({ include_global_field_schema: true }) + .then((content) => content) + .catch((error) => { + throw new Error(error.errorMessage || error.message); + }); +} +function getGlobalField(stack, globalFieldUid) { + return stack + .globalField(globalFieldUid) + .fetch({ include_content_types: true }) + .then((content) => content) + .catch((error) => { + throw new Error(error.errorMessage || error.message); + }); +} +function throwConfigError(error) { + const { name, path, argument } = error; + let fieldName = path.join('.'); + if (fieldName === '') { + fieldName = argument || 'Config'; + } + if (name === 'required') { + throw new Error(`${fieldName} is mandatory while defining config.`); + } else if (name === 'type') { + throw new Error(`Invalid key type. ${fieldName} must be of ${argument[0] || 'string'} type(s).`); + } else if (name === 'minimum' || name === 'maximum') { + throw new Error(`${fieldName} must be between 1 and 100.`); + } +} +function checkConfig(config) { + let v = new Validator(); + let res = v.validate(config, configSchema, { throwError: true, nestedErrors: true }); + return res.valid; +} +function prettyPrint(data) { + const chalk = getChalk(); + console.log(chalk.yellow('Configuration to be used for executing this command:')); + console.log(chalk.grey(JSON.stringify(data, null, 2))); + console.log('\n'); +} +async function confirmConfig(config, skipConfirmation) { + if (skipConfirmation) { + return Promise.resolve(true); + } + prettyPrint(config); + return await cliux.confirm('Do you want to continue with this configuration ? [yes or no]'); +} +const delay = (ms) => new Promise((res) => setTimeout(res, ms)); + +async function updateEntriesInBatch(contentType, config, skip = 0, retry = 0, locale = undefined) { + let title = `Migrating entries for ${contentType.uid}`; + let extraParams = {}; + if (locale) { + extraParams.locale = locale; + extraParams.query = { locale: locale }; + } + if (config['failed-entries'] && config['failed-entries'].length > 0) { + title = `Migrating failed entries for ${contentType.uid}`; + if (extraParams.query) { + extraParams.query['uid'] = { $in: config['failed-entries'] }; + } else { + extraParams = { query: { uid: { $in: config['failed-entries'] } } }; + } + } + let entryQuery = { + include_count: true, + ...extraParams, + skip: skip, + limit: config['batch-limit'] || 50, + }; + try { + await contentType + .entry() + .query(entryQuery) + .find() + .then(async (entriesResponse) => { + try { + customBar.start(entriesResponse.count, skip, { + title: title, + }); + } catch (error) {} + skip += entriesResponse.items.length; + let entries = entriesResponse.items; + + for (const entry of entries) { + try { + customBar.increment(); + } catch (error) {} + await updateSingleEntry(entry, contentType, config); + await delay(config.delay || 1000); + } + if (skip === entriesResponse.count) { + return Promise.resolve(); + } + await updateEntriesInBatch(contentType, config, skip, 0, locale); + }); + } catch (error) { + console.error(`Error while fetching batch of entries: ${error.message}`); + if (retry < 3) { + retry += 1; + console.error(`Retrying again in 5 seconds... (${retry}/3)`); + await delay(5000); + await updateEntriesInBatch(contentType, config, skip, retry, locale); + } else { + throw new Error(`Max retry exceeded: Error while fetching batch of entries: ${error.message}`); + } + } +} +async function updateSingleContentTypeEntries(stack, contentTypeUid, config) { + let contentType = await getContentType(stack, contentTypeUid); + let schema = contentType.schema; + for (const path of config.paths) { + if (!isEmpty(schema)) { + isPathValid(contentType.schema, path); + } else { + throw new Error(`The ${contentTypeUid} content type contains an empty schema.`); + } + } + if (config.locale && isArray(config.locale) && config.locale.length > 0) { + const locales = config.locale; + for (const locale of locales) { + console.log(`\nMigrating entries for "${contentTypeUid}" Content-type in "${locale}" locale`); + await updateEntriesInBatch(contentType, config, 0, 0, locale); + await delay(config.delay || 1000); + } + } else { + await updateEntriesInBatch(contentType, config); + } + config.contentTypeCount += 1; + try { + customBar.stop(); + } catch (error) {} +} +async function updateSingleContentTypeEntriesWithGlobalField(contentType, config) { + let schema = contentType.schema; + for (const path of config.paths) { + isPathValid(schema, path); + } + if (config.locale && isArray(config.locale) && config.locale.length > 0) { + const locales = config.locale; + for (const locale of locales) { + console.log(`\nMigrating entries for ${contentType.uid} in locale ${locale}`); + await updateEntriesInBatch(contentType, config, 0, 0, locale); + await delay(config.delay || 1000); + } + } else { + await updateEntriesInBatch(contentType, config); + } + config.contentTypeCount += 1; +} +async function updateSingleEntry(entry, contentType, config) { + let schema = contentType.schema; + let paths = config.paths; + let entryUploadPath = uploadPaths(schema); + entryUploadPath = Object.keys(entryUploadPath); + for (const path of paths) { + let htmlPath = path.from.split('.'); + let jsonPath = path.to.split('.'); + let htmlRteUid = htmlPath[htmlPath.length - 1]; + let jsonRteUid = jsonPath[jsonPath.length - 1]; + let parentPath = htmlPath.slice(0, htmlPath.length - 1).join('.'); + setEntryData(parentPath, entry, schema, { htmlRteUid, jsonRteUid }); + } + try { + for (const filePath of entryUploadPath) { + let fileFieldPath = filePath.split('.'); + let fileUid = fileFieldPath[fileFieldPath.length - 1]; + let parentFileFieldPath = fileFieldPath.slice(0, fileFieldPath.length - 1).join('.'); + unsetResolvedUploadData(parentFileFieldPath, entry, schema, { fileUid }); + } + } catch (error) { + console.error(`Error while unsetting resolved upload data: ${error.message}`); + } + await handleEntryUpdate(entry, config, 0); +} +async function handleEntryUpdate(entry, config, retry = 0) { + const chalk = getChalk(); + try { + await entry.update({ locale: entry.locale }); + config.entriesCount += 1; + } catch (error) { + console.log(chalk.red(`Error while updating '${entry.uid}' entry`)); + if (error.errors && isPlainObject(error.errors)) { + const errVal = Object.entries(error.errors); + errVal.forEach(([key, vals]) => { + console.log(chalk.red(` ${key}:- ${vals.join(',')}`)); + }); + } else { + console.log(chalk.red(`Error stack: ${error}`)); + } + if (retry < 3) { + retry += 1; + console.log(`Retrying again in 5 seconds... (${retry}/3)`); + await delay(5000); + await handleEntryUpdate(entry, config, retry); + } else { + if ( + config && + config.errorEntriesUid && + config.errorEntriesUid[entry.content_type_uid] && + config.errorEntriesUid[entry.content_type_uid][entry.locale] + ) { + config.errorEntriesUid[entry.content_type_uid][entry.locale].push(entry.uid); + } else { + set(config, ['errorEntriesUid', entry.content_type_uid, entry.locale], [entry.uid]); + } + } + } +} +function traverseSchemaForField(schema, path, field_uid) { + let paths = path.split('.'); + if (paths.length === 1) { + let field = find(schema, (o) => { + return o.uid === paths[0]; + }); + if (Boolean(field) && field.uid === field_uid) { + return field; + } + } else { + let fieldUid = paths.shift(); + let fieldSchema = find(schema, { uid: fieldUid }); + if (!isEmpty(fieldSchema)) { + if (fieldSchema.data_type === 'group' || fieldSchema.data_type === 'global_field') { + return traverseSchemaForField(fieldSchema.schema, paths.join('.'), field_uid); + } + if (fieldSchema.data_type === 'blocks') { + let blockUid = paths.shift(); + let block = find(fieldSchema.blocks, { uid: blockUid }); + if (!isEmpty(block) && block.schema) { + return traverseSchemaForField(block.schema, paths.join('.'), field_uid); + } + } + } + } + return {}; +} +function isPathValid(schema, path) { + let pathFrom = path.from.split('.'); + let htmlParentPath = pathFrom.slice(0, pathFrom.length - 1).join('.'); + const rteUid = pathFrom[pathFrom.length - 1]; + let rteSchema = traverseSchemaForField(schema, path.from, rteUid); + if (isEmpty(rteSchema)) { + throw new Error(`The specified path to ${rteUid} HTML RTE does not exist.`); + } + let ishtmlRteMultiple = rteSchema.multiple || false; + if (rteSchema.field_metadata && rteSchema.field_metadata.allow_rich_text) { + let pathTo = path.to.split('.'); + let jsonParentPath = pathTo.slice(0, pathTo.length - 1).join('.'); + + const jsonUid = pathTo[pathTo.length - 1]; + let jsonSchema = traverseSchemaForField(schema, path.to, jsonUid); + if (isEmpty(jsonSchema)) { + throw new Error(`The specified path to ${jsonUid} JSON RTE does not exist.`); + } + let isJSONRteMultiple = jsonSchema.multiple || false; + + if (jsonSchema.field_metadata && jsonSchema.field_metadata.allow_json_rte) { + if (htmlParentPath === jsonParentPath) { + if (ishtmlRteMultiple === isJSONRteMultiple) { + return true; + } + throw new Error( + `Cannot convert "${ishtmlRteMultiple ? 'Multiple' : 'Single'}" type HTML RTE to "${ + isJSONRteMultiple ? 'Multiple' : 'Single' + }" type JSON RTE.`, + ); + } else { + throw new Error( + 'To complete migration, HTML RTE and JSON RTE should be present at the same field depth level.', + ); + } + } else { + throw new Error(`The specified path to ${jsonUid} JSON RTE does not exist.`); + } + } else { + throw new Error(`The specified path to ${rteUid} HTML RTE does not exist.`); + } +} +function setEntryData(path, entry, schema, fieldMetaData) { + let paths = path.split('.'); + if (paths.length === 1 && paths[0] === '') { + paths.shift(); + } + if (paths.length > 0) { + let field = find(schema, { + uid: paths[0], + }); + if (field) { + if (field.data_type === 'group' || field.data_type === 'global_field') { + paths.shift(); + + let sub_entry_data = get(entry, field.uid); + if (isArray(sub_entry_data)) { + for (const sub_data of sub_entry_data) { + setEntryData(paths.join('.'), sub_data, field.schema, fieldMetaData); + } + } else { + setEntryData(paths.join('.'), sub_entry_data, field.schema, fieldMetaData); + } + } else if (field.data_type === 'blocks') { + if (field.blocks) { + let ModularBlockUid = paths.shift(); + let blockUid = paths.shift(); + let blockField = find(field.blocks, { uid: blockUid }); + if (blockField) { + let modularBlockDetails = get(entry, ModularBlockUid) || []; + for (const blocks of modularBlockDetails) { + let blockdata = get(blocks, blockUid); + if (blockdata) { + setEntryData(paths.join('.'), blockdata, blockField.schema, fieldMetaData); + } + } + } + } + } + } + } else if (paths.length === 0) { + if (entry) { + const { htmlRteUid, jsonRteUid } = fieldMetaData; + const htmlValue = get(entry, htmlRteUid); + // check if html field exist in traversed path + if (!isUndefined(htmlValue)) { + // if Rte field is multiple + if (isArray(htmlValue)) { + for (let i = 0; i < htmlValue.length; i++) { + let html = htmlValue[i]; + setJsonValue(html, entry, `${jsonRteUid}.${i}`); + } + } else { + setJsonValue(htmlValue, entry, jsonRteUid); + } + } + } + } +} + +function unsetResolvedUploadData(path, entry, schema, fieldMetaData) { + let paths = path.split('.'); + if (paths.length === 1 && paths[0] === '') { + paths.shift(); + } + if (paths.length > 0) { + let field = find(schema, { + uid: paths[0], + }); + if (field) { + if (field.data_type === 'group' || field.data_type === 'global_field') { + paths.shift(); + + let sub_entry_data = get(entry, field.uid); + if (isArray(sub_entry_data)) { + for (const sub_data of sub_entry_data) { + unsetResolvedUploadData(paths.join('.'), sub_data, field.schema, fieldMetaData); + } + } else { + unsetResolvedUploadData(paths.join('.'), sub_entry_data, field.schema, fieldMetaData); + } + } else if (field.data_type === 'blocks') { + if (field.blocks) { + let ModularBlockUid = paths.shift(); + let blockUid = paths.shift(); + let blockField = find(field.blocks, { uid: blockUid }); + if (blockField) { + let modularBlockDetails = get(entry, ModularBlockUid) || []; + for (const blocks of modularBlockDetails) { + let blockdata = get(blocks, blockUid); + if (blockdata) { + unsetResolvedUploadData(paths.join('.'), blockdata, blockField.schema, fieldMetaData); + } + } + } + } + } + } + } else if (paths.length === 0) { + if (entry) { + const { fileUid } = fieldMetaData; + const fieldValue = get(entry, fileUid); + if (!isUndefined(fieldValue) && !isNull(fieldValue)) { + if (isArray(fieldValue)) { + for (let i = 0; i < fieldValue.length; i++) { + const singleFile = fieldValue[i]; + if (!isUndefined(singleFile.uid)) { + set(entry, `${fileUid}.${i}`, singleFile.uid); + } + } + } else if (!isUndefined(fieldValue.uid)) { + set(entry, fileUid, fieldValue.uid); + } + } + } + } +} +function setJsonValue(html, entry, path) { + let doc = convertHtmlToJson(html); + set(entry, path, doc); +} +function convertHtmlToJson(html) { + const dom = new JSDOM(html); + let htmlDoc = dom.window.document.querySelector('body'); + collapseWithSpace(htmlDoc); + let doc; + try { + doc = htmlToJson(htmlDoc); + applyDirtyAttributesToBlock(doc); + } catch (error) { + throw new Error('Error while converting html '.concat(error.message)); + } + return doc; +} +function applyDirtyAttributesToBlock(block) { + if (block.hasOwnProperty('text')) { + return block; + } + let children = flatten([...(block.children || [])].map(applyDirtyAttributesToBlock)); + if (block.hasOwnProperty('type')) { + set(block, 'attrs.dirty', true); + } + block.children = children; + return block; +} +async function updateContentTypeForGlobalField(stack, global_field, config) { + const globalField = await getGlobalField(stack, global_field); + if (isEmpty(globalField.schema)) { + throw new Error(`The ${global_field} Global field contains an empty schema.`); + } + let allReferredContentTypes = globalField.referred_content_types; + if (!isEmpty(allReferredContentTypes)) { + for (const contentType of allReferredContentTypes) { + let contentTypeInstance = await getContentType(stack, contentType.uid); + const schema = contentTypeInstance.schema; + if (!isEmpty(schema) && !isUndefined(schema)) { + let globalFieldPaths = getGlobalFieldPath(contentTypeInstance.schema, global_field); + let newConfig = cloneDeep(config); + updateMigrationPath(globalFieldPaths, newConfig); + await updateSingleContentTypeEntriesWithGlobalField(contentTypeInstance, newConfig); + config.contentTypeCount = newConfig.contentTypeCount; + config.entriesCount = newConfig.entriesCount; + config.errorEntriesUid = newConfig.errorEntriesUid; + } else { + throw new Error(`The ${contentType.uid} content type referred in ${globalField.uid} contains an empty schema.`); + } + } + try { + customBar.stop(); + } catch (error) {} + } else { + throw new Error(`${globalField.uid} Global field is not referred in any content type.`); + } +} +function updateMigrationPath(globalFieldPaths, config) { + const newPath = []; + for (const path of config.paths) { + for (const globalFieldPath of globalFieldPaths) { + newPath.push({ from: globalFieldPath + '.' + path.from, to: globalFieldPath + '.' + path.to }); + } + } + config.paths = newPath; +} +function getGlobalFieldPath(schema, globalFieldUid) { + let paths = []; + + function genPath(prefix, path) { + return isEmpty(prefix) ? path : [prefix, path].join('.'); + } + + function traverse(fields, path) { + path = path || ''; + for (const field of fields) { + let currPath = genPath(path, field.uid); + if (field.data_type === 'group') { + traverse(field.schema, currPath); + } + + if ( + field.data_type === 'global_field' && + isUndefined(field.schema) === false && + isEmpty(field.schema) === false + ) { + if (field.reference_to === globalFieldUid) { + paths.push(currPath); + } + } + if (field.data_type === 'blocks') { + field.blocks.forEach(function (block) { + if (block.schema) { + if (block.reference_to && block.reference_to === globalFieldUid) { + paths.push(currPath + '.' + block.uid); + } + traverse(block.schema, currPath + '.' + block.uid); + } + }); + } + // experience_container + if (field.data_type === 'experience_container') { + if (field.variations) { + field.variations.forEach(function (variation) { + if (variation.schema) traverse(variation.schema, currPath + '.' + variation.uid); + }); + } + } + } + } + + if (!isEmpty(schema)) { + traverse(schema, ''); + } + + return paths; +} + +/* + Get the upload paths + */ +function uploadPaths(schema) { + return getPaths(schema, 'file'); +} + +/* +Generic function to get schema paths +*/ +function getPaths(schema, type) { + const paths = {}; + + function genPath(prefix, path) { + return isBlank(prefix) ? path : [prefix, path].join('.'); + } + + function traverse(fields, path) { + path = path || ''; + for (const element of fields) { + const field = element; + const currPath = genPath(path, field.uid); + + if (field.data_type === type) paths[currPath] = true; + + if (field.data_type === 'group') traverse(field.schema, currPath); + + if (field.data_type === 'global_field' && isUndefined(field.schema) === false && isEmpty(field.schema) === false) + traverse(field.schema, currPath); + if (field.data_type === 'blocks') { + field.blocks.forEach(function (block) { + if (block.schema) traverse(block.schema, currPath + '.' + block.uid); + }); + } + // experience_container + if (field.data_type === 'experience_container') { + field.variations.forEach(function (variation) { + if (variation.schema) traverse(variation.schema, currPath + '.' + variation.uid); + }); + } + } + } + + traverse(schema); + + return paths; +} + +module.exports = { + getStack, + getConfig, + getToken, + getContentType, + updateEntriesInBatch, + updateSingleContentTypeEntries, + updateContentTypeForGlobalField, + command, + normalizeFlags, +}; diff --git a/packages/contentstack-migrate-rte/test/commands/json-migration.test.js b/packages/contentstack-migrate-rte/test/commands/json-migration.test.js new file mode 100644 index 000000000..ec2b3f14d --- /dev/null +++ b/packages/contentstack-migrate-rte/test/commands/json-migration.test.js @@ -0,0 +1,1286 @@ +const { runCommand } = require("@oclif/test"); +const sinon = require("sinon"); +const qs = require("querystring"); +const nock = require("nock"); +const { cliux, managementSDKClient } = require("@contentstack/cli-utilities"); +const { expect } = require("chai"); +const { fancy } = require("fancy-test"); +const { + getToken, + getContentType, + getEntries, + getExpectedOutput, + getGlobalField, + getEntriesOnlyUID, + getEntry, +} = require("../utils"); +const omitDeep = require("omit-deep-lodash"); +const { isEqual, cloneDeep } = require("lodash"); +const { command } = require("../../src/lib/util"); + +// Helper function to set up common nock mocks for content type and entry operations +function setupCommonNockMocks(testApiUrl) { + nock.cleanAll(); + + // Stub cmaAPIUrl + sinon.stub(command, "cmaAPIUrl").value(testApiUrl); + + // Mock content type fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+$/) + .query({ + include_global_field_schema: true, + }) + .reply((uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)/); + return getContentType(match[1]); + }); + + // Mock entries UID list + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query({ + include_count: true, + skip: 0, + limit: 100, + "only[Base][]": "uid", + }) + .reply(200, (uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/); + return getEntriesOnlyUID(match[1]); + }); + + // Mock entries fetch with locale support + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query(true) + .reply(200, function (uri) { + let query = this.req.options.search; + query = query.substring(1); + let locale = undefined; + query = qs.parse(query); + if (query.locale) { + locale = query.locale; + } + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/); + return getEntries(match[1], locale); + }); + + // Mock get locale + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+\/locale/) + .query({ + deleted: false, + }) + .reply(200, () => { + return { + locales: [ + { + code: "en-in", + localized: true, + }, + { + code: "en-us", + }, + ], + }; + }); + + // Mock single entry fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+$/) + .query(true) + .reply(200, (uri) => { + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)/ + ); + return getEntry(match[1], match[2]); + }); + + // Mock entry update + nock(testApiUrl) + .persist() + .put(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+/) + .reply((uri, body) => { + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)\?locale=([a-zA-Z0-9_-]+)/ + ); + if (!match) return [400, { error_message: "Invalid URL format" }]; + + const responseModified = cloneDeep(omitDeep(body, ["uid"])); + const expectedResponse = omitDeep( + getExpectedOutput(match[1], match[2], match[3]), + ["uid"] + ); + + if (isEqual(responseModified, expectedResponse)) { + return [ + 200, + { + notice: "Entry updated successfully.", + entry: {}, + }, + ]; + } + return [ + 400, + { + notice: "Update Failed.", + error_message: "Entry update failed.", + entry: {}, + }, + ]; + }); +} + +describe("Migration Config validation", () => { + const getTokenCallback = sinon.stub(); + + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + getTokenCallback + .withArgs("invalidAlias") + .throws(new Error("Token with alias 'invalidAlias' was not found")); + + fancy + .stub(cliux, "confirm", () => false) + .it("deny config confirmation", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain("User aborted the command."); + }); + + fancy + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("throw error on Empty paths", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/configWithEmptyPath.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + 'No value provided for the "paths" property in config.' + ); + }); + + fancy + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid config type", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "../test/dummy/config/invalidConfig.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to config file does not exist." + ); + }); + + fancy + .stub(cliux, "confirm", () => true) + .it("throw error on config without alias property", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + 'is not exactly one from "stack-api-key","alias"' + ); + }); + + // skipped on purpose + fancy + .skip() + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalidAlias", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "invalidAlias", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--delay", + "50", + ], + { root: process.cwd() } + ); + + console.log("ACTUAL ERROR:", error); + + expect(error.message).to.contain( + "Invalid alias provided for the management token." + ); + }); + + fancy + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid config file", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/configWithInvalidPath.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to config file does not exist." + ); + }); +}); + +describe("Content Type with Single RTE Field of Single Type", function () { + this.timeout(1000000); + let token = getToken("test1"); + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + // Restore all stubs/nocks from previous tests to avoid cross-test pollution + nock.cleanAll(); + + // Stub cmaAPIUrl to avoid region configuration requirement + sinon.stub(command, "cmaAPIUrl").value(testApiUrl); + + // mock content type + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .get(/\/v3\/content_types\/(\w)*/) + .query({ + include_global_field_schema: true, + }) + .reply((uri) => { + const match = uri.match(/\/v3\/content_types\/((\w)*)/); + return getContentType(match[1]); + }); + + // mock entries UID list + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .get(/\/v3\/content_types\/((\w)*)\/entries/) + .query({ + include_count: true, + skip: 0, + limit: 100, + "only[Base][]": "uid", + }) + .reply(200, (uri) => { + const match = uri.match(/\/v3\/content_types\/((\w)*)\/entries/); + return getEntriesOnlyUID(match[1]); + }); + + // mock entries fetch with locale support + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .get(/\/v3\/content_types\/((\w)*)\/entries/) + .query(true) + .reply(200, function (uri) { + let search = this.req.options.search || ""; + if (search.startsWith("?")) search = search.slice(1); + + let locale; + const query = qs.parse(search); + if (query.locale) { + locale = query.locale; + } + + const match = uri.match(/\/v3\/content_types\/((\w)*)\/entries/); + return getEntries(match[1], locale); + }); + + // mock get locale + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .get(/\/v3\/content_types\/((\w)*)\/entries\/((\w)*)\/locale/) + .query({ + deleted: false, + }) + .reply(200, () => { + return { + locales: [{ code: "en-in", localized: true }, { code: "en-us" }], + }; + }); + + // mock single entry fetch + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .get(/\/v3\/content_types\/((\w)*)\/entries\/((\w)*)/) + .query(true) + .reply(200, function (uri) { + let search = this.req.options.search || ""; + if (search.startsWith("?")) search = search.slice(1); + + const query = qs.parse(search); + const match = uri.match( + /\/v3\/content_types\/((\w)*)\/entries\/((\w)*)/ + ); + const contentTypeUid = match[1]; + const entryUid = match[3]; + + if (query.locale) { + return getEntry(contentTypeUid, entryUid, query.locale); + } + return getEntry(contentTypeUid, entryUid); + }); + + // mock entry update + nock(testApiUrl, { + reqheaders: { + api_key: token.apiKey, + authorization: token.token, + }, + }) + .persist() + .put(/\/v3\/content_types\/((\w)*)\/entries/) + .reply((uri, body) => { + const match = uri.match( + /\/v3\/content_types\/((\w)*)\/entries\/((\w)*)\?locale=((\w|-)*)/ + ); + const responseModified = cloneDeep(omitDeep(body, ["uid"])); + let expectedResponse = omitDeep( + getExpectedOutput(match[1], match[3], match[5]), + ["uid"] + ); + expectedResponse = cloneDeep(expectedResponse); + + if (isEqual(responseModified, expectedResponse)) { + return [ + 200, + { + notice: "Entry updated successfully.", + entry: {}, + }, + ]; + } + + return [ + 400, + { + notice: "Update Failed.", + error_message: "Entry update failed.", + entry: {}, + }, + ]; + }); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using config file w/o locale", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/config.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 2 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using config file w/ locale", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/config_locale.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using config file w/ multiple locale", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/config-locale-2.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 3 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using flags (w/o locale)", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 2 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using flags w/ locale", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--locale", + "en-in", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid html rte path", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor.invalidPath", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to invalidPath HTML RTE does not exist." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid html rte field schema", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithinvalidhtmlrteschema", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to rich_text_editor HTML RTE does not exist." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid json rte field schema", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithinvalidjsonrteschema", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to supercharged_rte JSON RTE does not exist." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid json rte path", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithsinglerte", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte.invalidPath", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The specified path to invalidPath JSON RTE does not exist." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it( + "throw error on migration of Mutiple Html rte with single Json rte", + async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/configForInvalidContentType.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + 'Cannot convert "Multiple" type HTML RTE to "Single" type JSON RTE.' + ); + } + ); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on content type with empty schema", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithemptyschema", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The contenttypewithemptyschema content type contains an empty schema." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on different level rte migration", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypedifferentlevelrte", + "--html-path", + "group.rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "To complete migration, HTML RTE and JSON RTE should be present at the same field depth level." + ); + }); + + fancy + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid contenttype", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "invalidContentType", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The Content Type 'invalidContentType' was not found. Please try again." + ); + }); + + fancy + .skip() + .stub(cliux, "confirm", () => true) + .stub(command, "getToken", getTokenCallback) + .it("notify user on entry update failed", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithentryupdateerror", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain( + `Faced issue while migrating some entrie(s) for "contenttypewithentryupdateerror" Content-type in "en-us" locale,"blta9b16ac2827c54ed, blta9b16ac2827c54e1"` + ); + }); + + fancy + .skip() + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it( + "should have proper json structure for images migrated from HTML RTE", + async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/config-for-images-in-rte.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.match( + /Updated \d+ Content Type\(s\) and \d+ Entrie\(s\)/ + ); + } + ); +}); + +describe("Global Field Migration", () => { + let token = getToken("test1"); + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + nock.cleanAll(); + + // Stub cmaAPIUrl to avoid region configuration requirement + sinon.stub(command, "cmaAPIUrl").value(testApiUrl); + + // Mock global field fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/global_fields\/[a-zA-Z0-9_]+/) + .query({ + include_content_types: true, + }) + .reply((uri) => { + const match = uri.match(/\/v3\/global_fields\/([a-zA-Z0-9_]+)/); + return getGlobalField(match[1]); + }); + + // Mock content type fetch (needed when processing global field references) + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+$/) + .query({ + include_global_field_schema: true, + }) + .reply((uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)/); + return getContentType(match[1]); + }); + + // Mock entries UID list + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query({ + include_count: true, + skip: 0, + limit: 100, + "only[Base][]": "uid", + }) + .reply(200, (uri) => { + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/ + ); + return getEntriesOnlyUID(match[1]); + }); + + // Mock entries fetch with locale support + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query(true) + .reply(200, function (uri) { + let query = this.req.options.search; + query = query.substring(1); + let locale = undefined; + query = qs.parse(query); + if (query.locale) { + locale = query.locale; + } + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/ + ); + return getEntries(match[1], locale); + }); + + // Mock get locale + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+\/locale/) + .query({ + deleted: false, + }) + .reply(200, () => { + return { + locales: [ + { + code: "en-in", + localized: true, + }, + { + code: "en-us", + }, + ], + }; + }); + + // Mock single entry fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+$/) + .query(true) + .reply(200, (uri) => { + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)/ + ); + return getEntry(match[1], match[3]); + }); + + // Mock entry update + nock(testApiUrl) + .persist() + .put(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+/) + .reply((uri, body) => { + const match = uri.match( + /\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)\?locale=([a-zA-Z0-9_-]+)/ + ); + if (!match) return [400, { error_message: "Invalid URL format" }]; + + const responseModified = cloneDeep(omitDeep(body, ["uid"])); + const expectedResponse = omitDeep( + getExpectedOutput(match[1], match[2], match[3]), + ["uid"] + ); + + if (isEqual(responseModified, expectedResponse)) { + return [ + 200, + { + notice: "Entry updated successfully.", + entry: {}, + }, + ]; + } + return [ + 400, + { + notice: "Update Failed.", + error_message: "Entry update failed.", + entry: {}, + }, + ]; + }); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using config file", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/configForGlobalField.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 2 Content Type(s) and 2 Entrie(s)"); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it( + "throw error on global field with empty referred content_types", + async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "globalfieldwithemptycontenttype", + "--global-field", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "globalfieldformigration Global field is not referred in any content type." + ); + } + ); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on global field with invalid content_type", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "globalfieldwithinvalidcontenttype", + "--global-field", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The contenttypewithemptyschema content type referred in globalfieldformigration contains an empty schema." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on global field with empty schema", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "globalfieldwithemptyschema", + "--global-field", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The globalfieldwithemptyschema Global field contains an empty schema." + ); + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it( + "throw error on global field with empty schema content_type", + async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "globalfieldwithemptyschemacontenttype", + "--global-field", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The contenttypewithemptyschema content type referred in globalfieldwithemptyschemacontenttype contains an empty schema." + ); + } + ); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("throw error on invalid global_field uid", async () => { + const { error } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "invalidUidGlobalfield", + "--global-field", + "--html-path", + "rich_text_editor", + "--json-path", + "supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(error.message).to.contain( + "The Global Field 'invalidUidGlobalfield' was not found. Please try again." + ); + }); +}); + +describe("Content Type with single rte of multiple type", () => { + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + setupCommonNockMocks(testApiUrl); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using config file", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--config-path", + "./test/dummy/config/configForMultipleRte.json", + "--yes", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); +}); + +describe("Content Type with Single RTE inside modular block", () => { + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + setupCommonNockMocks(testApiUrl); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using Flags", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithmodularblock", + "--html-path", + "modular_blocks.test1.rich_text_editor", + "--json-path", + "modular_blocks.test1.supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); +}); + +describe("Content Type with Single RTE of type multiple inside group", () => { + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + setupCommonNockMocks(testApiUrl); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using Flags", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithgroup", + "--html-path", + "group.rich_text_editor", + "--json-path", + "group.supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); +}); + +describe("Content Type with Single RTE inside group of type multiple", () => { + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + setupCommonNockMocks(testApiUrl); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using Flags", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithmultiplegroup", + "--html-path", + "group.rich_text_editor", + "--json-path", + "group.supercharged_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); +}); + +describe("Content Type with multiple file field", () => { + let testApiUrl = "https://api.contentstack.io"; + + beforeEach(() => { + setupCommonNockMocks(testApiUrl); + }); + + const getTokenCallback = sinon.stub(); + getTokenCallback.withArgs("test1").returns({ + token: "testManagementToken", + apiKey: "testApiKey", + type: "management", + }); + + fancy + .stub(cliux, "confirm", () => "yes") + .stub(command, "getToken", getTokenCallback) + .it("execute using Flags", async () => { + const { stdout } = await runCommand( + [ + "cm:entries:migrate-html-rte", + "--alias", + "test1", + "--content-type", + "contenttypewithfilefield", + "--html-path", + "rich_text_editor", + "--json-path", + "json_rte", + "--yes", + "--delay", + "50", + ], + { root: process.cwd() } + ); + expect(stdout).to.contain("Updated 1 Content Type(s) and 1 Entrie(s)"); + }); +}); diff --git a/packages/contentstack-migrate-rte/test/dummy/config/config-for-images-in-rte.json b/packages/contentstack-migrate-rte/test/dummy/config/config-for-images-in-rte.json new file mode 100644 index 000000000..4b12389cc --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/config-for-images-in-rte.json @@ -0,0 +1,12 @@ +{ + "alias": "test1", + "content-type": "rte_images", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/config-locale-2.json b/packages/contentstack-migrate-rte/test/dummy/config/config-locale-2.json new file mode 100644 index 000000000..940501161 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/config-locale-2.json @@ -0,0 +1,13 @@ +{ + "alias": "test1", + "content-type": "contenttypewithsinglerte", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50, + "locale": ["en-in", "en-us"] +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/config-locale.json b/packages/contentstack-migrate-rte/test/dummy/config/config-locale.json new file mode 100644 index 000000000..a54e527c8 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/config-locale.json @@ -0,0 +1,13 @@ +{ + "alias": "test1", + "content_type": "contenttypewithsinglerte", + "isGlobalField": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50, + "locale":["en-in"] +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/dummy/config/config.json b/packages/contentstack-migrate-rte/test/dummy/config/config.json new file mode 100644 index 000000000..337aa4d3a --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/config.json @@ -0,0 +1,12 @@ +{ + "alias": "test1", + "content-type": "contenttypewithsinglerte", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/configForGlobalField.json b/packages/contentstack-migrate-rte/test/dummy/config/configForGlobalField.json new file mode 100644 index 000000000..80c572f48 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/configForGlobalField.json @@ -0,0 +1,12 @@ +{ + "alias": "test1", + "content-type": "globalfieldformigration", + "global-field": true, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/configForInvalidContentType.json b/packages/contentstack-migrate-rte/test/dummy/config/configForInvalidContentType.json new file mode 100644 index 000000000..3506c60cc --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/configForInvalidContentType.json @@ -0,0 +1,12 @@ +{ + "alias": "test1", + "content-type": "contenttypewithinvalidrtetype", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/configForMultipleRte.json b/packages/contentstack-migrate-rte/test/dummy/config/configForMultipleRte.json new file mode 100644 index 000000000..a17c5bb5b --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/configForMultipleRte.json @@ -0,0 +1,12 @@ +{ + "alias": "test1", + "content-type": "contenttypewithmultiplerte", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/configWithEmptyPath.json b/packages/contentstack-migrate-rte/test/dummy/config/configWithEmptyPath.json new file mode 100644 index 000000000..0361c3819 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/configWithEmptyPath.json @@ -0,0 +1,7 @@ +{ + "alias": "test2", + "content-type": "contenttypewithsinglerte", + "global-field": false, + "paths": [], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/config_locale.json b/packages/contentstack-migrate-rte/test/dummy/config/config_locale.json new file mode 100644 index 000000000..ffd445346 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/config_locale.json @@ -0,0 +1,13 @@ +{ + "alias": "test1", + "content-type": "contenttypewithsinglerte", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "locale":["en-in"], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/config/invalidConfig.json b/packages/contentstack-migrate-rte/test/dummy/config/invalidConfig.json new file mode 100644 index 000000000..2f939adc0 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/config/invalidConfig.json @@ -0,0 +1,12 @@ +{ + "alias": 123, + "content-type": "contenttypewithsinglerte", + "global-field": false, + "paths": [ + { + "from": "rich_text_editor", + "to": "supercharged_rte" + } + ], + "delay": 50 +} diff --git a/packages/contentstack-migrate-rte/test/dummy/contentTypeResponse.json b/packages/contentstack-migrate-rte/test/dummy/contentTypeResponse.json new file mode 100644 index 000000000..16f5e630e --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/contentTypeResponse.json @@ -0,0 +1,3014 @@ +{ + "contenttypewithsinglerte": { + "content_type": { + "created_at": "2021-06-18T08:28:40.550Z", + "updated_at": "2021-06-18T08:29:11.162Z", + "title": "ContentTypeWithSingleRTE", + "uid": "contenttypewithsinglerte", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithinvalidjsonrteschema": { + "content_type": { + "created_at": "2021-06-18T08:28:40.550Z", + "updated_at": "2021-06-18T08:29:11.162Z", + "title": "ContentTypeWithSingleRTE", + "uid": "contenttypewithsinglerte", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypedifferentlevelrte": { + "content_type": { + "created_at": "2021-06-22T11:35:00.339Z", + "updated_at": "2021-06-22T11:35:19.016Z", + "title": "contenttypedifferentlevelrte", + "uid": "contenttypedifferentlevelrte", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "group", + "display_name": "Group", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + } + ], + "uid": "group", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithinvalidhtmlrteschema": { + "content_type": { + "created_at": "2021-06-18T08:28:40.550Z", + "updated_at": "2021-06-18T08:29:11.162Z", + "title": "contenttypewithinvalidhtmlrteschema", + "uid": "contenttypewithinvalidhtmlrteschema", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttype1forglobalfield": { + "content_type": { + "created_at": "2021-06-21T09:35:10.023Z", + "updated_at": "2021-06-21T09:35:58.489Z", + "title": "contentType1ForglobalField", + "uid": "contenttype1forglobalfield", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "global_field", + "display_name": "Global", + "reference_to": "globalfieldformigration", + "field_metadata": { + "description": "" + }, + "uid": "global_field", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ] + }, + { + "data_type": "group", + "display_name": "Group", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "global_field", + "display_name": "Global", + "reference_to": "globalfieldformigration", + "field_metadata": { + "description": "" + }, + "uid": "global_field", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ] + } + ], + "uid": "group", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttype2forglobalfield": { + "content_type": { + "created_at": "2021-06-21T09:36:41.354Z", + "updated_at": "2021-06-21T09:37:19.970Z", + "title": "contenttype2forglobalfield", + "uid": "contenttype2forglobalfield", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "global_field", + "display_name": "Global", + "reference_to": "globalfieldformigration", + "field_metadata": { + "description": "" + }, + "uid": "global_field", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ] + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithinvalidrtetype": { + "content_type": { + "created_at": "2021-06-22T05:00:12.357Z", + "updated_at": "2021-06-22T05:01:53.175Z", + "title": "contenttypewithinvalidrtetype", + "uid": "contenttypewithinvalidrtetype", + "_version": 3, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "multiple": true, + "non_localizable": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithmultiplerte": { + "content_type": { + "created_at": "2021-06-22T05:18:50.239Z", + "updated_at": "2021-06-22T05:50:45.916Z", + "title": "contenttypewithmultiplerte", + "uid": "contenttypewithmultiplerte", + "_version": 3, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "multiple": true, + "non_localizable": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": true, + "non_localizable": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithemptyschema": { + "content_type": { + "created_at": "2021-06-22T05:18:50.239Z", + "updated_at": "2021-06-22T05:50:45.916Z", + "title": "contenttypewithemptyschema", + "uid": "contenttypewithemptyschema", + "_version": 3, + "inbuilt_class": false, + "schema": [], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithmodularblock": { + "content_type": { + "created_at": "2021-06-22T08:36:26.050Z", + "updated_at": "2021-06-22T09:55:52.105Z", + "title": "contenttypewithmodularblock", + "uid": "contenttypewithmodularblock", + "_version": 3, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "blocks", + "display_name": "Modular Blocks", + "blocks": [ + { + "title": "test1", + "uid": "test1", + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ] + } + ], + "multiple": true, + "uid": "modular_blocks", + "field_metadata": { + "instruction": "", + "description": "" + }, + "non_localizable": false, + "mandatory": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithgroup": { + "content_type": { + "created_at": "2021-06-22T10:18:54.177Z", + "updated_at": "2021-06-22T10:19:31.012Z", + "title": "contenttypewithgroup", + "uid": "contenttypewithgroup", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "group", + "display_name": "Group", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "multiple": true, + "non_localizable": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": true, + "non_localizable": false, + "unique": false + } + ], + "uid": "group", + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithmultiplegroup": { + "content_type": { + "created_at": "2021-06-22T10:35:26.275Z", + "updated_at": "2021-06-22T10:36:06.768Z", + "title": "contenttypewithmultiplegroup", + "uid": "contenttypewithmultiplegroup", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "group", + "display_name": "Group", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "uid": "group", + "multiple": true, + "non_localizable": false, + "mandatory": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithentryupdateerror": { + "content_type": { + "created_at": "2021-06-24T09:59:49.808Z", + "updated_at": "2021-06-24T10:01:00.908Z", + "title": "contenttypewithentryUpdateError", + "uid": "contenttypewithentryupdateerror", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + }, + "contenttypewithfilefield": { + "content_type": { + "created_at": "2021-08-11T08:48:38.491Z", + "updated_at": "2021-08-11T08:50:42.734Z", + "title": "contenttypewithfilefield", + "uid": "contenttypewithfilefield", + "_version": 3, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "file", + "display_name": "File", + "uid": "file", + "extensions": [], + "field_metadata": { + "description": "", + "rich_text_type": "standard" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false + }, + { + "data_type": "file", + "display_name": "File", + "uid": "asset", + "extensions": [], + "field_metadata": { + "description": "", + "rich_text_type": "standard" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false + }, + { + "data_type": "blocks", + "display_name": "Modular Blocks", + "blocks": [ + { + "title": "test1", + "uid": "test1", + "schema": [ + { + "data_type": "file", + "display_name": "File", + "uid": "file", + "extensions": [], + "field_metadata": { + "description": "", + "rich_text_type": "standard" + }, + "mandatory": false, + "multiple": true, + "non_localizable": false, + "unique": false + } + ] + } + ], + "multiple": true, + "uid": "modular_blocks", + "field_metadata": { + "instruction": "", + "description": "" + }, + "mandatory": false, + "non_localizable": false, + "unique": false + }, + { + "data_type": "group", + "display_name": "Group", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "file", + "display_name": "File", + "uid": "file", + "field_metadata": { + "description": "", + "rich_text_type": "standard", + "image": true + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "dimension": { + "width": { + "min": null, + "max": null + }, + "height": { + "min": null, + "max": null + } + } + } + ], + "uid": "group", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "JSON Rich Text Editor", + "uid": "json_rte", + "field_metadata": { + "allow_json_rte": true, + "embed_entry": false, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "reference_to": [ + "sys_assets" + ], + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": true, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + }, + "extension_uids": [] + } + }, + "rte_images": { + "content_type": { + "created_at": "2021-06-18T08:28:40.550Z", + "updated_at": "2021-06-18T08:29:11.162Z", + "title": "ContentTypeWithSingleRTE", + "uid": "rte_images", + "_version": 2, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "non_localizable": false, + "multiple": false + }, + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "DEFAULT_ACL": { + "others": { + "read": false, + "create": false + }, + "users": [ + { + "uid": "bltd17749910d177e04", + "read": true, + "sub_acl": { + "read": true + } + } + ] + }, + "SYS_ACL": { + "roles": [ + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltbfe2abeba30ff382", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + }, + { + "uid": "bltfa739fdd274aa467", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + } + }, + { + "uid": "blt6cafb115ee0f0a25", + "read": true, + "sub_acl": { + "create": true, + "read": true, + "update": true, + "delete": true, + "publish": true + }, + "update": true, + "delete": true + } + ], + "others": { + "read": false, + "create": false, + "update": false, + "delete": false, + "sub_acl": { + "read": false, + "create": false, + "update": false, + "delete": false, + "publish": false + } + } + }, + "options": { + "is_page": false, + "singleton": false, + "sub_title": [], + "title": "title" + }, + "abilities": { + "get_one_object": true, + "get_all_objects": true, + "create_object": true, + "update_object": true, + "delete_object": true, + "delete_all_objects": true + } + } + } +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/dummy/defaultConfig.json b/packages/contentstack-migrate-rte/test/dummy/defaultConfig.json new file mode 100644 index 000000000..0eb5963a9 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/defaultConfig.json @@ -0,0 +1,5 @@ +{ + "apiEndpoint": "http://localhost:8000", + "cdnEndpoint": "https://cdn.contentstack.io", + "apiVersion": 3 +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/dummy/entriesResponse.json b/packages/contentstack-migrate-rte/test/dummy/entriesResponse.json new file mode 100644 index 000000000..b313313f6 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/entriesResponse.json @@ -0,0 +1,444 @@ +{ + "contenttypewithsinglerte": { + "entries": [ + { + "title": "Entry 2", + "rich_text_editor": "

value 2

", + "tags": [], + "locale": "en-us", + "uid": "blt6168b6493a122849", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-18T10:10:35.292Z", + "updated_at": "2021-06-18T10:10:35.292Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + }, + { + "title": "Entry 2", + "rich_text_editor": "

value 2 en-in

", + "tags": [], + "locale": "en-in", + "uid": "blt6168b6493a122849", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-18T10:10:35.292Z", + "updated_at": "2021-06-18T10:10:35.292Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + }, + { + "title": "entry 1", + "rich_text_editor": "

value 1

", + "tags": [], + "locale": "en-us", + "uid": "blt94ab8cf0a7956243", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-18T10:01:47.035Z", + "updated_at": "2021-06-18T10:01:47.035Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 2 + }, + "contenttype1forglobalfield": { + "entries": [ + { + "title": "test1", + "global_field": { + "rich_text_editor": "

global => richtext

" + }, + "group": { + "global_field": { + "rich_text_editor": "

group => global => rte

" + } + }, + "tags": [], + "locale": "en-us", + "uid": "blt982804d4a39b0197", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-21T09:47:49.318Z", + "updated_at": "2021-06-21T09:47:49.318Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "contenttype2forglobalfield": { + "entries": [ + { + "title": "test1", + "global_field": { + "rich_text_editor": "

global => rte

" + }, + "tags": [], + "locale": "en-us", + "uid": "blta20a822fdd7bcc87", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-21T09:50:48.065Z", + "updated_at": "2021-06-21T09:50:48.065Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "contenttypewithmultiplerte": { + "entries": [ + { + "title": "test1", + "rich_text_editor": [ + "

multiple 1

", + "

mutiple 2

" + ], + "tags": [], + "locale": "en-us", + "uid": "blt55ac0aa11a1dafe9", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-22T05:28:36.417Z", + "updated_at": "2021-06-22T05:28:36.417Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "contenttypewithmodularblock": { + "entries": [ + { + "title": "", + "modular_blocks": [ + { + "test1": { + "rich_text_editor": "

M1

", + "_metadata": { + "uid": "csa15520723d8f9738" + } + } + }, + { + "test1": { + "rich_text_editor": "

m2

", + "_metadata": { + "uid": "csc3ccd3f10d8ceff6" + } + } + }, + { + "test1": { + "rich_text_editor": "

m3

", + "_metadata": { + "uid": "csf2355ddf2979a0a1" + } + } + } + ], + "tags": [], + "locale": "en-us", + "uid": "blte02628730f4d0c3c", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-22T08:37:47.911Z", + "updated_at": "2021-06-22T08:37:47.911Z", + "_version": 1, + "_in_progress": true + } + ], + "count": 1 + }, + "contenttypewithgroup": { + "entries": [ + { + "title": "test1", + "group": { + "rich_text_editor": [ + "

rte1

", + "

rte2

" + ] + }, + "tags": [], + "locale": "en-us", + "uid": "blt279161bdc0c77b2d", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-22T10:24:00.471Z", + "updated_at": "2021-06-22T10:24:00.471Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "contenttypewithmultiplegroup": { + "entries": [ + { + "title": "test1", + "group": [ + { + "rich_text_editor": "

instance 1

", + "_metadata": { + "uid": "cs46fbdc80bbb32699" + } + }, + { + "rich_text_editor": "

instance 2

", + "_metadata": { + "uid": "cs9fa775294fcb9aba" + } + } + ], + "tags": [], + "locale": "en-us", + "uid": "bltd939c78b885da314", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-22T10:38:00.168Z", + "updated_at": "2021-06-22T10:38:00.168Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "contenttypewithentryupdateerror": { + "entries": [ + { + "title": "sdsdsds", + "rich_text_editor": "

will give error

", + "tags": [], + "locale": "en-us", + "uid": "blta9b16ac2827c54ed", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-24T10:03:39.192Z", + "updated_at": "2021-06-24T10:03:44.173Z", + "_version": 2, + "_in_progress": false + }, + { + "title": "sdsdsdsds", + "rich_text_editor": "

will give error

", + "tags": [], + "locale": "en-us", + "uid": "blta9b16ac2827c54e1", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-24T10:03:39.192Z", + "updated_at": "2021-06-24T10:03:44.173Z", + "_version": 2, + "_in_progress": false + } + ], + "count": 2 + }, + "contenttypewithfilefield": { + "entries": [ + { + "title": "This is test", + "file": null, + "asset": { + "uid": "bltab83acc0e405b6f8", + "created_at": "2021-07-26T10:47:48.337Z", + "updated_at": "2021-08-05T06:30:56.359Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "content_type": "image/png", + "file_size": "344996", + "tags": [], + "filename": "image.png", + "url": "***REMOVED***bltab83acc0e405b6f8/60fe92d4fe65cc54a3e7e5c3/image.png", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 5, + "title": "image.png", + "description": "5", + "dimension": { + "height": 1080, + "width": 1920 + }, + "publish_details": [] + }, + "modular_blocks": [ + { + "test1": { + "file": [ + { + "uid": "bltb13b08635ebffdc4", + "created_at": "2021-03-10T12:51:20.447Z", + "updated_at": "2021-03-10T12:51:20.447Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "content_type": "image/jpeg", + "file_size": "7701", + "tags": [], + "filename": "48558898.jpeg", + "url": "***REMOVED***bltb13b08635ebffdc4/6048c0c887ab224351aab15f/48558898.jpeg", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 1, + "title": "48558898.jpeg", + "dimension": { + "height": 275, + "width": 183 + }, + "publish_details": [] + }, + { + "uid": "bltb85f710a4a984858", + "created_at": "2021-03-10T13:37:22.523Z", + "updated_at": "2021-07-22T12:20:20.026Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt6436c4ee410b7913", + "content_type": "image/jpeg", + "file_size": "5403", + "tags": [], + "filename": "download.jpeg", + "url": "***REMOVED***bltb85f710a4a984858/6049fa897477c27b7b34443d/download.jpeg", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 37, + "title": "This is test1", + "description": "test1", + "dimension": { + "height": 194, + "width": 259 + }, + "publish_details": [] + }, + { + "uid": "blt3d60320b8e0b9b0a", + "created_at": "2021-03-04T10:36:08.977Z", + "updated_at": "2021-03-04T10:36:08.977Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "content_type": "image/jpeg", + "file_size": "5906", + "tags": [], + "filename": "download_(2).jpeg", + "url": "***REMOVED***blt3d60320b8e0b9b0a/6040b819116a7015b467265e/download_(2).jpeg", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 1, + "title": "download_(2).jpeg", + "dimension": { + "height": 183, + "width": 275 + }, + "publish_details": [] + } + ], + "_metadata": { + "uid": "csd629a3435c480311" + } + } + }, + { + "test1": { + "file": [ + { + "uid": "bltab83acc0e405b6f8", + "created_at": "2021-07-26T10:47:48.337Z", + "updated_at": "2021-08-05T06:30:56.359Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "content_type": "image/png", + "file_size": "344996", + "tags": [], + "filename": "image.png", + "url": "***REMOVED***bltab83acc0e405b6f8/60fe92d4fe65cc54a3e7e5c3/image.png", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 5, + "title": "image.png", + "description": "5", + "dimension": { + "height": 1080, + "width": 1920 + }, + "publish_details": [] + } + ], + "_metadata": { + "uid": "cs2fc2c61d898700d9" + } + } + } + ], + "group": { + "file": { + "uid": "bltab83acc0e405b6f8", + "created_at": "2021-07-26T10:47:48.337Z", + "updated_at": "2021-08-05T06:30:56.359Z", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "content_type": "image/png", + "file_size": "344996", + "tags": [], + "filename": "image.png", + "url": "***REMOVED***bltab83acc0e405b6f8/60fe92d4fe65cc54a3e7e5c3/image.png", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 5, + "title": "image.png", + "description": "5", + "dimension": { + "height": 1080, + "width": 1920 + }, + "publish_details": [] + } + }, + "rich_text_editor": "

This is test

", + "tags": [], + "locale": "en-us", + "uid": "bltcbf22e3759df80ef", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-08-11T08:57:28.614Z", + "updated_at": "2021-08-11T08:57:28.614Z", + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + }, + "rte_images": { + "entries": [ + { + "title": "Entry 2", + "rich_text_editor": "

", + "tags": [], + "locale": "en-us", + "uid": "blt6168b6493a122849", + "created_by": "blt3943ff6628403515", + "updated_by": "blt3943ff6628403515", + "created_at": "2021-06-18T10:10:35.292Z", + "updated_at": "2021-06-18T10:10:35.292Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + ], + "count": 1 + } +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/dummy/expectedEntriesResponse.json b/packages/contentstack-migrate-rte/test/dummy/expectedEntriesResponse.json new file mode 100644 index 000000000..bfee8fe07 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/expectedEntriesResponse.json @@ -0,0 +1,663 @@ +{ + "contenttypewithsinglerte": { + "blt6168b6493a122849": { + "entry": { + "content_type_uid": "contenttypewithsinglerte", + "title": "Entry 2", + "rich_text_editor": "

value 2

", + "supercharged_rte": { + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "children": [ + { + "text": "value 2" + } + ] + } + ], + "type": "doc" + }, + "tags": [], + "locale": "en-us", + "uid": "blt6168b6493a122849", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + }, + "blt6168b6493a122849_en-in": { + "entry": { + "content_type_uid": "contenttypewithsinglerte", + "title": "Entry 2", + "rich_text_editor": "

value 2 en-in

", + "tags": [ + ], + "locale": "en-in", + "ACL": { + }, + "_version": 1, + "_in_progress": false, + "supercharged_rte": { + "type": "doc", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "children": [ + { + "text": "value 2 en-in" + } + ] + } + ] + } + } + }, + "blt94ab8cf0a7956243": { + "entry": { + "content_type_uid": "contenttypewithsinglerte", + "title": "entry 1", + "rich_text_editor": "

value 1

", + "supercharged_rte": { + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "children": [ + { + "text": "value 1" + } + ] + } + ], + "type": "doc" + }, + "tags": [], + "locale": "en-us", + "uid": "blt94ab8cf0a7956243", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + }, + "contenttype1forglobalfield": { + "blt982804d4a39b0197": { + "entry": { + "content_type_uid": "contenttype1forglobalfield", + "title": "test1", + "global_field": { + "rich_text_editor": "

global => richtext

", + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "global => richtext" + } + ] + } + ], + "type": "doc" + } + }, + "group": { + "global_field": { + "rich_text_editor": "

group => global => rte

", + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "group => global => rte" + } + ] + } + ], + "type": "doc" + } + } + }, + "tags": [], + "locale": "en-us", + "uid": "blt982804d4a39b0197", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + }, + "contenttype2forglobalfield": { + "blta20a822fdd7bcc87": { + "entry": { + "content_type_uid": "contenttype2forglobalfield", + "title": "test1", + "global_field": { + "rich_text_editor": "

global => rte

", + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "global => rte" + } + ] + } + ], + "type": "doc" + } + }, + "tags": [], + "locale": "en-us", + "uid": "blta20a822fdd7bcc87", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + }, + "contenttypewithmultiplerte": { + "blt55ac0aa11a1dafe9": { + "entry": { + "content_type_uid": "contenttypewithmultiplerte", + "title": "test1", + "rich_text_editor": [ + "

multiple 1

", + "

mutiple 2

" + ], + "tags": [], + "locale": "en-us", + "uid": "blt55ac0aa11a1dafe9", + "ACL": {}, + "_version": 1, + "_in_progress": false, + "supercharged_rte": [ + { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "multiple 1" + } + ] + } + ], + "type": "doc" + }, + { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "mutiple 2" + } + ] + } + ], + "type": "doc" + } + ] + } + } + }, + "contenttypewithmodularblock": { + "blte02628730f4d0c3c": { + "entry": { + "content_type_uid": "contenttypewithmodularblock", + "title": "", + "modular_blocks": [ + { + "test1": { + "rich_text_editor": "

M1

", + "_metadata": { + "uid": "csa15520723d8f9738" + }, + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "M1" + } + ] + } + ], + "type": "doc" + } + } + }, + { + "test1": { + "rich_text_editor": "

m2

", + "_metadata": { + "uid": "csc3ccd3f10d8ceff6" + }, + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "m2" + } + ] + } + ], + "type": "doc" + } + } + }, + { + "test1": { + "rich_text_editor": "

m3

", + "_metadata": { + "uid": "csf2355ddf2979a0a1" + }, + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "m3" + } + ] + } + ], + "type": "doc" + } + } + } + ], + "tags": [], + "locale": "en-us", + "uid": "blte02628730f4d0c3c", + "_version": 1, + "_in_progress": true + } + } + }, + "contenttypewithgroup": { + "blt279161bdc0c77b2d": { + "entry": { + "content_type_uid": "contenttypewithgroup", + "title": "test1", + "group": { + "rich_text_editor": [ + "

rte1

", + "

rte2

" + ], + "supercharged_rte": [ + { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "rte1" + } + ] + } + ], + "type": "doc" + }, + { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "rte2" + } + ] + } + ], + "type": "doc" + } + ] + }, + "tags": [], + "locale": "en-us", + "uid": "blt279161bdc0c77b2d", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + }, + "contenttypewithmultiplegroup": { + "bltd939c78b885da314": { + "entry": { + "content_type_uid": "contenttypewithmultiplegroup", + "title": "test1", + "group": [ + { + "rich_text_editor": "

instance 1

", + "_metadata": { + "uid": "cs46fbdc80bbb32699" + }, + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "instance 1" + } + ] + } + ], + "type": "doc" + } + }, + { + "rich_text_editor": "

instance 2

", + "_metadata": { + "uid": "cs9fa775294fcb9aba" + }, + "supercharged_rte": { + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "instance 2" + } + ] + } + ], + "type": "doc" + } + } + ], + "tags": [], + "locale": "en-us", + "uid": "bltd939c78b885da314", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + }, + "contenttypewithfilefield": { + "bltcbf22e3759df80ef": { + "entry": { + "content_type_uid": "contenttypewithfilefield", + "title": "This is test", + "file": null, + "asset": "bltab83acc0e405b6f8", + "modular_blocks": [ + { + "test1": { + "file": [ + "bltb13b08635ebffdc4", + "bltb85f710a4a984858", + "blt3d60320b8e0b9b0a" + ], + "_metadata": {} + } + }, + { + "test1": { + "file": [ + "bltab83acc0e405b6f8" + ], + "_metadata": {} + } + } + ], + "group": { + "file": "bltab83acc0e405b6f8" + }, + "rich_text_editor": "

This is test

", + "tags": [], + "locale": "en-us", + "_version": 1, + "_in_progress": false, + "json_rte": { + "type": "doc", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "children": [ + { + "text": "This is test" + } + ] + } + ] + } + } + } + }, + "rte_images": { + "blt6168b6493a122849": { + "entry": { + "content_type_uid": "rte_images", + "title": "Entry 2", + "rich_text_editor": "

", + "supercharged_rte": { + "type": "doc", + "uid": "***REMOVED***", + "attrs": { + "dirty": true + }, + "children": [ + { + "type": "p", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "" + } + ] + }, + { + "type": "reference", + "attrs": { + "asset-name": "landscape-3.jpg", + "content-type-uid": "sys_assets", + "asset-link": "***REMOVED***8e12437ac2679e/bltfea8157ddfb8e776/6329f1106a7f7364973c028c/landscape-3.jpg", + "asset-type": "image/jpg", + "display-type": "display", + "type": "asset", + "asset-uid": "bltfea8157ddfb8e776", + "style": { + "width": "194px", + "height": "auto" + }, + "redactor-attributes": { + "asset_uid": "bltfea8157ddfb8e776", + "height": "auto", + "src": "***REMOVED***8e12437ac2679e/bltfea8157ddfb8e776/6329f1106a7f7364973c028c/landscape-3.jpg", + "width": "194", + "max-width": "194", + "style": "width: 194px; height: auto;" + }, + "width": 194, + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "" + } + ] + }, + { + "type": "fragment", + "attrs": { + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": " " + } + ] + }, + { + "type": "img", + "attrs": { + "url": "***REMOVED***8e12437ac2679e/bltfea8157ddfb8e776/6329f1106a7f7364973c028c/landscape-3.jpg", + "style": { + "width": "194px", + "height": "auto" + }, + "redactor-attributes": { + "height": "auto", + "src": "***REMOVED***8e12437ac2679e/bltfea8157ddfb8e776/6329f1106a7f7364973c028c/landscape-3.jpg", + "width": "194", + "max-width": "194", + "style": "width: 194px; height: auto;" + }, + "width": 194, + "dirty": true + }, + "uid": "***REMOVED***", + "children": [ + { + "text": "" + } + ] + } + ] + }, + "tags": [], + "locale": "en-us", + "uid": "blt6168b6493a122849", + "ACL": {}, + "_version": 1, + "_in_progress": false + } + } + } +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/dummy/globalFieldResponse.json b/packages/contentstack-migrate-rte/test/dummy/globalFieldResponse.json new file mode 100644 index 000000000..c482d60d4 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/dummy/globalFieldResponse.json @@ -0,0 +1,257 @@ +{ + "globalfieldformigration": { + "global_field": { + "created_at": "2021-06-21T09:31:40.548Z", + "updated_at": "2021-06-21T09:31:40.548Z", + "title": "globalFieldForMigration", + "uid": "globalfieldformigration", + "_version": 1, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "referred_content_types": [ + { + "uid": "contenttype1forglobalfield", + "title": "contentType1ForglobalField" + }, + { + "uid": "contenttype2forglobalfield", + "title": "contenttype2forglobalfield" + } + ] + } + }, + "globalfieldwithemptyschema": { + "global_field": { + "created_at": "2021-06-21T09:31:40.548Z", + "updated_at": "2021-06-21T09:31:40.548Z", + "title": "globalfieldwithemptyschema", + "uid": "globalfieldwithemptyschema", + "_version": 1, + "inbuilt_class": false, + "schema": [], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "referred_content_types": [ + { + "uid": "contenttypewithemptyschema", + "title": "contenttypewithemptyschema" + } + ] + } + }, + "globalfieldwithemptyschemacontenttype": { + "global_field": { + "created_at": "2021-06-21T09:31:40.548Z", + "updated_at": "2021-06-21T09:31:40.548Z", + "title": "globalfieldwithemptyschemacontenttype", + "uid": "globalfieldwithemptyschemacontenttype", + "_version": 1, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "referred_content_types": [ + { + "uid": "contenttypewithemptyschema", + "title": "contenttypewithemptyschema" + } + ] + } + }, + "globalfieldwithemptycontenttype": { + "global_field": { + "created_at": "2021-06-21T09:31:40.548Z", + "updated_at": "2021-06-21T09:31:40.548Z", + "title": "globalFieldForMigration", + "uid": "globalfieldformigration", + "_version": 1, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "referred_content_types": [] + } + }, + "globalfieldwithinvalidcontenttype": { + "global_field": { + "created_at": "2021-06-21T09:31:40.548Z", + "updated_at": "2021-06-21T09:31:40.548Z", + "title": "globalFieldForMigration", + "uid": "globalfieldformigration", + "_version": 1, + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Rich Text Editor", + "uid": "rich_text_editor", + "field_metadata": { + "allow_rich_text": true, + "description": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [], + "version": 3 + }, + "non_localizable": false, + "multiple": false, + "mandatory": false, + "unique": false + }, + { + "data_type": "json", + "display_name": "Supercharged RTE", + "uid": "supercharged_rte", + "field_metadata": { + "allow_json_rte": true, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "non_localizable": false, + "multiple": false, + "unique": false + } + ], + "last_activity": {}, + "maintain_revisions": true, + "description": "", + "referred_content_types": [ + { + "uid": "contenttypewithemptyschema", + "title": "contenttypewithemptyschema" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/contentstack-migrate-rte/test/mocha.opts b/packages/contentstack-migrate-rte/test/mocha.opts new file mode 100644 index 000000000..856a8d834 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/mocha.opts @@ -0,0 +1,4 @@ +--require test/setup.js +--timeout 10000 +--recursive +--exit diff --git a/packages/contentstack-migrate-rte/test/nock-setup.js b/packages/contentstack-migrate-rte/test/nock-setup.js new file mode 100644 index 000000000..5fcfc3a14 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/nock-setup.js @@ -0,0 +1,114 @@ +const nock = require("nock"); +const qs = require("querystring"); +const { + getContentType, + getEntries, + getExpectedOutput, + getGlobalField, + getEntriesOnlyUID, + getEntry, +} = require("./utils"); +const omitDeep = require("omit-deep-lodash"); +const { isEqual, cloneDeep } = require("lodash"); + +function setupNockMocks(testApiUrl = "https://api.contentstack.io", token) { + // Clear all previous mocks + nock.cleanAll(); + + // Mock content type fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+$/) + .query((query) => query.include_global_field_schema === "true") + .reply((uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)/); + return getContentType(match[1]); + }); + + // Mock entries list (only UIDs) + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query((query) => { + return query.include_count === "true" && query["only[Base][]"] === "uid"; + }) + .reply(200, (uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/); + return getEntriesOnlyUID(match[1]); + }); + + // Mock entries fetch with all fields + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries/) + .query(() => true) // Match any query + .reply(200, function (uri) { + let query = this.req.options.search || ""; + query = query.substring(1); + let locale = "en-us"; + const parsedQuery = qs.parse(query); + if (parsedQuery.locale) { + locale = parsedQuery.locale; + } + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries/); + return getEntries(match[1], locale); + }); + + // Mock get entry locales + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+\/locale/) + .query(() => true) + .reply(200, () => { + return { + locales: [ + { code: "en-in", localized: true }, + { code: "en-us" }, + ], + }; + }); + + // Mock single entry fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+$/) + .query(() => true) + .reply(200, (uri) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)/); + const query = qs.parse(uri.split("?")[1] || ""); + const locale = query.locale || "en-us"; + return getEntry(match[1], match[2], locale); + }); + + // Mock entry update + nock(testApiUrl) + .persist() + .put(/\/v3\/content_types\/[a-zA-Z0-9_]+\/entries\/[a-zA-Z0-9_]+/) + .query(() => true) + .reply((uri, body) => { + const match = uri.match(/\/v3\/content_types\/([a-zA-Z0-9_]+)\/entries\/([a-zA-Z0-9_]+)/); + const query = qs.parse(uri.split("?")[1] || ""); + const locale = query.locale || "en-us"; + + const responseModified = cloneDeep(omitDeep(body, ["uid"])); + const expectedResponse = cloneDeep(omitDeep(getExpectedOutput(match[1], match[2], locale), ["uid"])); + + if (isEqual(responseModified, expectedResponse)) { + return [200, { notice: "Entry updated successfully.", entry: {} }]; + } + return [400, { notice: "Update Failed.", error_message: "Entry update failed.", entry: {} }]; + }); + + // Mock global field fetch + nock(testApiUrl) + .persist() + .get(/\/v3\/global_fields\/[a-zA-Z0-9_]+$/) + .query((query) => query.include_content_types === "true") + .reply((uri) => { + const match = uri.match(/\/v3\/global_fields\/([a-zA-Z0-9_]+)/); + return getGlobalField(match[1]); + }); +} + +module.exports = { setupNockMocks }; + diff --git a/packages/contentstack-migrate-rte/test/setup.js b/packages/contentstack-migrate-rte/test/setup.js new file mode 100644 index 000000000..7b2bbc50d --- /dev/null +++ b/packages/contentstack-migrate-rte/test/setup.js @@ -0,0 +1,44 @@ +// test/setup.js + +const { Command } = require('@contentstack/cli-command'); + +// Set default region for tests to avoid "Region not configured" error +process.env.CSDX_REGION = 'NA'; + +// Set the default host for Command instances to avoid region configuration errors +// Note: cmaHost should return just the hostname without protocol (SDK adds https://) +const testApiHost = 'api.contentstack.io'; + +// Stub the Command class's cmaHost getter globally for all tests +// This prevents "Region not configured" errors during test execution +Object.defineProperty(Command.prototype, 'cmaHost', { + get: function() { + return testApiHost; + }, + configurable: true +}); + +// Make unhandled promise rejections fail tests loudly +process.on("unhandledRejection", (reason) => { + // eslint-disable-next-line no-console + console.error("UNHANDLED REJECTION in tests:", reason); + // Non-zero exit so CI fails explicitly + process.exitCode = 1; +}); + +// Make uncaught exceptions visible +process.on("uncaughtException", (err) => { + // eslint-disable-next-line no-console + console.error("UNCAUGHT EXCEPTION in tests:", err); + process.exitCode = 1; +}); + +// Nock setup - enable net connections and configure mocks in individual test files +try { + const nock = require("nock"); + // Explicitly reset nock settings to allow connections by default + nock.enableNetConnect(); + // We'll set up specific nock mocks in test files as needed +} catch (e) { + // Nock not available, skip +} diff --git a/packages/contentstack-migrate-rte/test/utils/index.js b/packages/contentstack-migrate-rte/test/utils/index.js new file mode 100644 index 000000000..2d10fe667 --- /dev/null +++ b/packages/contentstack-migrate-rte/test/utils/index.js @@ -0,0 +1,140 @@ +const { find } = require('lodash') +const contentTypeResponse = require('../dummy/contentTypeResponse.json') +const entriesResponse = require('../dummy/entriesResponse.json') +const expectedResponse = require('../dummy/expectedEntriesResponse.json') +const globalFieldResponse = require('../dummy/globalFieldResponse.json') +const dummyToken = { + test1: { + token: 'testManagementToken', + apiKey: 'testApiKey', + type: 'management', + }, +} +function getToken(alias) { + if (dummyToken[alias]) { + return dummyToken[alias] + } + throw new Error('Token with alias ' + "'" + alias + "'" + ' was not found') +} +function getGlobalField(uid) { + if (globalFieldResponse[uid]) { + return [200, globalFieldResponse[uid]] + } + return [422, { + error_message: `The Global Field '${uid}' was not found. Please try again.`, + error_code: 118, + errors: { + uid: [ + 'is not valid.', + ], + }, + }] +} +function getContentType(uid) { + if (contentTypeResponse[uid]) { + return [200, contentTypeResponse[uid]] + } + + return [422, { + error_message: `The Content Type '${uid}' was not found. Please try again.`, + error_code: 118, + errors: { + uid: [ + 'is not valid.', + ], + }, + }] +} +function getEntriesOnlyUID(contentstackTypeUid,locale = 'en-us') { + if (entriesResponse[contentstackTypeUid]) { + const entries = entriesResponse[contentstackTypeUid] + const allEntries = entries.entries.filter(entry => entry.locale === locale).map(entry => ({uid:entry.uid})) + return { + entries: allEntries, + count: allEntries.length + } + } + return { + error_message: `The Content Type '${contentstackTypeUid}' was not found. Please try again.`, + error_code: 118, + errors: { + uid: [ + 'is not valid.', + ], + }, + } +} +function getEntries(contentstackTypeUid,locale = "en-us") { + if (entriesResponse[contentstackTypeUid]) { + const entries = entriesResponse[contentstackTypeUid] + return { + entries: entries.entries.filter(entry => entry.locale === locale), + count: entries.entries.filter(entry => entry.locale === locale).length + } + } + return { + error_message: `The Content Type '${contentstackTypeUid}' was not found. Please try again.`, + error_code: 118, + errors: { + uid: [ + 'is not valid.', + ], + }, + } +} +function getEntry(contentstackTypeUid, entryUid, locale = "en-us") { + if (entriesResponse[contentstackTypeUid]) { + const entries = entriesResponse[contentstackTypeUid] + const entry = find(entries.entries, { uid: entryUid, locale }) + const masterEntry = find(entries.entries, { uid: entryUid, locale: 'en-us' }) + if (entry) { + return { + entry + } + } else { + if (masterEntry) { + return { + entry: masterEntry + } + } else { + return { + "error_message": "The requested object doesn't exist.", + "error_code": 141, + "errors": { + "uid": [ + "is not valid." + ] + } + } + } + } + } + return { + "error_message": "The requested object doesn't exist.", + "error_code": 141, + "errors": { + "uid": [ + "is not valid." + ] + } + } +} +function getExpectedOutput(contentTypeUid, entryUid,locale = "en-us") { + let entrySuffix = '' + if (locale !== 'en-us') { + entrySuffix = '_' + locale + } + if (expectedResponse[contentTypeUid] && expectedResponse[contentTypeUid][`${entryUid}${entrySuffix}`]) { + return expectedResponse[contentTypeUid][`${entryUid}${entrySuffix}`] + } + return {} +} +module.exports = { + getToken, + getContentType, + getEntries, + getExpectedOutput, + getGlobalField, + getEntriesOnlyUID, + getEntry +} diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md index 7b46e7113..bb0ca8b1a 100644 --- a/skills/dev-workflow/SKILL.md +++ b/skills/dev-workflow/SKILL.md @@ -15,7 +15,8 @@ description: Branches, CI, pnpm workspace commands, PR expectations, and TDD wor Plugins live under `packages/` (pnpm workspaces: `packages/*`). Current packages include: -- `contentstack-apps-cli` — Developer Hub apps (`app:*` commands); npm package `@contentstack/apps-cli` +- `contentstack-apps-cli` — Developer Hub apps (`app:*` commands); npm `@contentstack/apps-cli` +- `contentstack-migrate-rte` — HTML → JSON RTE (`cm:entries:migrate-html-rte`); npm `@contentstack/cli-cm-migrate-rte` - `contentstack-audit`, `contentstack-bootstrap`, `contentstack-branches`, `contentstack-clone`, `contentstack-export`, `contentstack-export-to-csv`, `contentstack-import`, `contentstack-import-setup`, `contentstack-migration`, `contentstack-seed`, `contentstack-variants` Plugins typically depend on `@contentstack/cli-command` and `@contentstack/cli-utilities`. Match dependency major/beta lines to the repo branch (`v1-dev` vs `v2-dev`). @@ -32,6 +33,13 @@ From repo root or the package directory: CI runs apps-cli tests in [`.github/workflows/unit-test.yml`](../../.github/workflows/unit-test.yml). +### Migrate RTE package commands + +| Command | Purpose | +| --- | --- | +| `pnpm --filter @contentstack/cli-cm-migrate-rte run build` | `oclif manifest` | +| `pnpm --filter @contentstack/cli-cm-migrate-rte test` | Mocha + nyc | + ## Commands (root) | Command | Purpose | From 50b9462de590b1d66309df22757fb32daf801b28 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Fri, 22 May 2026 12:43:55 +0530 Subject: [PATCH 2/3] feat: add bulk operations plugin to cli-plugins monorepo - Introduced the `@contentstack/cli-bulk-operations` plugin for performing bulk operations on Contentstack content. - Updated `AGENTS.md` to include the new bulk operations plugin and its purpose. - Created migration documentation in `BULK-OPERATIONS-MIGRATION.md`. - Added commands for bulk assets, entries, and taxonomies with detailed usage examples. - Implemented CI workflows for testing and publishing the bulk operations plugin. - Included necessary configuration files and updated README for the new plugin. --- .github/config/release.json | 3 +- .github/workflows/release-v2-beta-plugins.yml | 9 + .github/workflows/unit-test.yml | 4 + AGENTS.md | 9 +- BULK-OPERATIONS-MIGRATION.md | 10 + README.md | 2 +- .../.editorconfig | 13 + .../contentstack-bulk-operations/.gitignore | 21 + .../.mocharc.json | 14 + .../contentstack-bulk-operations/.npmignore | 27 + packages/contentstack-bulk-operations/.npmrc | 1 + packages/contentstack-bulk-operations/.nycrc | 28 + .../.prettierignore | 9 + .../contentstack-bulk-operations/.prettierrc | 10 + packages/contentstack-bulk-operations/LICENSE | 21 + .../contentstack-bulk-operations/README.md | 373 +++++ .../contentstack-bulk-operations/SECURITY.md | 27 + .../contentstack-bulk-operations/bin/dev.cmd | 3 + .../contentstack-bulk-operations/bin/dev.js | 6 + .../contentstack-bulk-operations/bin/run.cmd | 4 + .../contentstack-bulk-operations/bin/run.js | 7 + .../eslint.config.js | 64 + .../contentstack-bulk-operations/package.json | 121 ++ .../src/base-bulk-command.ts | 561 +++++++ .../src/commands/cm/stacks/bulk-assets.ts | 84 + .../src/commands/cm/stacks/bulk-entries.ts | 181 ++ .../src/commands/cm/stacks/bulk-taxonomies.ts | 201 +++ .../src/config/index.ts | 17 + .../src/core/index.ts | 4 + .../src/core/operation-executor.ts | 303 ++++ .../src/core/queue-manager.ts | 216 +++ .../src/core/rate-limiter.ts | 331 ++++ .../src/core/retry-strategy.ts | 57 + .../src/interfaces/index.ts | 423 +++++ .../src/messages/index.ts | 431 +++++ .../src/services/asset-service.ts | 276 ++++ .../src/services/bulk-operation-service.ts | 293 ++++ .../src/services/entry-service.ts | 609 +++++++ .../src/services/index.ts | 4 + .../src/services/taxonomy-service.ts | 67 + .../src/utils/batch-helper.ts | 167 ++ .../src/utils/batch-queue-handler.ts | 198 +++ .../src/utils/bulk-operation-log-handler.ts | 282 ++++ .../src/utils/bulk-publish-url-generator.ts | 36 + .../src/utils/client.ts | 58 + .../src/utils/command-helpers.ts | 107 ++ .../src/utils/config-builder.ts | 339 ++++ .../src/utils/constants.ts | 58 + .../src/utils/cross-publish-handler.ts | 304 ++++ .../src/utils/helpers.ts | 124 ++ .../src/utils/index.ts | 110 ++ .../src/utils/interactive.ts | 225 +++ .../src/utils/item-fetcher.ts | 257 +++ .../src/utils/non-localized-field-handler.ts | 303 ++++ .../src/utils/operation-confirmation.ts | 51 + .../src/utils/revert-retry-handler.ts | 266 +++ .../src/utils/taxonomy-publish-parse.ts | 30 + .../src/utils/validators.ts | 113 ++ .../test/helpers/init.js | 46 + .../test/tsconfig.json | 13 + .../test/unit/base-bulk-command.test.ts | 1015 ++++++++++++ .../test/unit/commands/bulk-assets.test.ts | 903 ++++++++++ .../test/unit/commands/bulk-entries.test.ts | 907 ++++++++++ .../test/unit/config/index.test.ts | 122 ++ .../test/unit/core/index.test.ts | 8 + .../test/unit/core/operation-executor.test.ts | 796 +++++++++ .../test/unit/core/queue-manager.test.ts | 682 ++++++++ .../test/unit/core/rate-limiter.test.ts | 570 +++++++ .../test/unit/core/retry-strategy.test.ts | 434 +++++ .../test/unit/messages/index.test.ts | 217 +++ .../test/unit/services/asset-service.test.ts | 396 +++++ .../services/bulk-operation-service.test.ts | 449 +++++ .../test/unit/services/entry-service.test.ts | 1459 +++++++++++++++++ .../test/unit/services/index.test.ts | 8 + .../unit/services/taxonomy-service.test.ts | 96 ++ .../test/unit/utils/batch-helper.test.ts | 355 ++++ .../unit/utils/batch-queue-handler.test.ts | 597 +++++++ .../utils/bulk-operation-log-handler.test.ts | 703 ++++++++ .../test/unit/utils/client.test.ts | 352 ++++ .../test/unit/utils/command-helpers.test.ts | 345 ++++ .../test/unit/utils/config-builder.test.ts | 755 +++++++++ .../unit/utils/cross-publish-handler.test.ts | 383 +++++ .../test/unit/utils/helpers.test.ts | 458 ++++++ .../test/unit/utils/index.test.ts | 350 ++++ .../test/unit/utils/interactive.test.ts | 419 +++++ .../test/unit/utils/item-fetcher.test.ts | 669 ++++++++ .../utils/non-localized-field-handler.test.ts | 319 ++++ .../unit/utils/operation-confirmation.test.ts | 305 ++++ .../unit/utils/revert-retry-handler.test.ts | 421 +++++ .../utils/taxonomy-interactive-select.test.ts | 69 + .../unit/utils/taxonomy-publish-parse.test.ts | 28 + .../test/unit/utils/validators.test.ts | 245 +++ .../tsconfig.json | 37 + skills/dev-workflow/SKILL.md | 8 + 94 files changed, 22808 insertions(+), 3 deletions(-) create mode 100644 packages/contentstack-bulk-operations/.editorconfig create mode 100644 packages/contentstack-bulk-operations/.gitignore create mode 100644 packages/contentstack-bulk-operations/.mocharc.json create mode 100644 packages/contentstack-bulk-operations/.npmignore create mode 100644 packages/contentstack-bulk-operations/.npmrc create mode 100644 packages/contentstack-bulk-operations/.nycrc create mode 100644 packages/contentstack-bulk-operations/.prettierignore create mode 100644 packages/contentstack-bulk-operations/.prettierrc create mode 100644 packages/contentstack-bulk-operations/LICENSE create mode 100644 packages/contentstack-bulk-operations/README.md create mode 100644 packages/contentstack-bulk-operations/SECURITY.md create mode 100644 packages/contentstack-bulk-operations/bin/dev.cmd create mode 100755 packages/contentstack-bulk-operations/bin/dev.js create mode 100644 packages/contentstack-bulk-operations/bin/run.cmd create mode 100755 packages/contentstack-bulk-operations/bin/run.js create mode 100644 packages/contentstack-bulk-operations/eslint.config.js create mode 100644 packages/contentstack-bulk-operations/package.json create mode 100644 packages/contentstack-bulk-operations/src/base-bulk-command.ts create mode 100644 packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts create mode 100644 packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-entries.ts create mode 100644 packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-taxonomies.ts create mode 100644 packages/contentstack-bulk-operations/src/config/index.ts create mode 100644 packages/contentstack-bulk-operations/src/core/index.ts create mode 100644 packages/contentstack-bulk-operations/src/core/operation-executor.ts create mode 100644 packages/contentstack-bulk-operations/src/core/queue-manager.ts create mode 100644 packages/contentstack-bulk-operations/src/core/rate-limiter.ts create mode 100644 packages/contentstack-bulk-operations/src/core/retry-strategy.ts create mode 100644 packages/contentstack-bulk-operations/src/interfaces/index.ts create mode 100644 packages/contentstack-bulk-operations/src/messages/index.ts create mode 100644 packages/contentstack-bulk-operations/src/services/asset-service.ts create mode 100644 packages/contentstack-bulk-operations/src/services/bulk-operation-service.ts create mode 100644 packages/contentstack-bulk-operations/src/services/entry-service.ts create mode 100644 packages/contentstack-bulk-operations/src/services/index.ts create mode 100644 packages/contentstack-bulk-operations/src/services/taxonomy-service.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/batch-helper.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/batch-queue-handler.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/bulk-operation-log-handler.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/bulk-publish-url-generator.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/client.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/command-helpers.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/config-builder.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/constants.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/cross-publish-handler.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/helpers.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/index.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/interactive.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/item-fetcher.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/non-localized-field-handler.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/operation-confirmation.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/revert-retry-handler.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/taxonomy-publish-parse.ts create mode 100644 packages/contentstack-bulk-operations/src/utils/validators.ts create mode 100644 packages/contentstack-bulk-operations/test/helpers/init.js create mode 100644 packages/contentstack-bulk-operations/test/tsconfig.json create mode 100644 packages/contentstack-bulk-operations/test/unit/base-bulk-command.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/commands/bulk-assets.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/commands/bulk-entries.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/config/index.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/core/index.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/core/operation-executor.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/core/queue-manager.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/core/rate-limiter.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/core/retry-strategy.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/messages/index.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/services/asset-service.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/services/bulk-operation-service.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/services/entry-service.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/services/index.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/services/taxonomy-service.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/batch-helper.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/batch-queue-handler.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/bulk-operation-log-handler.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/client.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/command-helpers.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/config-builder.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/cross-publish-handler.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/helpers.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/index.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/interactive.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/item-fetcher.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/non-localized-field-handler.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/operation-confirmation.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/revert-retry-handler.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/taxonomy-interactive-select.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/taxonomy-publish-parse.test.ts create mode 100644 packages/contentstack-bulk-operations/test/unit/utils/validators.test.ts create mode 100644 packages/contentstack-bulk-operations/tsconfig.json diff --git a/.github/config/release.json b/.github/config/release.json index 07a492d25..2e417b8e4 100755 --- a/.github/config/release.json +++ b/.github/config/release.json @@ -11,6 +11,7 @@ "bootstrap": false, "branches": false, "apps-cli": false, - "migrate-rte": false + "migrate-rte": false, + "bulk-operations": false } } diff --git a/.github/workflows/release-v2-beta-plugins.yml b/.github/workflows/release-v2-beta-plugins.yml index d1eb7a29f..922a7837a 100644 --- a/.github/workflows/release-v2-beta-plugins.yml +++ b/.github/workflows/release-v2-beta-plugins.yml @@ -159,3 +159,12 @@ jobs: package: ./packages/contentstack-migrate-rte/package.json access: public tag: beta + + # Bulk Operations + - name: Publishing bulk-operations (Beta) + uses: JS-DevTools/npm-publish@v3 + with: + token: ${{ secrets.NPM_TOKEN }} + package: ./packages/contentstack-bulk-operations/package.json + access: public + tag: beta diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index f58c8c712..e5acae444 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -74,3 +74,7 @@ jobs: - name: Run tests for Contentstack Migrate RTE working-directory: ./packages/contentstack-migrate-rte run: npm test + + - name: Run tests for Contentstack Bulk Operations + working-directory: ./packages/contentstack-bulk-operations + run: npm test diff --git a/AGENTS.md b/AGENTS.md index 41662f964..61605dbeb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ | Field | Detail | | --- | --- | | **Name:** | Contentstack CLI plugins (pnpm monorepo; root package name `csdx`) | -| **Purpose:** | OCLIF plugins that extend the Contentstack CLI (import/export, clone, migration, migrate RTE, seed, audit, variants, Developer Hub apps, etc.). | +| **Purpose:** | OCLIF plugins that extend the Contentstack CLI (import/export, clone, migration, migrate RTE, bulk operations, seed, audit, variants, Developer Hub apps, etc.). | | **Out of scope (if any):** | The **core** CLI aggregation lives in the separate `cli` monorepo; this repo ships plugin packages only. | ## Tech stack (at a glance) @@ -55,6 +55,13 @@ CI: [.github/workflows/unit-test.yml](.github/workflows/unit-test.yml) and other - **Migrated from:** [contentstack/cli-cm-migrate-rte](https://github.com/contentstack/cli-cm-migrate-rte) — see [MIGRATE-RTE-MIGRATION.md](MIGRATE-RTE-MIGRATION.md) - **Command:** `csdx cm:entries:migrate-html-rte` — JS sources in `src/`; `pnpm --filter @contentstack/cli-cm-migrate-rte run build` (`oclif manifest`) and `test` (see [dev-workflow](skills/dev-workflow/SKILL.md)) +## Bulk operations plugin (`@contentstack/cli-bulk-operations`) + +- **Package path:** [packages/contentstack-bulk-operations](packages/contentstack-bulk-operations) +- **npm name:** `@contentstack/cli-bulk-operations` (unchanged) +- **Migrated from:** [contentstack/cli-bulk-operations](https://github.com/contentstack/cli-bulk-operations) — see [BULK-OPERATIONS-MIGRATION.md](BULK-OPERATIONS-MIGRATION.md) (commands + repository) +- **Commands:** `csdx cm:stacks:bulk-entries`, `csdx cm:stacks:bulk-assets`, `csdx cm:stacks:bulk-taxonomies` — see [dev-workflow](skills/dev-workflow/SKILL.md) + ## Using Cursor (optional) If you use **Cursor**, [.cursor/rules/README.md](.cursor/rules/README.md) only points to **`AGENTS.md`**—same docs as everyone else. diff --git a/BULK-OPERATIONS-MIGRATION.md b/BULK-OPERATIONS-MIGRATION.md index fecea2cb6..576e83c0b 100644 --- a/BULK-OPERATIONS-MIGRATION.md +++ b/BULK-OPERATIONS-MIGRATION.md @@ -1,6 +1,16 @@ # 🔄 Migration Guide: From Bulk Publish to Bulk Operations Commands > **Migrating from @contentstack/cli-cm-bulk-publish (v1.x) to New Unified Commands @contentstack/cli-bulk-operations (v1.x)** + +## Repository + +| Before | After | +| --- | --- | +| [contentstack/cli-bulk-operations](https://github.com/contentstack/cli-bulk-operations) | [contentstack/cli-plugins](https://github.com/contentstack/cli-plugins) → `packages/contentstack-bulk-operations` | +| Issues on standalone repo | [cli-plugins issues](https://github.com/contentstack/cli-plugins/issues) | + +npm package name unchanged: **`@contentstack/cli-bulk-operations`**. Local dev: `pnpm --filter @contentstack/cli-bulk-operations run build` / `test` from cli-plugins (or cli-dev-workspace). + --- ## What Changed? diff --git a/README.md b/README.md index ef8ea5843..85964e21c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Contentstack is a headless CMS with an API-first approach that puts content at t CLI supports content management scripts through which you can perform the following tasks: -- Bulk publish content +- Bulk publish content (`cm:stacks:bulk-*` via `@contentstack/cli-bulk-operations`) - Export content - Import content - Clone Stack diff --git a/packages/contentstack-bulk-operations/.editorconfig b/packages/contentstack-bulk-operations/.editorconfig new file mode 100644 index 000000000..9a91138fb --- /dev/null +++ b/packages/contentstack-bulk-operations/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + + diff --git a/packages/contentstack-bulk-operations/.gitignore b/packages/contentstack-bulk-operations/.gitignore new file mode 100644 index 000000000..60cf481c5 --- /dev/null +++ b/packages/contentstack-bulk-operations/.gitignore @@ -0,0 +1,21 @@ +*-debug.log +*-error.log +/.nyc_output +/dist +/lib +/tmp +node_modules +oclif.manifest.json +.env +*.log +tsconfig.tsbuildinfo +.vscode +*.env +coverage +.husky/_ +.nvmrc +report.json +/doc +/logs +/bulk-operation +.DS_Store \ No newline at end of file diff --git a/packages/contentstack-bulk-operations/.mocharc.json b/packages/contentstack-bulk-operations/.mocharc.json new file mode 100644 index 000000000..5cf9a0423 --- /dev/null +++ b/packages/contentstack-bulk-operations/.mocharc.json @@ -0,0 +1,14 @@ +{ + "require": [ + "test/helpers/init.js", + "ts-node/register" + ], + "watch-extensions": [ + "ts" + ], + "recursive": true, + "reporter": "spec", + "timeout": 60000, + "exit": true, + "spec": ["test/**/*.test.ts"] +} \ No newline at end of file diff --git a/packages/contentstack-bulk-operations/.npmignore b/packages/contentstack-bulk-operations/.npmignore new file mode 100644 index 000000000..780b61332 --- /dev/null +++ b/packages/contentstack-bulk-operations/.npmignore @@ -0,0 +1,27 @@ +* +!lib/** +!bin/** +!oclif.manifest.json +!npm-shrinkwrap.json +lib/**/*.test.js +lib/**/*.test.d.ts +*.test.ts +.vscode +.github +src +test +eslint.config.js +.prettierrc +.prettierignore +.editorconfig +.mocharc.json +.nycrc +tsconfig.json +.husky +*.log +coverage +.env +*.env +.nvmrc + + diff --git a/packages/contentstack-bulk-operations/.npmrc b/packages/contentstack-bulk-operations/.npmrc new file mode 100644 index 000000000..521a9f7c0 --- /dev/null +++ b/packages/contentstack-bulk-operations/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/packages/contentstack-bulk-operations/.nycrc b/packages/contentstack-bulk-operations/.nycrc new file mode 100644 index 000000000..937827050 --- /dev/null +++ b/packages/contentstack-bulk-operations/.nycrc @@ -0,0 +1,28 @@ +{ + "extension": [ + ".ts" + ], + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.d.ts", + "test/**", + "lib/**" + ], + "reporter": [ + "text", + "lcov", + "html", + "json", + "json-summary", + "clover" + ], + "all": true, + "check-coverage": true, + "lines": 80, + "statements": 80, + "functions": 80, + "branches": 75 +} + diff --git a/packages/contentstack-bulk-operations/.prettierignore b/packages/contentstack-bulk-operations/.prettierignore new file mode 100644 index 000000000..39a4b599b --- /dev/null +++ b/packages/contentstack-bulk-operations/.prettierignore @@ -0,0 +1,9 @@ +lib +node_modules +coverage +*.log +dist +oclif.manifest.json +tsconfig.tsbuildinfo + + diff --git a/packages/contentstack-bulk-operations/.prettierrc b/packages/contentstack-bulk-operations/.prettierrc new file mode 100644 index 000000000..20cbf8f0f --- /dev/null +++ b/packages/contentstack-bulk-operations/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "arrowParens": "always" +} + diff --git a/packages/contentstack-bulk-operations/LICENSE b/packages/contentstack-bulk-operations/LICENSE new file mode 100644 index 000000000..aff1142ee --- /dev/null +++ b/packages/contentstack-bulk-operations/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/contentstack-bulk-operations/README.md b/packages/contentstack-bulk-operations/README.md new file mode 100644 index 000000000..c3da40745 --- /dev/null +++ b/packages/contentstack-bulk-operations/README.md @@ -0,0 +1,373 @@ +> **Source of truth:** [cli-plugins](https://github.com/contentstack/cli-plugins) — `packages/contentstack-bulk-operations`. Migrated from [cli-bulk-operations](https://github.com/contentstack/cli-bulk-operations). Command migration guide: [BULK-OPERATIONS-MIGRATION.md](../../BULK-OPERATIONS-MIGRATION.md). + +# @contentstack/cli-bulk-operations + +> Contentstack CLI plugin for performing bulk operations on your content. + +## Features + +- Perform bulk operations on Contentstack content +- Built with TypeScript for type safety +- Comprehensive test coverage +- Code quality enforced with ESLint and Prettier +- Automated CI/CD workflows + +## Usage + + +```sh-session +# For CLI 1.x:** + +# Install Contentstack CLI +$ npm install -g @contentstack/cli +$ csdx +running command... +$ csdx (--version|-v) +$ csdx --help [COMMAND] + +# Install bulk operations plugin +csdx plugins:install @contentstack/cli-bulk-operations + +# Verify installation +csdx cm:stacks:bulk-entries --help +``` +```sh-session +# For CLI 2.x:** + +# Install Contentstack CLI +$ npm install -g @contentstack/cli +$ csdx +running command... +$ csdx (--version|-v) +$ csdx --help [COMMAND] + +# Verify installation +csdx cm:stacks:bulk-entries --help +``` + + +## Commands + + +* [`csdx cm:stacks:bulk-assets`](#csdx-cmstacksbulk-assets) +* [`csdx cm:stacks:bulk-entries`](#csdx-cmstacksbulk-entries) +* [`csdx cm:stacks:bulk-taxonomies`](#csdx-cmstacksbulk-taxonomies) + +## `csdx cm:stacks:bulk-assets` + +Bulk operations for assets (publish/unpublish/cross-publish) + +``` +USAGE + $ csdx cm:stacks:bulk-assets [-a ] [-k ] [--operation publish|unpublish] [--environments ...] + [--locales ...] [--source-env ] [--source-alias ] [--publish-mode bulk|single] [--branch + ] [-c ] [-y] [--retry-failed ] [--revert ] [--bulk-operation-file ] [--folder-uid + ] + +FLAGS + -a, --alias= Uses the name of a saved Management Token to authenticate the command. The command + can only access the branches allowed for that token. This option can be used as an + alternative to` --stack-api-key.` + -c, --config= (optional) Specifies the path to a JSON configuration file that defines the options + for the command. Use this file instead of passing multiple CLI flags for a single + run. + -k, --stack-api-key= API key of the source stack. You must use either the --stack-api-key flag or the + --alias flag. + -y, --yes Skips interactive confirmation prompts and runs the command immediately using the + provided options. Useful for automation and scripts. + --branch= [default: main] The name of the branch where you want to perform the bulk publish + operation. If you don't mention the branch name, then by default the content from + main branch will be published. + --bulk-operation-file= [default: bulk-operation] (optional) Folder path to store operation logs. Creates + separate files for success and failed operations. Default: bulk-operation + --environments=... Specifies one or more environments where the entries or assets should be published. + Separate multiple environments with spaces. + --folder-uid= (optional) The UID of the Assets' folder from which the assets need to be + published. The default value is cs_root. + --locales=... Specifies one or more locale codes for which the entries or assets should be + published. Separate multiple locales with spaces. + --operation=