From 555a73fcb263aeaff3f8eeaf7a53927ffb419a10 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Wed, 26 Aug 2026 11:40:55 +0530 Subject: [PATCH] fix(import): survive marketplace app config decrypt failure without aborting the module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `updateAppsConfig` called `nodeCrypto.decrypt(configuration)` inline as the argument to `.setConfiguration(...)` / `.setServerConfig(...)`. A bad-decrypt (e.g. ERR_OSSL_BAD_DECRYPT when an app's exported `configuration` was encrypted with a different key) therefore threw synchronously — outside the promise chain's `.catch` — and propagated up through `installApps` (which re-throws), aborting the entire marketplace-apps module. Because the module aborted, `mapper/marketplace_apps/uid-mapping.json` was never written. Global fields / content types (and entry data) then had no marketplace extension mapping to remap against, so their imports failed with "extension_uid ... does not exist in destination stack". Fix: decrypt inside a try/catch for both configuration and server_configuration. On failure, log a warning and skip only that app's config push; the app stays installed and its extension_uid mapping is still recorded, so downstream GF/CT/entry remapping works and the uid-mapping file is written. Co-Authored-By: Claude Opus 4.8 --- .../src/import/modules/marketplace-apps.ts | 110 ++++++++++++------ 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/packages/contentstack-import/src/import/modules/marketplace-apps.ts b/packages/contentstack-import/src/import/modules/marketplace-apps.ts index cb150583a..e418a4256 100644 --- a/packages/contentstack-import/src/import/modules/marketplace-apps.ts +++ b/packages/contentstack-import/src/import/modules/marketplace-apps.ts @@ -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); + }); + } } }