FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(import): remap nested entry-data extension UIDs for is_asset custom fields [DX-7312] by harshitha-cstk · Pull Request #359 · contentstack/cli-plugins · GitHub

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -697,46 +697,84 @@ export default class ImportMarketplaceApps extends BaseClass {

if (!isEmpty(configuration)) {
log.debug(`Updating app configuration for: ${appName}`, this.importConfig.context);
await this.appSdk
.marketplace(this.importConfig.org_uid)
.installation(installation_uid)
.setConfiguration(this.nodeCrypto.decrypt(configuration))
.then(({ data }: any) => {
if (data?.message) {
log.debug(data, this.importConfig.context);
log.info(formatError(data.message), this.importConfig.context);
} else {
log.success(`${appName} app config updated successfully.!`, this.importConfig.context);
log.debug(`Configuration update successful for: ${appName}`, this.importConfig.context);
}
})
.catch((error: any) => {
log.debug(error, this.importConfig.context);
log.error(formatError(error), this.importConfig.context);
log.debug(`Configuration update failed for: ${appName}`, this.importConfig.context);
});
// NOTE: decrypt synchronously in a guard. A bad-decrypt (e.g. ERR_OSSL_BAD_DECRYPT when the
// export was encrypted with a different key) would otherwise throw here — outside the promise
// chain's .catch — abort the whole marketplace-apps module, and skip writing the
// marketplace_apps uid-mapping, which starves the downstream GF/CT/entry extension remap.
// Instead: warn and skip only this app's configuration; the app stays installed and its
// extension mappings are still recorded.
let decryptedConfiguration: any;
try {
decryptedConfiguration = this.nodeCrypto.decrypt(configuration);
} catch (error: any) {
log.warn(
`Failed to decrypt configuration for '${appName}'; skipping its configuration update. The app is installed and its extension mappings are preserved. (${
error?.message || error
})`,
this.importConfig.context,
);
decryptedConfiguration = undefined;
}

if (decryptedConfiguration !== undefined) {
await this.appSdk
.marketplace(this.importConfig.org_uid)
.installation(installation_uid)
.setConfiguration(decryptedConfiguration)
.then(({ data }: any) => {
if (data?.message) {
log.debug(data, this.importConfig.context);
log.info(formatError(data.message), this.importConfig.context);
} else {
log.success(`${appName} app config updated successfully.!`, this.importConfig.context);
log.debug(`Configuration update successful for: ${appName}`, this.importConfig.context);
}
})
.catch((error: any) => {
log.debug(error, this.importConfig.context);
log.error(formatError(error), this.importConfig.context);
log.debug(`Configuration update failed for: ${appName}`, this.importConfig.context);
});
}
}

if (!isEmpty(server_configuration)) {
log.debug(`Updating server configuration for: ${appName}`, this.importConfig.context);
await this.appSdk
.marketplace(this.importConfig.org_uid)
.installation(installation_uid)
.setServerConfig(this.nodeCrypto.decrypt(server_configuration))
.then(({ data }: any) => {
if (data?.message) {
log.debug(data, this.importConfig.context);
log.error(formatError(data.message), this.importConfig.context);
} else {
log.success(`${appName} app server config updated successfully.!`, this.importConfig.context);
log.debug(`Server configuration update successful for: ${appName}`, this.importConfig.context);
}
})
.catch((error: any) => {
log.debug(error, this.importConfig.context);
log.error(formatError(error), this.importConfig.context);
log.debug(`Server configuration update failed for: ${appName}`, this.importConfig.context);
});
// NOTE: guard the decrypt for the same reason as `configuration` above — a bad-decrypt must
// not abort the module or skip the uid-mapping write.
let decryptedServerConfiguration: any;
try {
decryptedServerConfiguration = this.nodeCrypto.decrypt(server_configuration);
} catch (error: any) {
log.warn(
`Failed to decrypt server configuration for '${appName}'; skipping its server configuration update. The app is installed and its extension mappings are preserved. (${
error?.message || error
})`,
this.importConfig.context,
);
decryptedServerConfiguration = undefined;
}

if (decryptedServerConfiguration !== undefined) {
await this.appSdk
.marketplace(this.importConfig.org_uid)
.installation(installation_uid)
.setServerConfig(decryptedServerConfiguration)
.then(({ data }: any) => {
if (data?.message) {
log.debug(data, this.importConfig.context);
log.error(formatError(data.message), this.importConfig.context);
} else {
log.success(`${appName} app server config updated successfully.!`, this.importConfig.context);
log.debug(`Server configuration update successful for: ${appName}`, this.importConfig.context);
}
})
.catch((error: any) => {
log.debug(error, this.importConfig.context);
log.error(formatError(error), this.importConfig.context);
log.debug(`Server configuration update failed for: ${appName}`, this.importConfig.context);
});
}
}
}

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export const lookupAssets = function (
let matchedUrls: string[] = [];

let find = function (schema: any, entryToFind: any) {
// Guard against a missing schema — e.g. a reference-only global_field stub
// (data_type: 'global_field', reference_to, no expanded `schema`) as produced by
// query-export. Without this, recursing into an undefined schema throws.
if (!Array.isArray(schema)) return;
for (let i = 0, _i = schema.length; i < _i; i++) {
if (
schema[i].data_type === 'text' &&
Expand Down Expand Up @@ -170,6 +174,32 @@ export const lookupAssets = function (
});
}

// Remap `metadata.extension_uid` anywhere in the entry DATA to the destination app's
// extension_uid via the marketplace_apps mapping (`installedExtensions`).
//
// is_asset custom-field values (e.g. `image_presets`) carry a stack-scoped
// `metadata.extension_uid` in the entry data. Left unmapped, the reference is orphaned on
// the destination and a later audit strips it as a missing reference (silent data loss).
// These fields can be nested inside group / global_field / blocks, and an imported
// content-type schema may represent a global field as a reference-only stub (no expanded
// `schema`) — so this walks the entry DATA directly instead of relying on the schema shape.
// Only known source->destination app UIDs are remapped, so unrelated values are untouched.
function remapEntryMetadataExtensionUids(node: any) {
if (!node || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const item of node) remapEntryMetadataExtensionUids(item);
return;
}
const currentUid = node.metadata?.extension_uid;
if (currentUid && installedExtensions && installedExtensions[currentUid]) {
log.debug(`Mapping metadata extension UID: ${currentUid} -> ${installedExtensions[currentUid]}`);
node.metadata.extension_uid = installedExtensions[currentUid];
}
for (const key of Object.keys(node)) {
remapEntryMetadataExtensionUids(node[key]);
}
}

function findAssetIdsFromHtmlRte(entryObj: any, ctSchema: any) {
log.debug('Extracting asset UIDs from HTML RTE fields');
const regex = /<img asset_uid=\\"([^"]+)\\"/g;
Expand Down Expand Up @@ -266,6 +296,11 @@ export const lookupAssets = function (
}

find(data.content_type.schema, data.entry);
// Remap marketplace-app extension UIDs in entry data (e.g. image_presets' metadata.extension_uid)
// once, unconditionally. find() only reaches the is_asset branch when it can descend the schema,
// so a reference-only global_field stub (no expanded schema) would otherwise be missed. This pass
// is schema-independent and idempotent (already-mapped UIDs are no-ops on re-run).
remapEntryMetadataExtensionUids(data.entry);
// findFileUrls scans the whole entry object, but is only triggered inside find() when a
// text field has markdown/rich_text_type metadata. Content types with no such fields
// (e.g. those storing asset URLs in plain text fields) never call findFileUrls, so URLs
Expand Down
Loading

Back | FazBrowse Home | New Git URL