diff --git a/package-lock.json b/package-lock.json index 5f92539d83..e7cf5847c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23496,7 +23496,7 @@ "@contentstack/cli-audit": "~1.3.2", "@contentstack/cli-auth": "~1.3.17", "@contentstack/cli-cm-bootstrap": "~1.7.1", - "@contentstack/cli-cm-branches": "~1.0.19", + "@contentstack/cli-cm-branches": "~1.0.20", "@contentstack/cli-cm-bulk-publish": "~1.4.0", "@contentstack/cli-cm-clone": "~1.9.0", "@contentstack/cli-cm-export": "~1.10.2", @@ -23923,7 +23923,7 @@ }, "packages/contentstack-branches": { "name": "@contentstack/cli-cm-branches", - "version": "1.0.19", + "version": "1.0.20", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.2.16", diff --git a/packages/contentstack-branches/README.md b/packages/contentstack-branches/README.md index 2cdc9f6ddf..2ecc88b069 100755 --- a/packages/contentstack-branches/README.md +++ b/packages/contentstack-branches/README.md @@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-cm-branches/1.0.19 darwin-arm64 node-v20.8.0 +@contentstack/cli-cm-branches/1.0.20 darwin-arm64 node-v20.8.0 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-branches/package.json b/packages/contentstack-branches/package.json index f9fe694229..29b95f9d6b 100644 --- a/packages/contentstack-branches/package.json +++ b/packages/contentstack-branches/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-branches", "description": "Contentstack CLI plugin to do branches operations", - "version": "1.0.19", + "version": "1.0.20", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-branches/src/branch/merge-handler.ts b/packages/contentstack-branches/src/branch/merge-handler.ts index 823da0743a..aa91ede899 100644 --- a/packages/contentstack-branches/src/branch/merge-handler.ts +++ b/packages/contentstack-branches/src/branch/merge-handler.ts @@ -1,3 +1,4 @@ +import os from 'os'; import path from 'path'; import forEach from 'lodash/forEach'; import { cliux } from '@contentstack/cli-utilities'; @@ -275,7 +276,7 @@ export default class MergeHandler { deleted: [], }; - selectedMergeItems.forEach((item) => { + selectedMergeItems?.forEach((item) => { mergeContent.content_types[item.status].push(item.value); }); break; @@ -290,10 +291,16 @@ export default class MergeHandler { if (scriptFolderPath !== undefined) { cliux.success(`\nSuccess! We have generated entry migration files in the folder ${scriptFolderPath}`); cliux.print('\nWARNING!!! Migration is not intended to be run more than once. Migrated(entries/assets) will be duplicated if run more than once', {color: 'yellow'}); - - const migrationCommand = `csdx cm:stacks:migration --multiple --file-path ./${scriptFolderPath} --config {compare-branch:${mergePayload.compare_branch},file-path:./${scriptFolderPath}} --branch ${mergePayload.base_branch} --stack-api-key ${this.stackAPIKey}`; + + let migrationCommand: string; + if(os.platform() === 'win32'){ + migrationCommand = `csdx cm:stacks:migration --multiple --file-path ./${scriptFolderPath} --config compare-branch:${mergePayload.compare_branch},file-path:./${scriptFolderPath} --branch ${mergePayload.base_branch} --stack-api-key ${this.stackAPIKey}`; + }else{ + migrationCommand = `csdx cm:stacks:migration --multiple --file-path ./${scriptFolderPath} --config {compare-branch:${mergePayload.compare_branch},file-path:./${scriptFolderPath}} --branch ${mergePayload.base_branch} --stack-api-key ${this.stackAPIKey}`; + } + cliux.print( - `\nKindly follow the steps in the guide "https://www.contentstack.com/docs/developers/cli/migrate-branch-entries" to update the migration scripts, and then run the command:\n\n${migrationCommand}`, + `\nKindly follow the steps in the guide "https://www.contentstack.com/docs/developers/cli/entry-migration" to update the migration scripts, and then run the command:\n\n${migrationCommand}`, { color: 'blue' }, ); } diff --git a/packages/contentstack-branches/src/utils/create-merge-scripts.ts b/packages/contentstack-branches/src/utils/create-merge-scripts.ts index 239cb86a27..95c2cf1bbc 100644 --- a/packages/contentstack-branches/src/utils/create-merge-scripts.ts +++ b/packages/contentstack-branches/src/utils/create-merge-scripts.ts @@ -30,7 +30,6 @@ export function generateMergeScripts(mergeSummary, mergeJobUID) { merge_existing_new: entryCreateUpdateScript, merge_existing: entryUpdateScript, merge_new: entryCreateScript, - ignore: entryCreateUpdateScript, }; const processContentTypes = (contentTypes, messageType) => { @@ -66,6 +65,8 @@ export function getContentTypeMergeStatus(status) { return 'created'; } else if (status === 'merge_existing_new') { return 'created_updated'; + } else if (status === 'ignore') { + return; } else { return ''; } diff --git a/packages/contentstack-branches/src/utils/entry-create-script.ts b/packages/contentstack-branches/src/utils/entry-create-script.ts index e40f18cb16..228202ae59 100644 --- a/packages/contentstack-branches/src/utils/entry-create-script.ts +++ b/packages/contentstack-branches/src/utils/entry-create-script.ts @@ -2,6 +2,14 @@ export function entryCreateScript(contentType) { return ` const fs = require('fs'); const path = require('path'); + const { marked } = require('marked'); + const has = require('lodash/has'); + const isArray = require('lodash/isArray'); + const isObject = require('lodash/isObject'); + const omit = require('lodash/omit'); + const compact = require('lodash/compact') + const isPlainObject = require('lodash/isPlainObject'); + const {cliux, LoggerService} = require('@contentstack/cli-utilities') module.exports = async ({ migration, stackSDKInstance, managementAPIClient, config, branch, apiKey }) => { const keysToRemove = [ 'content_type_uid', @@ -31,20 +39,18 @@ export function entryCreateScript(contentType) { let assetUIDMapper = {}; let assetUrlMapper = {}; let assetRefPath = {}; - let isAssetDownload = false; + let downloadedAssets = []; + let parent=[]; + let logger; function getValueByPath(obj, path) { return path.split('[').reduce((o, key) => o && o[key.replace(/\]$/, '')], obj); } - function updateValueByPath(obj, path, newValue, type, fileIndex) { + function updateValueByPath(obj, path, newValue) { path.split('[').reduce((o, key, index, arr) => { if (index === arr.length - 1) { - if (type === 'file') { - o[key.replace(/]$/, '')][fileIndex] = newValue; - } else { o[key.replace(/]$/, '')][0].uid = newValue; - } } else { return o[key.replace(/\]$/, '')]; } @@ -77,35 +83,52 @@ export function entryCreateScript(contentType) { return references; }; - const findAssets = function (schema, entry, refPath, path) { + const findAssets = function (schema, entry) { for (const i in schema) { - const currentPath = path ? path + '[' + schema[i].uid : schema[i].uid; if (schema[i].data_type === 'group' || schema[i].data_type === 'global_field') { - findAssets(schema[i].schema, entry, refPath, currentPath + '[0]'); - } else if (schema[i].data_type === 'blocks') { - for (const block in schema[i].blocks) { + parent.push(schema[i].uid); + findAssets(schema[i].schema, entry); + parent.pop(); + } + if (schema[i].data_type === 'blocks') { + for (const j = 0; j < schema[i].blocks; j++) { { - if (schema[i].blocks[block].schema) { - findAssets( - schema[i].blocks[block].schema, - entry, - refPath, - currentPath + '[' + block + '][' + schema[i].blocks[block].uid + ']', - ); + if (schema[i].blocks[j].schema) { + parent.push(schema[i].uid); + parent.push(j); + parent.push(schema[i].blocks[j].uid); + findAssets(schema[i].blocks[j].schema, entry); + parent.pop(); + parent.pop(); + parent.pop(); } } } - } else if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { - findAssetIdsFromJsonRte(entry, schema, refPath, path); - } else if ( + } + if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { + parent.push(schema[i].uid); + findAssetIdsFromJsonRte(entry, schema); + parent.pop(); + } + if ( schema[i].data_type === 'text' && schema[i].field_metadata && (schema[i].field_metadata.markdown || schema[i].field_metadata.rich_text_type) ) { + parent.push(schema[i].uid); findFileUrls(schema[i], entry); - } else if (schema[i].data_type === 'file') { - refPath.push(currentPath) - const imgDetails = getValueByPath(entry, currentPath); + if (schema[i].field_metadata.rich_text_type) { + findAssetIdsFromHtmlRte(entry, schema[i]); + } + parent.pop(); + } + if (schema[i].data_type === 'file') { + parent.push(schema[i].uid); + let updatedEntry = entry; + for (let i = 0; i < parent.length; i++) { + updatedEntry = updatedEntry[parent[i]]; + } + const imgDetails = updatedEntry; if (schema[i].multiple) { if (imgDetails && imgDetails.length) { imgDetails.forEach((img) => { @@ -133,10 +156,21 @@ export function entryCreateScript(contentType) { cAssetDetails.push(obj); } } + parent.pop(); } } }; + function findAssetIdsFromHtmlRte(entryObj, ctSchema) { + const regex = / { - let imgDetails = entry[refPath]; - if (imgDetails !== undefined) { - if (imgDetails && !Array.isArray(imgDetails)) { - entry[refPath] = assetUIDMapper[imgDetails.uid]; - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - entry[refPath][i] = assetUIDMapper[img.uid]; - } - } - } else { - imgDetails = getValueByPath(entry, refPath); - if (imgDetails && !Array.isArray(imgDetails)) { - const imgUID = imgDetails?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', 0); - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - const imgUID = img?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', i); - } - } - } - }); + let updatedEntry = Object.assign({},entry); + entry = updateFileFields(updatedEntry, entry, null) entry = JSON.stringify(entry); const assetUrls = cAssetDetails.map((asset) => asset.url); const assetUIDs = cAssetDetails.map((asset) => asset.uid); @@ -299,6 +310,65 @@ export function entryCreateScript(contentType) { }); return JSON.parse(entry); }; + + function updateFileFields( + object, + parent, + pos + ) { + if (isPlainObject(object) && has(object, 'filename') && has(object, 'uid')) { + if (typeof pos !== 'undefined') { + if (typeof pos === 'number' || typeof pos === 'string') { + const replacer = () => { + if (assetUIDMapper.hasOwnProperty(object.uid)) { + parent[pos] = assetUIDMapper[object.uid]; + } else { + parent[pos] = ''; + } + }; + + if (parent.uid && assetUIDMapper[parent.uid]) { + parent.uid = assetUIDMapper[parent.uid]; + } + + if ( + object && + isObject(parent[pos]) && + parent[pos].uid && + parent[pos].url && + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + if ( + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + parent = omit(parent, ['asset']); + } + + if (object.uid && assetUIDMapper[object.uid]) { + object.uid = assetUIDMapper[object.uid]; + } + if (object.url && assetUrlMapper[object.url]) { + object.url = assetUrlMapper[object.url]; + } + } else { + replacer(); + } + } + } + } else if (isPlainObject(object)) { + for (let key in object) updateFileFields(object[key], object, key); + } else if (isArray(object) && object.length) { + for (let i = 0; i <= object.length; i++){ + updateFileFields(object[i], object, i); + } + parent[pos] = compact(object); + } + return object; + } const checkAndDownloadAsset = async function (cAsset) { const assetUID = cAsset?.uid; @@ -315,7 +385,6 @@ export function entryCreateScript(contentType) { return false; } else { - isAssetDownload = true; const cAssetDetail = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .asset(assetUID) @@ -357,8 +426,8 @@ export function entryCreateScript(contentType) { const uploadAssets = async function () { const assetFolderMap = JSON.parse(fs.readFileSync(path.resolve(filePath, 'folder-mapper.json'), 'utf8')); const stackAPIClient = managementAPIClient.stack({ api_key: stackSDKInstance.api_key, branch_uid: branch }); - for (let i = 0; i < cAssetDetails?.length; i++) { - const asset = cAssetDetails[i]; + for (let i = 0; i < downloadedAssets?.length; i++) { + const asset = downloadedAssets[i]; let requestOption = {}; requestOption.parent_uid = assetFolderMap[asset.parent_uid] || asset.parent_uid; @@ -401,6 +470,11 @@ export function entryCreateScript(contentType) { successTitle: 'Entries Created Successfully', failedTitle: 'Failed to create entries', task: async () => { + //logger file + if(!fs.existsSync(path.join(filePath, 'entry-migration'))){ + logger = new LoggerService(filePath, 'entry-migration'); + } + const compareBranchEntries = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .contentType('${contentType}') @@ -432,9 +506,10 @@ export function entryCreateScript(contentType) { const updatedCAsset = await checkAndDownloadAsset(asset); if (updatedCAsset) { cAssetDetails[i] = updatedCAsset; + downloadedAssets.push(updatedCAsset) } } - if (isAssetDownload) await uploadAssets(); + if (downloadedAssets?.length) await uploadAssets(); } let flag = { @@ -445,7 +520,17 @@ export function entryCreateScript(contentType) { async function updateEntry(entry, entryDetails) { Object.assign(entry, { ...entryDetails }); - await entry.update(); + await entry.update().catch(err => { + let errorMsg = 'Entry update failed for uid: ' + entry?.uid + ', title: ' + entry?.title + '. '; + if(err?.errors?.title){ + errorMsg += 'title'+ err?.errors?.title; + }else if(err?.errors?.entry){ + errorMsg += err?.errors?.entry; + }else{ + errorMsg += (err?.entry?.errorMessage || err?.errorMessage || err?.message) ?? 'Something went wrong!'; + } + logger.error(errorMsg) + }); } async function updateReferences(entryDetails, baseEntry, references) { @@ -477,8 +562,16 @@ export function entryCreateScript(contentType) { compareFilteredProperties.forEach(async (entryDetails) => { if(entryDetails !== undefined){ entryDetails = updateAssetDetailsInEntries(entryDetails); - let createdEntry = await stackSDKInstance.contentType('${contentType}').entry().create({ entry: entryDetails }).catch(error => { - throw error; + let createdEntry = await stackSDKInstance.contentType('${contentType}').entry().create({ entry: entryDetails }).catch(err => { + let errorMsg = 'Entry creation failed for contentType: ' + contentType + ', title: ' + entryDetails?.title + '. '; + if(err?.errors?.title){ + errorMsg += err?.errors?.title; + }else if(err?.errors?.entry){ + errorMsg += err?.errors?.entry; + }else{ + errorMsg += (err?.entry?.errorMessage || err?.errorMessage || err?.message) ?? 'Something went wrong!'; + } + logger.error(errorMsg) }); if(createdEntry){ if (flag.references) { diff --git a/packages/contentstack-branches/src/utils/entry-create-update-script.ts b/packages/contentstack-branches/src/utils/entry-create-update-script.ts index 194a218aad..f4e8b8048c 100644 --- a/packages/contentstack-branches/src/utils/entry-create-update-script.ts +++ b/packages/contentstack-branches/src/utils/entry-create-update-script.ts @@ -2,6 +2,14 @@ export function entryCreateUpdateScript(contentType) { return ` const fs = require('fs'); const path = require('path'); + const { marked } = require('marked'); + const has = require('lodash/has'); + const isArray = require('lodash/isArray'); + const isObject = require('lodash/isObject'); + const omit = require('lodash/omit'); + const compact = require('lodash/compact') + const isPlainObject = require('lodash/isPlainObject'); + const {cliux, LoggerService} = require('@contentstack/cli-utilities') module.exports = async ({ migration, stackSDKInstance, managementAPIClient, config, branch, apiKey }) => { const keysToRemove = [ 'content_type_uid', @@ -29,10 +37,12 @@ export function entryCreateUpdateScript(contentType) { let assetDirPath = path.resolve(filePath, 'assets'); let assetDetails = []; let newAssetDetails = []; + let downloadedAssets = []; let assetUIDMapper = {}; let assetUrlMapper = {}; let assetRefPath = {}; - let isAssetDownload = false; + let parent=[]; + let logger; function converter(data) { let arr = []; @@ -59,14 +69,10 @@ export function entryCreateUpdateScript(contentType) { return path.split('[').reduce((o, key) => o && o[key.replace(/\]$/, '')], obj); } - function updateValueByPath(obj, path, newValue, type, fileIndex) { + function updateValueByPath(obj, path, newValue) { path.split('[').reduce((o, key, index, arr) => { if (index === arr.length - 1) { - if (type === 'file') { - o[key.replace(/]$/, '')][fileIndex] = newValue; - } else { o[key.replace(/]$/, '')][0].uid = newValue; - } } else { return o[key.replace(/\]$/, '')]; } @@ -99,65 +105,93 @@ export function entryCreateUpdateScript(contentType) { return references; }; - const findAssets = function (schema, entry, refPath, path) { - for (const i in schema) { - const currentPath = path ? path + '[' + schema[i].uid : schema[i].uid; - if (schema[i].data_type === 'group' || schema[i].data_type === 'global_field') { - findAssets(schema[i].schema, entry, refPath, currentPath + '[0]'); - } else if (schema[i].data_type === 'blocks') { - for (const block in schema[i].blocks) { - { - if (schema[i].blocks[block].schema) { - findAssets( - schema[i].blocks[block].schema, - entry, - refPath, - currentPath + '[' + block + '][' + schema[i].blocks[block].uid + ']', - ); + const findAssets = function (schema, entry) { + for (const i in schema) { + if (schema[i].data_type === 'group' || schema[i].data_type === 'global_field') { + parent.push(schema[i].uid); + findAssets(schema[i].schema, entry); + parent.pop(); + } + if (schema[i].data_type === 'blocks') { + for (const j = 0; j < schema[i].blocks; j++) { + { + if (schema[i].blocks[j].schema) { + parent.push(schema[i].uid); + parent.push(j); + parent.push(schema[i].blocks[j].uid); + findAssets(schema[i].blocks[j].schema, entry); + parent.pop(); + parent.pop(); + parent.pop(); + } } } } - } else if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { - findAssetIdsFromJsonRte(entry, schema, refPath, path); - } else if ( - schema[i].data_type === 'text' && - schema[i].field_metadata && - (schema[i].field_metadata.markdown || schema[i].field_metadata.rich_text_type) - ) { - findFileUrls(schema[i], entry); - } else if (schema[i].data_type === 'file') { - refPath.push(currentPath) - const imgDetails = getValueByPath(entry, currentPath); - if (schema[i].multiple) { - if (imgDetails && imgDetails.length) { - imgDetails.forEach((img) => { + if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { + parent.push(schema[i].uid); + findAssetIdsFromJsonRte(entry, schema); + parent.pop(); + } + if ( + schema[i].data_type === 'text' && + schema[i].field_metadata && + (schema[i].field_metadata.markdown || schema[i].field_metadata.rich_text_type) + ) { + parent.push(schema[i].uid); + findFileUrls(schema[i], entry); + if (schema[i].field_metadata.rich_text_type) { + findAssetIdsFromHtmlRte(entry, schema[i]); + } + parent.pop(); + } + if (schema[i].data_type === 'file') { + parent.push(schema[i].uid); + let updatedEntry = entry; + for (let i = 0; i < parent.length; i++) { + updatedEntry = updatedEntry[parent[i]]; + } + const imgDetails = updatedEntry; + if (schema[i].multiple) { + if (imgDetails && imgDetails.length) { + imgDetails.forEach((img) => { + const obj = { + uid: img.uid, + parent_uid: img.parent_uid, + description: img.description, + title: img.title, + filename: img.filename, + url: img.url, + }; + assetDetails.push(obj); + }); + } + } else { + if (imgDetails) { const obj = { - uid: img.uid, - parent_uid: img.parent_uid, - description: img.description, - title: img.title, - filename: img.filename, - url: img.url, + uid: imgDetails.uid, + parent_uid: imgDetails.parent_uid, + description: imgDetails.description, + title: imgDetails.title, + filename: imgDetails.filename, + url: imgDetails.url, }; assetDetails.push(obj); - }); - } - } else { - if (imgDetails) { - const obj = { - uid: imgDetails.uid, - parent_uid: imgDetails.parent_uid, - description: imgDetails.description, - title: imgDetails.title, - filename: imgDetails.filename, - url: imgDetails.url, - }; - assetDetails.push(obj); + } } + parent.pop(); } } + }; + + function findAssetIdsFromHtmlRte(entryObj, ctSchema) { + const regex = / { - let imgDetails = entry[refPath]; - if (imgDetails !== undefined) { - if (imgDetails && !Array.isArray(imgDetails)) { - entry[refPath] = assetUIDMapper[imgDetails.uid]; - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - entry[refPath][i] = assetUIDMapper[img.uid]; - } - } - } else { - imgDetails = getValueByPath(entry, refPath); - if (imgDetails && !Array.isArray(imgDetails)) { - const imgUID = imgDetails?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', 0); - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - const imgUID = img?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', i); - } - } - } - }); + let updatedEntry = Object.assign({},entry); + entry = updateFileFields(updatedEntry, entry, null) entry = JSON.stringify(entry); const assetUrls = assetDetails.map((asset) => asset.url); const assetUIDs = assetDetails.map((asset) => asset.uid); @@ -321,6 +332,65 @@ export function entryCreateUpdateScript(contentType) { }); return JSON.parse(entry); }; + + function updateFileFields( + object, + parent, + pos + ) { + if (isPlainObject(object) && has(object, 'filename') && has(object, 'uid')) { + if (typeof pos !== 'undefined') { + if (typeof pos === 'number' || typeof pos === 'string') { + const replacer = () => { + if (assetUIDMapper.hasOwnProperty(object.uid)) { + parent[pos] = assetUIDMapper[object.uid]; + } else { + parent[pos] = ''; + } + }; + + if (parent.uid && assetUIDMapper[parent.uid]) { + parent.uid = assetUIDMapper[parent.uid]; + } + + if ( + object && + isObject(parent[pos]) && + parent[pos].uid && + parent[pos].url && + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + if ( + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + parent = omit(parent, ['asset']); + } + + if (object.uid && assetUIDMapper[object.uid]) { + object.uid = assetUIDMapper[object.uid]; + } + if (object.url && assetUrlMapper[object.url]) { + object.url = assetUrlMapper[object.url]; + } + } else { + replacer(); + } + } + } + } else if (isPlainObject(object)) { + for (let key in object) updateFileFields(object[key], object, key); + } else if (isArray(object) && object.length) { + for (let i = 0; i <= object.length; i++){ + updateFileFields(object[i], object, i); + } + parent[pos] = compact(object); + } + return object; + } const checkAndDownloadAsset = async function (cAsset) { if (cAsset) { @@ -337,7 +407,6 @@ export function entryCreateUpdateScript(contentType) { return false; } else { - isAssetDownload = true; const cAssetDetail = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .asset(assetUID) @@ -379,8 +448,8 @@ export function entryCreateUpdateScript(contentType) { const uploadAssets = async function () { const assetFolderMap = JSON.parse(fs.readFileSync(path.resolve(filePath, 'folder-mapper.json'), 'utf8')); const stackAPIClient = managementAPIClient.stack({ api_key: stackSDKInstance.api_key, branch_uid: branch }); - for (let i = 0; i < assetDetails?.length; i++) { - const asset = assetDetails[i]; + for (let i = 0; i < downloadedAssets?.length; i++) { + const asset = downloadedAssets[i]; let requestOption = {}; requestOption.parent_uid = assetFolderMap[asset.parent_uid] || asset.parent_uid; @@ -416,6 +485,10 @@ export function entryCreateUpdateScript(contentType) { successMessage: 'Entries Updated Successfully', failedMessage: 'Failed to update entries', task: async () => { + //logger file + if(!fs.existsSync(path.join(filePath, 'entry-migration'))){ + logger = new LoggerService(filePath, 'entry-migration'); + } let compareBranchEntries = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .contentType('${contentType}') @@ -451,9 +524,10 @@ export function entryCreateUpdateScript(contentType) { const updatedCAsset = await checkAndDownloadAsset(asset); if(updatedCAsset){ newAssetDetails[i] = updatedCAsset; + downloadedAssets.push(updatedCAsset) } } - if (isAssetDownload) await uploadAssets(); + if (downloadedAssets?.length) await uploadAssets(); } let flag = { @@ -465,7 +539,17 @@ export function entryCreateUpdateScript(contentType) { async function updateEntry(entry, entryDetails) { if (entry) { Object.assign(entry, { ...entryDetails }); - await entry.update(); + await entry.update().catch(err => { + let errorMsg = 'Entry update failed for uid: ' + entry?.uid + ', title: ' + entry?.title + '.'; + if(err?.errors?.title){ + errorMsg += err?.errors?.title; + }else if(err?.errors?.entry){ + errorMsg += err?.errors?.entry; + }else{ + errorMsg += (err?.entry?.errorMessage || err?.errorMessage || err?.message) ?? 'Something went wrong!'; + } + logger.error(errorMsg) + }); } } diff --git a/packages/contentstack-branches/src/utils/entry-update-script.ts b/packages/contentstack-branches/src/utils/entry-update-script.ts index 19b6549bda..ce6257a4d1 100644 --- a/packages/contentstack-branches/src/utils/entry-update-script.ts +++ b/packages/contentstack-branches/src/utils/entry-update-script.ts @@ -2,6 +2,14 @@ export function entryUpdateScript(contentType) { return ` const fs = require('fs'); const path = require('path'); + const { marked } = require('marked'); + const has = require('lodash/has'); + const isArray = require('lodash/isArray'); + const isObject = require('lodash/isObject'); + const omit = require('lodash/omit'); + const compact = require('lodash/compact') + const isPlainObject = require('lodash/isPlainObject'); + const {cliux, LoggerService} = require('@contentstack/cli-utilities') module.exports = async ({ migration, stackSDKInstance, managementAPIClient, config, branch, apiKey }) => { const keysToRemove = [ 'content_type_uid', @@ -29,10 +37,12 @@ export function entryUpdateScript(contentType) { let assetDirPath = path.resolve(filePath, 'assets'); let assetDetails = []; let newAssetDetails = []; + let downloadedAssets = []; let assetUIDMapper = {}; let assetUrlMapper = {}; let assetRefPath = {}; - let isAssetDownload = false; + let parent=[]; + let logger; function converter(data) { let arr = []; @@ -59,14 +69,10 @@ export function entryUpdateScript(contentType) { return path.split('[').reduce((o, key) => o && o[key.replace(/\]$/, '')], obj); } - function updateValueByPath(obj, path, newValue, type, fileIndex) { + function updateValueByPath(obj, path, newValue) { path.split('[').reduce((o, key, index, arr) => { if (index === arr.length - 1) { - if (type === 'file') { - o[key.replace(/]$/, '')][fileIndex] = newValue; - } else { o[key.replace(/]$/, '')][0].uid = newValue; - } } else { return o[key.replace(/\]$/, '')]; } @@ -99,35 +105,52 @@ export function entryUpdateScript(contentType) { return references; }; - const findAssets = function (schema, entry, refPath, path) { + const findAssets = function (schema, entry) { for (const i in schema) { - const currentPath = path ? path + '[' + schema[i].uid : schema[i].uid; if (schema[i].data_type === 'group' || schema[i].data_type === 'global_field') { - findAssets(schema[i].schema, entry, refPath, currentPath + '[0]'); - } else if (schema[i].data_type === 'blocks') { - for (const block in schema[i].blocks) { + parent.push(schema[i].uid); + findAssets(schema[i].schema, entry); + parent.pop(); + } + if (schema[i].data_type === 'blocks') { + for (const j = 0; j < schema[i].blocks; j++) { { - if (schema[i].blocks[block].schema) { - findAssets( - schema[i].blocks[block].schema, - entry, - refPath, - currentPath + '[' + block + '][' + schema[i].blocks[block].uid + ']', - ); + if (schema[i].blocks[j].schema) { + parent.push(schema[i].uid); + parent.push(j); + parent.push(schema[i].blocks[j].uid); + findAssets(schema[i].blocks[j].schema, entry); + parent.pop(); + parent.pop(); + parent.pop(); } } } - } else if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { - findAssetIdsFromJsonRte(entry, schema, refPath, path); - } else if ( + } + if (schema[i].data_type === 'json' && schema[i].field_metadata.rich_text_type) { + parent.push(schema[i].uid); + findAssetIdsFromJsonRte(entry, schema); + parent.pop(); + } + if ( schema[i].data_type === 'text' && schema[i].field_metadata && (schema[i].field_metadata.markdown || schema[i].field_metadata.rich_text_type) ) { + parent.push(schema[i].uid); findFileUrls(schema[i], entry); - } else if (schema[i].data_type === 'file') { - refPath.push(currentPath) - const imgDetails = getValueByPath(entry, currentPath); + if (schema[i].field_metadata.rich_text_type) { + findAssetIdsFromHtmlRte(entry, schema[i]); + } + parent.pop(); + } + if (schema[i].data_type === 'file') { + parent.push(schema[i].uid); + let updatedEntry = entry; + for (let i = 0; i < parent.length; i++) { + updatedEntry = updatedEntry[parent[i]]; + } + const imgDetails = updatedEntry; if (schema[i].multiple) { if (imgDetails && imgDetails.length) { imgDetails.forEach((img) => { @@ -155,10 +178,21 @@ export function entryUpdateScript(contentType) { assetDetails.push(obj); } } + parent.pop(); } } }; + function findAssetIdsFromHtmlRte(entryObj, ctSchema) { + const regex = / { - let imgDetails = entry[refPath]; - if (imgDetails !== undefined) { - if (imgDetails && !Array.isArray(imgDetails)) { - entry[refPath] = assetUIDMapper[imgDetails.uid]; - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - entry[refPath][i] = assetUIDMapper[img.uid]; - } - } - } else { - imgDetails = getValueByPath(entry, refPath); - if (imgDetails && !Array.isArray(imgDetails)) { - const imgUID = imgDetails?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', 0); - } else if (imgDetails && Array.isArray(imgDetails)) { - for (let i = 0; i < imgDetails.length; i++) { - const img = imgDetails[i]; - const imgUID = img?.uid; - updateValueByPath(entry, refPath, assetUIDMapper[imgUID], 'file', i); - } - } - } - }); + let updatedEntry = Object.assign({},entry); + entry = updateFileFields(updatedEntry, entry, null) entry = JSON.stringify(entry); const assetUrls = assetDetails.map((asset) => asset.url); const assetUIDs = assetDetails.map((asset) => asset.uid); @@ -321,6 +332,65 @@ export function entryUpdateScript(contentType) { }); return JSON.parse(entry); }; + + function updateFileFields( + object, + parent, + pos + ) { + if (isPlainObject(object) && has(object, 'filename') && has(object, 'uid')) { + if (typeof pos !== 'undefined') { + if (typeof pos === 'number' || typeof pos === 'string') { + const replacer = () => { + if (assetUIDMapper.hasOwnProperty(object.uid)) { + parent[pos] = assetUIDMapper[object.uid]; + } else { + parent[pos] = ''; + } + }; + + if (parent.uid && assetUIDMapper[parent.uid]) { + parent.uid = assetUIDMapper[parent.uid]; + } + + if ( + object && + isObject(parent[pos]) && + parent[pos].uid && + parent[pos].url && + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + if ( + has(parent, 'asset') && + has(parent, '_content_type_uid') && + parent._content_type_uid === 'sys_assets' + ) { + parent = omit(parent, ['asset']); + } + + if (object.uid && assetUIDMapper[object.uid]) { + object.uid = assetUIDMapper[object.uid]; + } + if (object.url && assetUrlMapper[object.url]) { + object.url = assetUrlMapper[object.url]; + } + } else { + replacer(); + } + } + } + } else if (isPlainObject(object)) { + for (let key in object) updateFileFields(object[key], object, key); + } else if (isArray(object) && object.length) { + for (let i = 0; i <= object.length; i++){ + updateFileFields(object[i], object, i); + } + parent[pos] = compact(object); + } + return object; + } const checkAndDownloadAsset = async function (cAsset) { if (cAsset) { @@ -337,7 +407,6 @@ export function entryUpdateScript(contentType) { return false; } else { - isAssetDownload = true; const cAssetDetail = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .asset(assetUID) @@ -379,8 +448,8 @@ export function entryUpdateScript(contentType) { const uploadAssets = async function () { const assetFolderMap = JSON.parse(fs.readFileSync(path.resolve(filePath, 'folder-mapper.json'), 'utf8')); const stackAPIClient = managementAPIClient.stack({ api_key: stackSDKInstance.api_key, branch_uid: branch }); - for (let i = 0; i < assetDetails?.length; i++) { - const asset = assetDetails[i]; + for (let i = 0; i < downloadedAssets?.length; i++) { + const asset = downloadedAssets[i]; let requestOption = {}; requestOption.parent_uid = assetFolderMap[asset.parent_uid] || asset.parent_uid; @@ -415,6 +484,11 @@ export function entryUpdateScript(contentType) { successMessage: 'Entries Updated Successfully', failedMessage: 'Failed to update entries', task: async () => { + //logger file + if(!fs.existsSync(path.join(filePath, 'entry-migration'))){ + logger = new LoggerService(filePath, 'entry-migration'); + } + let compareBranchEntries = await managementAPIClient .stack({ api_key: stackSDKInstance.api_key, branch_uid: compareBranch }) .contentType('${contentType}') @@ -450,9 +524,10 @@ export function entryUpdateScript(contentType) { const updatedCAsset = await checkAndDownloadAsset(asset); if(updatedCAsset){ newAssetDetails[i] = updatedCAsset; + downloadedAssets.push(updatedCAsset); } } - if (isAssetDownload) await uploadAssets(); + if (downloadedAssets?.length) await uploadAssets(); } let flag = { @@ -463,7 +538,17 @@ export function entryUpdateScript(contentType) { async function updateEntry(entry, entryDetails) { Object.assign(entry, { ...entryDetails }); - await entry.update(); + await entry.update().catch(err => { + let errorMsg = 'Entry update failed for uid: ' + entry?.uid + ', title: ' + entry?.title + '.'; + if(err?.errors?.title){ + errorMsg += err?.errors?.title; + }else if(err?.errors?.entry){ + errorMsg += err?.errors?.entry; + }else{ + errorMsg += (err?.entry?.errorMessage || err?.errorMessage || err?.message) ?? 'Something went wrong!'; + } + logger.error(errorMsg) + }); } async function updateReferences(entryDetails, baseEntry, references) { diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index dd83c43c56..9e4921c8d7 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -25,7 +25,7 @@ "@contentstack/cli-audit": "~1.3.2", "@contentstack/cli-auth": "~1.3.17", "@contentstack/cli-cm-bootstrap": "~1.7.1", - "@contentstack/cli-cm-branches": "~1.0.19", + "@contentstack/cli-cm-branches": "~1.0.20", "@contentstack/cli-cm-bulk-publish": "~1.4.0", "@contentstack/cli-cm-clone": "~1.9.0", "@contentstack/cli-cm-export": "~1.10.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9332f9b940..9cfe8e1a66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: '@contentstack/cli-audit': ~1.3.2 '@contentstack/cli-auth': ~1.3.17 '@contentstack/cli-cm-bootstrap': ~1.7.1 - '@contentstack/cli-cm-branches': ~1.0.19 + '@contentstack/cli-cm-branches': ~1.0.20 '@contentstack/cli-cm-bulk-publish': ~1.4.0 '@contentstack/cli-cm-clone': ~1.9.0 '@contentstack/cli-cm-export': ~1.10.2