diff --git a/.talismanrc b/.talismanrc index 3f80d4430..2da7e187e 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,4 +1,4 @@ fileignoreconfig: - filename: pnpm-lock.yaml - checksum: ce5abaaafcfa33bb71e4af691eb1f5786b7851bfb7f936712374251cbffe2a32 + checksum: 48a1b9aca69c8decfed0f5333c8f4a8fa4689f1cb9c00a91dd18532418ea910f version: '1.0' diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index d0e8e55df..d4589a633 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-bootstrap", "description": "Bootstrap contentstack apps", - "version": "1.19.1", + "version": "1.19.2", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -16,7 +16,7 @@ "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, "dependencies": { - "@contentstack/cli-cm-seed": "~1.15.1", + "@contentstack/cli-cm-seed": "~1.15.2", "@contentstack/cli-command": "~1.8.1", "@contentstack/cli-config": "~1.20.2", "@contentstack/cli-utilities": "~1.18.2", diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index b305d768a..242924f8f 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -1,13 +1,13 @@ { "name": "@contentstack/cli-cm-clone", "description": "Contentstack stack clone plugin", - "version": "1.21.2", + "version": "1.21.3", "author": "Contentstack", "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues", "dependencies": { "@colors/colors": "^1.6.0", - "@contentstack/cli-cm-export": "~1.24.2", - "@contentstack/cli-cm-import": "~1.32.2", + "@contentstack/cli-cm-export": "~1.25.0", + "@contentstack/cli-cm-import": "~1.33.0", "@contentstack/cli-command": "~1.8.1", "@contentstack/cli-utilities": "~1.18.2", "@oclif/core": "^4.10.5", diff --git a/packages/contentstack-export/package.json b/packages/contentstack-export/package.json index 610f2d0e0..94e9024c2 100644 --- a/packages/contentstack-export/package.json +++ b/packages/contentstack-export/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-export", "description": "Contentstack CLI plugin to export content from stack", - "version": "1.24.2", + "version": "1.25.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-export/src/commands/cm/stacks/export.ts b/packages/contentstack-export/src/commands/cm/stacks/export.ts index 15f88c573..4bd542e6d 100644 --- a/packages/contentstack-export/src/commands/cm/stacks/export.ts +++ b/packages/contentstack-export/src/commands/cm/stacks/export.ts @@ -121,14 +121,14 @@ export default class ExportCommand extends Command { try { const { flags } = await this.parse(ExportCommand); const exportConfig = await setupExportConfig(flags); - + // Store apiKey in configHandler for session.json (return value not needed) createLogContext( this.context?.info?.command || 'cm:stacks:export', exportConfig.apiKey, - exportConfig.authenticationMethod + exportConfig.authenticationMethod, ); - + // For log entries, only pass module (other fields are in session.json) exportConfig.context = { module: '' }; //log.info(`Using Cli Version: ${this.context?.cliVersion}`, exportConfig.context); @@ -143,9 +143,7 @@ export default class ExportCommand extends Command { if (!exportConfig.branches?.length) { writeExportMetaFile(exportConfig); } - log.success( - `The content of the stack ${exportConfig.apiKey} has been exported successfully!`, - ); + log.success(`The content of the stack ${exportConfig.apiKey} has been exported successfully!`); log.info(`The exported content has been stored at '${exportDir}'.`, exportConfig.context); log.success(`The log has been stored at '${getLogPath()}'.`, exportConfig.context); } catch (error) { @@ -154,7 +152,6 @@ export default class ExportCommand extends Command { } } - // Assign values to exportConfig private assignExportConfig(exportConfig: ExportConfig): void { // Note setting host to create cma client diff --git a/packages/contentstack-export/src/config/index.ts b/packages/contentstack-export/src/config/index.ts index 85aa028fa..e3c4d12a2 100644 --- a/packages/contentstack-export/src/config/index.ts +++ b/packages/contentstack-export/src/config/index.ts @@ -36,6 +36,7 @@ const config: DefaultConfig = { 'content-types', 'custom-roles', 'workflows', + 'publishing-rules', 'personalize', 'entries', 'labels', @@ -86,6 +87,11 @@ const config: DefaultConfig = { fileName: 'workflows.json', invalidKeys: ['stackHeaders', 'urlPath', 'created_at', 'updated_at', 'created_by', 'updated_by'], }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['stackHeaders', 'urlPath', 'created_at', 'updated_at', 'created_by', 'updated_by'], + }, globalfields: { dirName: 'global_fields', fileName: 'globalfields.json', diff --git a/packages/contentstack-export/src/export/modules/content-types.ts b/packages/contentstack-export/src/export/modules/content-types.ts index ad571929d..7baf2b4ca 100644 --- a/packages/contentstack-export/src/export/modules/content-types.ts +++ b/packages/contentstack-export/src/export/modules/content-types.ts @@ -1,11 +1,5 @@ import * as path from 'path'; -import { - ContentstackClient, - handleAndLogError, - messageHandler, - log, - sanitizePath, -} from '@contentstack/cli-utilities'; +import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; import BaseClass from './base-class'; import { fsUtil, executeTask } from '../../utils'; @@ -86,7 +80,9 @@ export default class ContentTypesExport extends BaseClass { const contentTypeSearchResponse = await this.stackAPIClient.contentType().query(this.qs).find(); log.debug( - `Fetched ${contentTypeSearchResponse.items?.length || 0} content types out of total ${contentTypeSearchResponse.count}`, + `Fetched ${contentTypeSearchResponse.items?.length || 0} content types out of total ${ + contentTypeSearchResponse.count + }`, this.exportConfig.context, ); diff --git a/packages/contentstack-export/src/export/modules/custom-roles.ts b/packages/contentstack-export/src/export/modules/custom-roles.ts index 1d9d10529..6db21485a 100644 --- a/packages/contentstack-export/src/export/modules/custom-roles.ts +++ b/packages/contentstack-export/src/export/modules/custom-roles.ts @@ -37,23 +37,26 @@ export default class ExportCustomRoles extends BaseClass { this.customRolesConfig.dirName, ); log.debug(`Custom roles folder path is: ${this.rolesFolderPath}`, this.exportConfig.context); - + await fsUtil.makeDirectory(this.rolesFolderPath); log.debug('Custom roles directory created.', this.exportConfig.context); - + this.customRolesLocalesFilepath = pResolve(this.rolesFolderPath, this.customRolesConfig.customRolesLocalesFileName); log.debug(`Custom roles locales file path is: ${this.customRolesLocalesFilepath}`, this.exportConfig.context); - + await this.getCustomRoles(); await this.getLocales(); await this.getCustomRolesLocales(); - - log.debug(`Custom roles export completed. Total custom roles: ${Object.keys(this.customRoles).length}`, this.exportConfig.context); + + log.debug( + `Custom roles export completed. Total custom roles: ${Object.keys(this.customRoles).length}`, + this.exportConfig.context, + ); } async getCustomRoles(): Promise { log.debug('Fetching all roles from the stack...', this.exportConfig.context); - + const roles = await this.stack .role() .fetchAll({ include_rules: true, include_permissions: true }) @@ -65,9 +68,12 @@ export default class ExportCustomRoles extends BaseClass { log.debug('An error occurred while fetching roles.', this.exportConfig.context); return handleAndLogError(err, { ...this.exportConfig.context }); }); - + const customRoles = roles.items.filter((role: any) => !this.existingRoles[role.name]); - log.debug(`Found ${customRoles.length} custom roles from ${roles.items?.length || 0} total roles.`, this.exportConfig.context); + log.debug( + `Found ${customRoles.length} custom roles from ${roles.items?.length || 0} total roles.`, + this.exportConfig.context, + ); if (!customRoles.length) { log.info(messageHandler.parse('ROLES_NO_CUSTOM_ROLES'), this.exportConfig.context); @@ -79,7 +85,7 @@ export default class ExportCustomRoles extends BaseClass { log.info(messageHandler.parse('ROLES_EXPORTING_ROLE', role?.name), this.exportConfig.context); this.customRoles[role.uid] = role; }); - + const customRolesFilePath = pResolve(this.rolesFolderPath, this.customRolesConfig.fileName); log.debug(`Writing custom roles to: ${customRolesFilePath}.`, this.exportConfig.context); fsUtil.writeFile(customRolesFilePath, this.customRoles); @@ -87,7 +93,7 @@ export default class ExportCustomRoles extends BaseClass { async getLocales() { log.debug('Fetching locales for custom roles mapping...', this.exportConfig.context); - + const locales = await this.stack .locale() .query({}) @@ -100,25 +106,28 @@ export default class ExportCustomRoles extends BaseClass { log.debug('An error occurred while fetching locales.', this.exportConfig.context); return handleAndLogError(err, { ...this.exportConfig.context }); }); - + for (const locale of locales.items) { log.debug(`Mapping locale: ${locale?.name} (${locale?.uid})`, this.exportConfig.context); this.sourceLocalesMap[locale.uid] = locale; } - + log.debug(`Mapped ${Object.keys(this.sourceLocalesMap).length} source locales.`, this.exportConfig.context); } async getCustomRolesLocales() { log.debug('Processing custom roles locales mapping...', this.exportConfig.context); - + for (const role of values(this.customRoles)) { const customRole = role as Record; log.debug(`Processing locales for custom role: ${customRole?.name}`, this.exportConfig.context); - + const rulesLocales = find(customRole.rules, (rule: any) => rule.module === 'locale'); if (rulesLocales?.locales?.length) { - log.debug(`Found ${rulesLocales.locales.length} locales for the role: ${customRole?.name}.`, this.exportConfig.context); + log.debug( + `Found ${rulesLocales.locales.length} locales for the role: ${customRole?.name}.`, + this.exportConfig.context, + ); forEach(rulesLocales.locales, (locale: any) => { log.debug(`Adding locale ${locale} to the custom roles mapping.`, this.exportConfig.context); this.localesMap[locale] = 1; @@ -128,7 +137,7 @@ export default class ExportCustomRoles extends BaseClass { if (keys(this.localesMap)?.length) { log.debug(`Processing ${Object.keys(this.localesMap).length} mapped locales.`, this.exportConfig.context); - + for (const locale in this.localesMap) { if (this.sourceLocalesMap[locale] !== undefined) { const sourceLocale = this.sourceLocalesMap[locale] as Record; @@ -137,7 +146,7 @@ export default class ExportCustomRoles extends BaseClass { } this.localesMap[locale] = this.sourceLocalesMap[locale]; } - + log.debug(`Writing custom roles locales to: ${this.customRolesLocalesFilepath}.`, this.exportConfig.context); fsUtil.writeFile(this.customRolesLocalesFilepath, this.localesMap); } else { diff --git a/packages/contentstack-export/src/export/modules/environments.ts b/packages/contentstack-export/src/export/modules/environments.ts index 85e9f8af3..43c3745df 100644 --- a/packages/contentstack-export/src/export/modules/environments.ts +++ b/packages/contentstack-export/src/export/modules/environments.ts @@ -35,7 +35,7 @@ export default class ExportEnvironments extends BaseClass { await fsUtil.makeDirectory(this.environmentsFolderPath); log.debug('Environments directory created.', this.exportConfig.context); - + await this.getEnvironments(); log.debug(`Retrieved ${Object.keys(this.environments).length} environments.`, this.exportConfig.context); @@ -59,9 +59,9 @@ export default class ExportEnvironments extends BaseClass { } else { log.debug('Fetching environments with initial query...', this.exportConfig.context); } - + log.debug(`Query parameters: ${JSON.stringify(this.qs)}`, this.exportConfig.context); - + await this.stack .environment() .query(this.qs) @@ -69,7 +69,7 @@ export default class ExportEnvironments extends BaseClass { .then(async (data: any) => { const { items, count } = data; log.debug(`Fetched ${items?.length || 0} environments out of ${count} total.`, this.exportConfig.context); - + if (items?.length) { log.debug(`Processing ${items.length} environments.`, this.exportConfig.context); this.sanitizeAttribs(items); @@ -92,16 +92,19 @@ export default class ExportEnvironments extends BaseClass { sanitizeAttribs(environments: Record[]) { log.debug(`Sanitizing ${environments.length} environments...`, this.exportConfig.context); - + for (let index = 0; index < environments?.length; index++) { const extUid = environments[index].uid; const envName = environments[index]?.name; log.debug(`Processing environment: ${envName} (${extUid})`, this.exportConfig.context); - + this.environments[extUid] = omit(environments[index], ['ACL']); - log.success(messageHandler.parse('ENVIRONMENT_EXPORT_SUCCESS', envName ), this.exportConfig.context); + log.success(messageHandler.parse('ENVIRONMENT_EXPORT_SUCCESS', envName), this.exportConfig.context); } - - log.debug(`Sanitization complete. Total environments processed: ${Object.keys(this.environments).length}`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total environments processed: ${Object.keys(this.environments).length}`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/extensions.ts b/packages/contentstack-export/src/export/modules/extensions.ts index 5da610ec2..efe3e1060 100644 --- a/packages/contentstack-export/src/export/modules/extensions.ts +++ b/packages/contentstack-export/src/export/modules/extensions.ts @@ -27,7 +27,7 @@ export default class ExportExtensions extends BaseClass { async start(): Promise { log.debug('Starting extensions export process...', this.exportConfig.context); - + this.extensionsFolderPath = pResolve( this.exportConfig.data, this.exportConfig.branchName || '', @@ -37,7 +37,7 @@ export default class ExportExtensions extends BaseClass { await fsUtil.makeDirectory(this.extensionsFolderPath); log.debug('Extensions directory created.', this.exportConfig.context); - + await this.getExtensions(); log.debug(`Retrieved ${Object.keys(this.extensions).length} extensions.`, this.exportConfig.context); @@ -48,7 +48,7 @@ export default class ExportExtensions extends BaseClass { log.debug(`Writing extensions to: ${extensionsFilePath}.`, this.exportConfig.context); fsUtil.writeFile(extensionsFilePath, this.extensions); log.success( - messageHandler.parse('EXTENSION_EXPORT_COMPLETE', Object.keys(this.extensions).length ), + messageHandler.parse('EXTENSION_EXPORT_COMPLETE', Object.keys(this.extensions).length), this.exportConfig.context, ); } @@ -61,9 +61,9 @@ export default class ExportExtensions extends BaseClass { } else { log.debug('Fetching extensions with initial query...', this.exportConfig.context); } - + log.debug(`Query parameters: ${JSON.stringify(this.qs)}.`, this.exportConfig.context); - + await this.stack .extension() .query(this.qs) @@ -71,7 +71,7 @@ export default class ExportExtensions extends BaseClass { .then(async (data: any) => { const { items, count } = data; log.debug(`Fetched ${items?.length || 0} extensions out of ${count}.`, this.exportConfig.context); - + if (items?.length) { log.debug(`Processing ${items.length} extensions...`, this.exportConfig.context); this.sanitizeAttribs(items); @@ -94,16 +94,19 @@ export default class ExportExtensions extends BaseClass { sanitizeAttribs(extensions: Record[]) { log.debug(`Sanitizing ${extensions.length} extensions...`, this.exportConfig.context); - + for (let index = 0; index < extensions?.length; index++) { const extUid = extensions[index].uid; const extTitle = extensions[index]?.title; log.debug(`Processing extension: '${extTitle}' (UID: ${extUid})...`, this.exportConfig.context); - + this.extensions[extUid] = omit(extensions[index], ['SYS_ACL']); log.info(messageHandler.parse('EXTENSION_EXPORT_SUCCESS', extTitle), this.exportConfig.context); } - - log.debug(`Sanitization complete. Total extensions processed: ${Object.keys(this.extensions).length}.`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total extensions processed: ${Object.keys(this.extensions).length}.`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/global-fields.ts b/packages/contentstack-export/src/export/modules/global-fields.ts index 421665cfc..aae016f7d 100644 --- a/packages/contentstack-export/src/export/modules/global-fields.ts +++ b/packages/contentstack-export/src/export/modules/global-fields.ts @@ -1,11 +1,5 @@ import * as path from 'path'; -import { - ContentstackClient, - handleAndLogError, - messageHandler, - log, - sanitizePath, -} from '@contentstack/cli-utilities'; +import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; import { fsUtil } from '../../utils'; import { ExportConfig, ModuleClassParams } from '../../types'; @@ -56,17 +50,17 @@ export default class GlobalFieldsExport extends BaseClass { async start() { try { log.debug('Starting export process for global fields...', this.exportConfig.context); - log.debug(`Global fields directory path: '${this.globalFieldsDirPath}'`, this.exportConfig.context); + log.debug(`Global fields directory path: '${this.globalFieldsDirPath}'`, this.exportConfig.context); await fsUtil.makeDirectory(this.globalFieldsDirPath); log.debug('Created global fields directory.', this.exportConfig.context); - + await this.getGlobalFields(); log.debug(`Retrieved ${this.globalFields.length} global fields.`, this.exportConfig.context); - + const globalFieldsFilePath = path.join(this.globalFieldsDirPath, this.globalFieldsConfig.fileName); log.debug(`Writing global fields to: '${globalFieldsFilePath}'`, this.exportConfig.context); fsUtil.writeFile(globalFieldsFilePath, this.globalFields); - + log.success( messageHandler.parse('GLOBAL_FIELDS_EXPORT_COMPLETE', this.globalFields.length), this.exportConfig.context, @@ -83,11 +77,16 @@ export default class GlobalFieldsExport extends BaseClass { log.debug(`Fetching global fields with skip: ${skip}.`, this.exportConfig.context); } log.debug(`Query parameters: ${JSON.stringify(this.qs)}.`, this.exportConfig.context); - + let globalFieldsFetchResponse = await this.stackAPIClient.globalField({ api_version: '3.2' }).query(this.qs).find(); - - log.debug(`Fetched ${globalFieldsFetchResponse.items?.length || 0} global fields out of ${globalFieldsFetchResponse.count}.`, this.exportConfig.context); - + + log.debug( + `Fetched ${globalFieldsFetchResponse.items?.length || 0} global fields out of ${ + globalFieldsFetchResponse.count + }.`, + this.exportConfig.context, + ); + if (Array.isArray(globalFieldsFetchResponse.items) && globalFieldsFetchResponse.items.length > 0) { log.debug(`Processing ${globalFieldsFetchResponse.items.length} global fields...`, this.exportConfig.context); this.sanitizeAttribs(globalFieldsFetchResponse.items); @@ -105,10 +104,10 @@ export default class GlobalFieldsExport extends BaseClass { sanitizeAttribs(globalFields: Record[]) { log.debug(`Sanitizing ${globalFields.length} global fields...`, this.exportConfig.context); - + globalFields.forEach((globalField: Record) => { log.debug(`Processing global field: '${globalField.uid || 'unknown'}'...`, this.exportConfig.context); - + for (let key in globalField) { if (this.globalFieldsConfig.validKeys.indexOf(key) === -1) { delete globalField[key]; @@ -116,7 +115,10 @@ export default class GlobalFieldsExport extends BaseClass { } this.globalFields.push(globalField); }); - - log.debug(`Sanitization complete. Total global fields processed: ${this.globalFields.length}.`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total global fields processed: ${this.globalFields.length}.`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/index.ts b/packages/contentstack-export/src/export/modules/index.ts index f13bc4fa3..ca0896b44 100644 --- a/packages/contentstack-export/src/export/modules/index.ts +++ b/packages/contentstack-export/src/export/modules/index.ts @@ -11,7 +11,7 @@ export default async function startModuleExport(modulePayload: ModuleClassParams ...modulePayload.exportConfig.context, module: modulePayload.moduleName, }); - throw error; + throw error; } } diff --git a/packages/contentstack-export/src/export/modules/labels.ts b/packages/contentstack-export/src/export/modules/labels.ts index 414f13077..c639aca60 100644 --- a/packages/contentstack-export/src/export/modules/labels.ts +++ b/packages/contentstack-export/src/export/modules/labels.ts @@ -26,7 +26,7 @@ export default class ExportLabels extends BaseClass { async start(): Promise { log.debug('Starting export process for labels...', this.exportConfig.context); - + this.labelsFolderPath = pResolve( this.exportConfig.data, this.exportConfig.branchName || '', @@ -36,10 +36,10 @@ export default class ExportLabels extends BaseClass { await fsUtil.makeDirectory(this.labelsFolderPath); log.debug('Created labels directory.', this.exportConfig.context); - + await this.getLabels(); log.debug(`Retrieved ${Object.keys(this.labels).length} labels.`, this.exportConfig.context); - + if (this.labels === undefined || isEmpty(this.labels)) { log.info(messageHandler.parse('LABELS_NOT_FOUND'), this.exportConfig.context); } else { @@ -60,9 +60,9 @@ export default class ExportLabels extends BaseClass { } else { log.debug('Fetching labels with initial query...', this.exportConfig.context); } - + log.debug(`Query parameters: ${JSON.stringify(this.qs)}.`, this.exportConfig.context); - + await this.stack .label() .query(this.qs) @@ -70,7 +70,7 @@ export default class ExportLabels extends BaseClass { .then(async (data: any) => { const { items, count } = data; log.debug(`Fetched ${items?.length || 0} labels out of ${count}.`, this.exportConfig.context); - + if (items?.length) { log.debug(`Processing ${items.length} labels...`, this.exportConfig.context); this.sanitizeAttribs(items); @@ -93,16 +93,19 @@ export default class ExportLabels extends BaseClass { sanitizeAttribs(labels: Record[]) { log.debug(`Sanitizing ${labels.length} labels...`, this.exportConfig.context); - + for (let index = 0; index < labels?.length; index++) { const labelUid = labels[index].uid; const labelName = labels[index]?.name; log.debug(`Processing label: '${labelName}' (UID: ${labelUid})...`, this.exportConfig.context); - + this.labels[labelUid] = omit(labels[index], this.labelConfig.invalidKeys); log.info(messageHandler.parse('LABEL_EXPORT_SUCCESS', labelName), this.exportConfig.context); } - - log.debug(`Sanitization complete. Total labels processed: ${Object.keys(this.labels).length}.`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total labels processed: ${Object.keys(this.labels).length}.`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/locales.ts b/packages/contentstack-export/src/export/modules/locales.ts index 97f8d16fe..4ff73240b 100644 --- a/packages/contentstack-export/src/export/modules/locales.ts +++ b/packages/contentstack-export/src/export/modules/locales.ts @@ -1,11 +1,5 @@ import * as path from 'path'; -import { - ContentstackClient, - handleAndLogError, - messageHandler, - log, - sanitizePath, -} from '@contentstack/cli-utilities'; +import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePath } from '@contentstack/cli-utilities'; import { fsUtil } from '../../utils'; import BaseClass from './base-class'; @@ -61,22 +55,27 @@ export default class LocaleExport extends BaseClass { try { log.debug('Starting export process for locales...', this.exportConfig.context); log.debug(`Locales path: '${this.localesPath}'`, this.exportConfig.context); - + await fsUtil.makeDirectory(this.localesPath); log.debug('Created locales directory.', this.exportConfig.context); - + await this.getLocales(); - log.debug(`Retrieved ${Object.keys(this.locales).length} locales and ${Object.keys(this.masterLocale).length} master locales.`, this.exportConfig.context); - + log.debug( + `Retrieved ${Object.keys(this.locales).length} locales and ${ + Object.keys(this.masterLocale).length + } master locales.`, + this.exportConfig.context, + ); + const localesFilePath = path.join(this.localesPath, this.localeConfig.fileName); const masterLocaleFilePath = path.join(this.localesPath, this.masterLocaleConfig.fileName); - + log.debug(`Writing locales to: '${localesFilePath}'`, this.exportConfig.context); fsUtil.writeFile(localesFilePath, this.locales); - + log.debug(`Writing master locale to: '${masterLocaleFilePath}'`, this.exportConfig.context); fsUtil.writeFile(masterLocaleFilePath, this.masterLocale); - + log.success( messageHandler.parse( 'LOCALES_EXPORT_COMPLETE', @@ -97,15 +96,18 @@ export default class LocaleExport extends BaseClass { log.debug(`Fetching locales with skip: ${skip}.`, this.exportConfig.context); } log.debug(`Query parameters: ${JSON.stringify(this.qs)}.`, this.exportConfig.context); - + let localesFetchResponse = await this.stackAPIClient.locale().query(this.qs).find(); - - log.debug(`Fetched ${localesFetchResponse.items?.length || 0} locales out of ${localesFetchResponse.count}.`, this.exportConfig.context); - + + log.debug( + `Fetched ${localesFetchResponse.items?.length || 0} locales out of ${localesFetchResponse.count}.`, + this.exportConfig.context, + ); + if (Array.isArray(localesFetchResponse.items) && localesFetchResponse.items.length > 0) { log.debug(`Processing ${localesFetchResponse.items.length} locales...`, this.exportConfig.context); this.sanitizeAttribs(localesFetchResponse.items); - + skip += this.localeConfig.limit || 100; if (skip > localesFetchResponse.count) { log.debug('Completed fetching all locales.', this.exportConfig.context); @@ -120,7 +122,7 @@ export default class LocaleExport extends BaseClass { sanitizeAttribs(locales: Record[]) { log.debug(`Sanitizing ${locales.length} locales...`, this.exportConfig.context); - + locales.forEach((locale: Record) => { for (let key in locale) { if (this.localeConfig.requiredKeys.indexOf(key) === -1) { @@ -136,7 +138,12 @@ export default class LocaleExport extends BaseClass { this.locales[locale.uid] = locale; } }); - - log.debug(`Sanitization complete. Master locales: ${Object.keys(this.masterLocale).length}, Regular locales: ${Object.keys(this.locales).length}.`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Master locales: ${Object.keys(this.masterLocale).length}, Regular locales: ${ + Object.keys(this.locales).length + }.`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/marketplace-apps.ts b/packages/contentstack-export/src/export/modules/marketplace-apps.ts index 094c05a22..50cd0b761 100644 --- a/packages/contentstack-export/src/export/modules/marketplace-apps.ts +++ b/packages/contentstack-export/src/export/modules/marketplace-apps.ts @@ -39,7 +39,7 @@ export default class ExportMarketplaceApps { async start(): Promise { log.debug('Starting export process for Marketplace Apps...', this.exportConfig.context); - + if (!isAuthenticated()) { cliux.print( 'WARNING!!! To export Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in', @@ -54,13 +54,13 @@ export default class ExportMarketplaceApps { this.marketplaceAppConfig.dirName, ); log.debug(`Marketplace apps folder path: '${this.marketplaceAppPath}'`, this.exportConfig.context); - + await fsUtil.makeDirectory(this.marketplaceAppPath); log.debug('Created Marketplace Apps directory.', this.exportConfig.context); - + this.developerHubBaseUrl = this.exportConfig.developerHubBaseUrl || (await getDeveloperHubUrl(this.exportConfig)); log.debug(`Developer Hub base URL: '${this.developerHubBaseUrl}'`, this.exportConfig.context); - + this.exportConfig.org_uid = await getOrgUid(this.exportConfig); this.query = { target_uids: this.exportConfig.source_stack }; log.debug(`Organization UID: '${this.exportConfig.org_uid}'.`, this.exportConfig.context); @@ -90,10 +90,10 @@ export default class ExportMarketplaceApps { this.query.installation_uids = externalQuery.installation_uid?.$in?.join(','); } } - + await this.getStackSpecificApps(); log.debug(`Retrieved ${this.installedApps.length} stack-specific apps.`, this.exportConfig.context); - + await this.getAppManifestAndAppConfig(); log.debug('Completed app manifest and configuration processing.', this.exportConfig.context); @@ -109,7 +109,7 @@ export default class ExportMarketplaceApps { } return app; }); - + log.debug(`Processed ${this.installedApps.length} Marketplace Apps.`, this.exportConfig.context); } @@ -122,7 +122,7 @@ export default class ExportMarketplaceApps { log.info(messageHandler.parse('MARKETPLACE_APPS_NOT_FOUND'), this.exportConfig.context); } else { log.debug(`Processing ${this.installedApps.length} installed apps...`, this.exportConfig.context); - + for (const [index, app] of entries(this.installedApps)) { if (app.manifest.visibility === 'private') { log.debug(`Processing private app manifest: '${app.manifest.name}'...`, this.exportConfig.context); @@ -131,7 +131,10 @@ export default class ExportMarketplaceApps { } for (const [index, app] of entries(this.installedApps)) { - log.debug(`Processing app configurations for: '${app.manifest?.name || app.uid}'...`, this.exportConfig.context); + log.debug( + `Processing app configurations for: '${app.manifest?.name || app.uid}'...`, + this.exportConfig.context, + ); await this.getAppConfigurations(+index, app); } @@ -156,14 +159,20 @@ export default class ExportMarketplaceApps { * app's manifest. */ async getPrivateAppsManifest(index: number, appInstallation: Installation) { - log.debug(`Fetching private app manifest for: '${appInstallation.manifest.name}' (UID: ${appInstallation.manifest.uid})...`, this.exportConfig.context); - + log.debug( + `Fetching private app manifest for: '${appInstallation.manifest.name}' (UID: ${appInstallation.manifest.uid})...`, + this.exportConfig.context, + ); + const manifest = await this.appSdk .marketplace(this.exportConfig.org_uid) .app(appInstallation.manifest.uid) .fetch({ include_oauth: true }) .catch((error) => { - log.debug(`Failed to fetch private app manifest for: '${appInstallation.manifest.name}'.`, this.exportConfig.context); + log.debug( + `Failed to fetch private app manifest for: '${appInstallation.manifest.name}'.`, + this.exportConfig.context, + ); handleAndLogError( error, { @@ -174,7 +183,10 @@ export default class ExportMarketplaceApps { }); if (manifest) { - log.debug(`Successfully fetched private app manifest for: '${appInstallation.manifest.name}'.`, this.exportConfig.context); + log.debug( + `Successfully fetched private app manifest for: '${appInstallation.manifest.name}'.`, + this.exportConfig.context, + ); this.installedApps[index].manifest = manifest as unknown as Manifest; } } @@ -205,7 +217,7 @@ export default class ExportMarketplaceApps { if (has(data, 'server_configuration') || has(data, 'configuration')) { log.debug(`Found configuration data for app: '${app}'.`, this.exportConfig.context); - + if (!this.nodeCrypto && (has(data, 'server_configuration') || has(data, 'configuration'))) { log.debug(`Initializing NodeCrypto for app: '${app}'...`, this.exportConfig.context); this.nodeCrypto = await createNodeCryptoInstance(this.exportConfig); @@ -254,7 +266,7 @@ export default class ExportMarketplaceApps { * the API. In this code, it is initially set to 0, indicating that no items should be skipped in */ async getStackSpecificApps(skip = 0) { - log.debug(`Fetching stack-specific apps with skip: ${skip}`, this.exportConfig.context); + log.debug(`Fetching stack-specific apps with skip: ${skip}`, this.exportConfig.context); const collection = await this.appSdk .marketplace(this.exportConfig.org_uid) .installation() @@ -269,7 +281,7 @@ export default class ExportMarketplaceApps { if (collection) { const { items: apps, count } = collection; log.debug(`Fetched ${apps?.length || 0} apps out of ${count}.`, this.exportConfig.context); - + // NOTE Remove all the chain functions const installation = map(apps, (app) => omitBy(app, (val, _key) => { @@ -277,7 +289,7 @@ export default class ExportMarketplaceApps { return false; }), ) as unknown as Installation[]; - + log.debug(`Processed ${installation.length} app installations.`, this.exportConfig.context); this.installedApps = this.installedApps.concat(installation); diff --git a/packages/contentstack-export/src/export/modules/personalize.ts b/packages/contentstack-export/src/export/modules/personalize.ts index 51656635c..deffcd50b 100644 --- a/packages/contentstack-export/src/export/modules/personalize.ts +++ b/packages/contentstack-export/src/export/modules/personalize.ts @@ -22,27 +22,31 @@ export default class ExportPersonalize { async start(): Promise { try { log.debug('Starting export process for Personalize...', this.exportConfig.context); - + if (!this.personalizeConfig.baseURL[this.exportConfig.region.name]) { log.debug(`Personalize URL not set for region: '${this.exportConfig.region.name}'.`, this.exportConfig.context); log.info(messageHandler.parse('PERSONALIZE_URL_NOT_SET'), this.exportConfig.context); this.exportConfig.personalizationEnabled = false; return; } - + if (this.exportConfig.management_token) { log.debug('Management token detected, skipping personalize export.', this.exportConfig.context); log.info(messageHandler.parse('PERSONALIZE_SKIPPING_WITH_MANAGEMENT_TOKEN'), this.exportConfig.context); this.exportConfig.personalizationEnabled = false; return; } - + log.debug('Starting export process for personalization projects...', this.exportConfig.context); await new ExportProjects(this.exportConfig).start(); - + if (this.exportConfig.personalizationEnabled) { - log.debug('Personalization is enabled, processing personalize modules... ' + this.exportConfig.modules.personalize.exportOrder.join(', '), this.exportConfig.context); - + log.debug( + 'Personalization is enabled, processing personalize modules... ' + + this.exportConfig.modules.personalize.exportOrder.join(', '), + this.exportConfig.context, + ); + const moduleMapper = { events: new ExportEvents(this.exportConfig), attributes: new ExportAttributes(this.exportConfig), @@ -54,23 +58,20 @@ export default class ExportPersonalize { .exportOrder as (keyof typeof moduleMapper)[]; log.debug(`Personalize export order: ${order.join(', ')}.`, this.exportConfig.context); - + for (const module of order) { log.debug(`Processing personalization module: '${module}'...`, this.exportConfig.context); - + if (moduleMapper[module]) { log.debug(`Starting export for module: '${module}'...`, this.exportConfig.context); await moduleMapper[module].start(); log.debug(`Completed export for module: '${module}'.`, this.exportConfig.context); } else { log.debug(`Module not implemented: '${module}'.`, this.exportConfig.context); - log.info( - messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), - this.exportConfig.context, - ); + log.info(messageHandler.parse('PERSONALIZE_MODULE_NOT_IMPLEMENTED', module), this.exportConfig.context); } } - + log.debug('Completed all personalization module exports.', this.exportConfig.context); } else { log.debug('Personalization is disabled, skipping personalize module exports.', this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/publishing-rules.ts b/packages/contentstack-export/src/export/modules/publishing-rules.ts new file mode 100644 index 000000000..86be31dee --- /dev/null +++ b/packages/contentstack-export/src/export/modules/publishing-rules.ts @@ -0,0 +1,83 @@ +import omit from 'lodash/omit'; +import isEmpty from 'lodash/isEmpty'; +import { resolve as pResolve } from 'node:path'; +import { handleAndLogError, log } from '@contentstack/cli-utilities'; + +import BaseClass from './base-class'; +import { fsUtil } from '../../utils'; +import { PublishingRulesConfig, ModuleClassParams } from '../../types'; + +export default class ExportPublishingRules extends BaseClass { + private readonly publishingRules: Record> = {}; + private readonly publishingRulesConfig: PublishingRulesConfig; + private publishingRulesFolderPath: string; + private readonly qs: { include_count: boolean; skip?: number }; + + constructor({ exportConfig, stackAPIClient }: ModuleClassParams) { + super({ exportConfig, stackAPIClient }); + this.publishingRulesConfig = exportConfig.modules['publishing-rules']; + this.qs = { include_count: true }; + this.exportConfig.context.module = 'publishing-rules'; + } + + async start(): Promise { + this.publishingRulesFolderPath = pResolve( + this.exportConfig.data, + this.exportConfig.branchName || '', + this.publishingRulesConfig.dirName, + ); + log.debug(`Publishing rules folder path: ${this.publishingRulesFolderPath}`, this.exportConfig.context); + + await fsUtil.makeDirectory(this.publishingRulesFolderPath); + log.debug('Created publishing rules directory', this.exportConfig.context); + + await this.fetchAllPublishingRules(); + + if (isEmpty(this.publishingRules)) { + log.info('No Publishing Rules found', this.exportConfig.context); + return; + } + + const outPath = pResolve(this.publishingRulesFolderPath, this.publishingRulesConfig.fileName); + fsUtil.writeFile(outPath, this.publishingRules); + log.success( + `Publishing rules exported successfully! Total count: ${Object.keys(this.publishingRules).length}`, + this.exportConfig.context, + ); + } + + private async fetchAllPublishingRules(skip = 0): Promise { + try { + if (skip > 0) { + this.qs.skip = skip; + } + + const data: { items?: Record[]; count?: number } = await this.stack + .workflow() + .publishRule() + .fetchAll(this.qs); + + const items = data.items ?? []; + const total = data.count ?? items.length; + + if (!items.length) { + log.debug('No publishing rules returned for this page', this.exportConfig.context); + return; + } + + for (const rule of items) { + const uid = rule.uid as string | undefined; + if (uid) { + this.publishingRules[uid] = omit(rule, this.publishingRulesConfig.invalidKeys) as Record; + } + } + + const nextSkip = skip + items.length; + if (nextSkip < total) { + await this.fetchAllPublishingRules(nextSkip); + } + } catch (error: unknown) { + handleAndLogError(error as Error, { ...this.exportConfig.context }); + } + } +} diff --git a/packages/contentstack-export/src/export/modules/stack.ts b/packages/contentstack-export/src/export/modules/stack.ts index cb1d75fb0..bfe6d7c93 100644 --- a/packages/contentstack-export/src/export/modules/stack.ts +++ b/packages/contentstack-export/src/export/modules/stack.ts @@ -77,7 +77,10 @@ export default class ExportStack extends BaseClass { .stack({ api_key: this.exportConfig.source_stack }) .fetch() .then((data: any) => { - log.debug(`Successfully fetched stack data for: '${this.exportConfig.source_stack}'.`, this.exportConfig.context); + log.debug( + `Successfully fetched stack data for: '${this.exportConfig.source_stack}'.`, + this.exportConfig.context, + ); return data; }) .catch((error: any) => { @@ -168,7 +171,10 @@ export default class ExportStack extends BaseClass { return resp; }) .catch((error: any) => { - log.debug(`An error occurred while exporting stack: '${this.exportConfig.source_stack}'.`, this.exportConfig.context); + log.debug( + `An error occurred while exporting stack: '${this.exportConfig.source_stack}'.`, + this.exportConfig.context, + ); handleAndLogError(error, { ...this.exportConfig.context }); }); } diff --git a/packages/contentstack-export/src/export/modules/taxonomies.ts b/packages/contentstack-export/src/export/modules/taxonomies.ts index 69acea863..11ed9bb5c 100644 --- a/packages/contentstack-export/src/export/modules/taxonomies.ts +++ b/packages/contentstack-export/src/export/modules/taxonomies.ts @@ -45,7 +45,7 @@ export default class ExportTaxonomies extends BaseClass { async start(): Promise { log.debug('Starting export process for taxonomies...', this.exportConfig.context); - + //create taxonomies folder this.taxonomiesFolderPath = pResolve( this.exportConfig.data, @@ -53,7 +53,7 @@ export default class ExportTaxonomies extends BaseClass { this.taxonomiesConfig.dirName, ); log.debug(`Taxonomies folder path: '${this.taxonomiesFolderPath}'`, this.exportConfig.context); - + await fsUtil.makeDirectory(this.taxonomiesFolderPath); log.debug('Created taxonomies directory.', this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/webhooks.ts b/packages/contentstack-export/src/export/modules/webhooks.ts index 42d657490..ef59a13c2 100644 --- a/packages/contentstack-export/src/export/modules/webhooks.ts +++ b/packages/contentstack-export/src/export/modules/webhooks.ts @@ -27,7 +27,7 @@ export default class ExportWebhooks extends BaseClass { async start(): Promise { log.debug('Starting webhooks export process...', this.exportConfig.context); - + this.webhooksFolderPath = pResolve( this.exportConfig.data, this.exportConfig.branchName || '', @@ -37,10 +37,10 @@ export default class ExportWebhooks extends BaseClass { await fsUtil.makeDirectory(this.webhooksFolderPath); log.debug('Created webhooks directory', this.exportConfig.context); - + await this.getWebhooks(); log.debug(`Retrieved ${Object.keys(this.webhooks).length} webhooks`, this.exportConfig.context); - + if (this.webhooks === undefined || isEmpty(this.webhooks)) { log.info(messageHandler.parse('WEBHOOK_NOT_FOUND'), this.exportConfig.context); } else { @@ -61,7 +61,7 @@ export default class ExportWebhooks extends BaseClass { } else { log.debug('Fetching webhooks with initial query', this.exportConfig.context); } - + log.debug(`Query parameters: ${JSON.stringify(this.qs)}`, this.exportConfig.context); await this.stack @@ -70,7 +70,7 @@ export default class ExportWebhooks extends BaseClass { .then(async (data: any) => { const { items, count } = data; log.debug(`Fetched ${items?.length || 0} webhooks out of total ${count}`, this.exportConfig.context); - + if (items?.length) { log.debug(`Processing ${items.length} webhooks`, this.exportConfig.context); this.sanitizeAttribs(items); @@ -93,16 +93,19 @@ export default class ExportWebhooks extends BaseClass { sanitizeAttribs(webhooks: Record[]) { log.debug(`Sanitizing ${webhooks.length} webhooks`, this.exportConfig.context); - + for (let index = 0; index < webhooks?.length; index++) { const webhookUid = webhooks[index].uid; const webhookName = webhooks[index]?.name; log.debug(`Processing webhook: ${webhookName} (${webhookUid})`, this.exportConfig.context); - + this.webhooks[webhookUid] = omit(webhooks[index], ['SYS_ACL']); log.success(messageHandler.parse('WEBHOOK_EXPORT_SUCCESS', webhookName), this.exportConfig.context); } - - log.debug(`Sanitization complete. Total webhooks processed: ${Object.keys(this.webhooks).length}`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total webhooks processed: ${Object.keys(this.webhooks).length}`, + this.exportConfig.context, + ); } } diff --git a/packages/contentstack-export/src/export/modules/workflows.ts b/packages/contentstack-export/src/export/modules/workflows.ts index 6a9fedb8a..f51b0d34a 100644 --- a/packages/contentstack-export/src/export/modules/workflows.ts +++ b/packages/contentstack-export/src/export/modules/workflows.ts @@ -24,7 +24,7 @@ export default class ExportWorkFlows extends BaseClass { this.exportConfig.context.module = 'workflows'; } - async start(): Promise { + async start(): Promise { this.webhooksFolderPath = pResolve( this.exportConfig.data, this.exportConfig.branchName || '', @@ -34,7 +34,7 @@ export default class ExportWorkFlows extends BaseClass { await fsUtil.makeDirectory(this.webhooksFolderPath); log.debug('Created workflows directory', this.exportConfig.context); - + await this.getWorkflows(); log.debug(`Retrieved ${Object.keys(this.workflows).length} workflows`, this.exportConfig.context); @@ -45,7 +45,7 @@ export default class ExportWorkFlows extends BaseClass { log.debug(`Writing workflows to: ${workflowsFilePath}`, this.exportConfig.context); fsUtil.writeFile(workflowsFilePath, this.workflows); log.success( - messageHandler.parse('WORKFLOW_EXPORT_COMPLETE', Object.keys(this.workflows).length ), + messageHandler.parse('WORKFLOW_EXPORT_COMPLETE', Object.keys(this.workflows).length), this.exportConfig.context, ); } @@ -66,7 +66,7 @@ export default class ExportWorkFlows extends BaseClass { //NOTE - Handle the case where old workflow api is enabled in that case getting responses as objects. const workflowCount = count !== undefined ? count : items.length; log.debug(`Fetched ${items?.length || 0} workflows out of total ${workflowCount}`, this.exportConfig.context); - + if (items?.length) { log.debug(`Processing ${items.length} workflows`, this.exportConfig.context); await this.sanitizeAttribs(items); @@ -89,29 +89,29 @@ export default class ExportWorkFlows extends BaseClass { async sanitizeAttribs(workflows: Record[]) { log.debug(`Sanitizing ${workflows.length} workflows`, this.exportConfig.context); - + for (let index = 0; index < workflows?.length; index++) { const workflowUid = workflows[index].uid; const workflowName = workflows[index]?.name || ''; log.debug(`Processing workflow: ${workflowName} (${workflowUid})`, this.exportConfig.context); - + await this.getWorkflowRoles(workflows[index]); this.workflows[workflowUid] = omit(workflows[index], this.workflowConfig.invalidKeys); - log.success( - messageHandler.parse('WORKFLOW_EXPORT_SUCCESS', workflowName), - this.exportConfig.context, - ); + log.success(messageHandler.parse('WORKFLOW_EXPORT_SUCCESS', workflowName), this.exportConfig.context); } - - log.debug(`Sanitization complete. Total workflows processed: ${Object.keys(this.workflows).length}`, this.exportConfig.context); + + log.debug( + `Sanitization complete. Total workflows processed: ${Object.keys(this.workflows).length}`, + this.exportConfig.context, + ); } async getWorkflowRoles(workflow: Record) { log.debug(`Processing workflow roles for workflow: ${workflow.uid}`, this.exportConfig.context); - + for (const stage of workflow?.workflow_stages) { log.debug(`Processing workflow stage: ${stage.name}`, this.exportConfig.context); - + for (let i = 0; i < stage?.SYS_ACL?.roles?.uids?.length; i++) { const roleUid = stage.SYS_ACL.roles.uids[i]; log.debug(`Fetching role data for role UID: ${roleUid}`, this.exportConfig.context); @@ -123,7 +123,7 @@ export default class ExportWorkFlows extends BaseClass { async getRoles(roleUid: number): Promise { log.debug(`Fetching role with UID: ${roleUid}`, this.exportConfig.context); - + return await this.stack .role(roleUid) .fetch({ include_rules: true, include_permissions: true }) @@ -133,10 +133,7 @@ export default class ExportWorkFlows extends BaseClass { }) .catch((err: any) => { log.debug(`Failed to fetch role data for UID: ${roleUid}`, this.exportConfig.context); - handleAndLogError( - err, - { ...this.exportConfig.context } - ); + handleAndLogError(err, { ...this.exportConfig.context }); }); } } diff --git a/packages/contentstack-export/src/types/default-config.ts b/packages/contentstack-export/src/types/default-config.ts index 12090760c..01cb84c89 100644 --- a/packages/contentstack-export/src/types/default-config.ts +++ b/packages/contentstack-export/src/types/default-config.ts @@ -67,6 +67,13 @@ export default interface DefaultConfig { invalidKeys: string[]; dependencies?: Modules[]; }; + 'publishing-rules': { + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + limit?: number; + }; globalfields: { dirName: string; fileName: string; diff --git a/packages/contentstack-export/src/types/index.ts b/packages/contentstack-export/src/types/index.ts index ec2118510..fd13364a5 100644 --- a/packages/contentstack-export/src/types/index.ts +++ b/packages/contentstack-export/src/types/index.ts @@ -46,6 +46,7 @@ export type Modules = | 'content-types' | 'custom-roles' | 'workflows' + | 'publishing-rules' | 'labels' | 'marketplace-apps' | 'taxonomies' @@ -117,6 +118,14 @@ export interface WorkflowConfig { limit?: number; } +export interface PublishingRulesConfig { + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + limit?: number; +} + export interface CustomRoleConfig { dirName: string; fileName: string; diff --git a/packages/contentstack-export/src/utils/export-config-handler.ts b/packages/contentstack-export/src/utils/export-config-handler.ts index c67b6c12b..2303f3937 100644 --- a/packages/contentstack-export/src/utils/export-config-handler.ts +++ b/packages/contentstack-export/src/utils/export-config-handler.ts @@ -1,6 +1,6 @@ import merge from 'merge'; import * as path from 'path'; -import { configHandler, isAuthenticated,cliux, sanitizePath, log } from '@contentstack/cli-utilities'; +import { configHandler, isAuthenticated, cliux, sanitizePath, log } from '@contentstack/cli-utilities'; import defaultConfig from '../config'; import { readFile } from './file-helper'; import { askExportDir, askAPIKey } from './interactive'; @@ -97,7 +97,7 @@ const setupConfig = async (exportCmdFlags: any): Promise => { if (exportCmdFlags['branch-alias']) { config.branchAlias = exportCmdFlags['branch-alias']; - } + } if (exportCmdFlags['branch']) { config.branchName = exportCmdFlags['branch']; } @@ -133,7 +133,7 @@ const setupConfig = async (exportCmdFlags: any): Promise => { } } - // Add authentication details to config for context tracking + // Add authentication details to config for context tracking config.authenticationMethod = authenticationMethod; log.debug('Export configuration setup completed.', { ...config }); diff --git a/packages/contentstack-export/src/utils/logger.ts b/packages/contentstack-export/src/utils/logger.ts index 07aa897b8..337a9d897 100644 --- a/packages/contentstack-export/src/utils/logger.ts +++ b/packages/contentstack-export/src/utils/logger.ts @@ -24,7 +24,7 @@ function returnString(args: unknown[]) { if (item && typeof item === 'object') { try { const redactedObject = redactObject(item); - if(redactedObject && typeof redactedObject === 'object') { + if (redactedObject && typeof redactedObject === 'object') { return JSON.stringify(redactedObject); } } catch (error) {} diff --git a/packages/contentstack-export/src/utils/marketplace-app-helper.ts b/packages/contentstack-export/src/utils/marketplace-app-helper.ts index 970f35527..f77c2ccb1 100644 --- a/packages/contentstack-export/src/utils/marketplace-app-helper.ts +++ b/packages/contentstack-export/src/utils/marketplace-app-helper.ts @@ -1,4 +1,10 @@ -import { cliux, handleAndLogError, NodeCrypto, managementSDKClient, createDeveloperHubUrl } from '@contentstack/cli-utilities'; +import { + cliux, + handleAndLogError, + NodeCrypto, + managementSDKClient, + createDeveloperHubUrl, +} from '@contentstack/cli-utilities'; import { ExportConfig } from '../types'; @@ -12,7 +18,7 @@ export async function getOrgUid(config: ExportConfig): Promise { .stack({ api_key: config.source_stack }) .fetch() .catch((error: any) => { - handleAndLogError(error, {...config.context}); + handleAndLogError(error, { ...config.context }); }); return tempStackData?.org_uid; diff --git a/packages/contentstack-export/src/utils/setup-export-dir.ts b/packages/contentstack-export/src/utils/setup-export-dir.ts index 76de5b364..cc49ee2c8 100644 --- a/packages/contentstack-export/src/utils/setup-export-dir.ts +++ b/packages/contentstack-export/src/utils/setup-export-dir.ts @@ -8,7 +8,9 @@ export default async function setupExportDir(exportConfig: ExportConfig) { makeDirectory(exportConfig.exportDir); if (exportConfig.branches) { return Promise.all( - exportConfig.branches.map((branch) => makeDirectory(path.join(sanitizePath(exportConfig.exportDir), sanitizePath(branch.uid)))), + exportConfig.branches.map((branch) => + makeDirectory(path.join(sanitizePath(exportConfig.exportDir), sanitizePath(branch.uid))), + ), ); } } diff --git a/packages/contentstack-export/test/unit/export/modules/assets.test.ts b/packages/contentstack-export/test/unit/export/modules/assets.test.ts index 56ef04ef7..408c9a63c 100644 --- a/packages/contentstack-export/test/unit/export/modules/assets.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/assets.test.ts @@ -113,6 +113,11 @@ describe('ExportAssets', () => { fileName: 'workflows.json', invalidKeys: [] }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: [], + }, globalfields: { dirName: 'global_fields', fileName: 'globalfields.json', diff --git a/packages/contentstack-export/test/unit/export/modules/base-class.test.ts b/packages/contentstack-export/test/unit/export/modules/base-class.test.ts index 0ffe4187f..4b62b5230 100644 --- a/packages/contentstack-export/test/unit/export/modules/base-class.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/base-class.test.ts @@ -131,6 +131,11 @@ describe('BaseClass', () => { fileName: 'workflows.json', invalidKeys: [] }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: [], + }, globalfields: { dirName: 'global_fields', fileName: 'globalfields.json', diff --git a/packages/contentstack-export/test/unit/export/modules/publishing-rules.test.ts b/packages/contentstack-export/test/unit/export/modules/publishing-rules.test.ts new file mode 100644 index 000000000..e6ab1321b --- /dev/null +++ b/packages/contentstack-export/test/unit/export/modules/publishing-rules.test.ts @@ -0,0 +1,180 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { resolve as pResolve } from 'node:path'; +import { FsUtility } from '@contentstack/cli-utilities'; +import ExportPublishingRules from '../../../../src/export/modules/publishing-rules'; +import ExportConfig from '../../../../src/types/export-config'; + +describe('ExportPublishingRules', () => { + let exportPublishingRules: ExportPublishingRules; + let mockStackClient: any; + let mockExportConfig: ExportConfig; + + beforeEach(() => { + mockStackClient = { + workflow: sinon.stub().returns({ + publishRule: sinon.stub().returns({ + fetchAll: sinon.stub().resolves({ items: [], count: 0 }), + }), + }), + }; + + mockExportConfig = { + contentVersion: 1, + versioning: false, + host: 'https://api.contentstack.io', + developerHubUrls: {}, + apiKey: 'test-api-key', + exportDir: '/test/export', + data: '/test/data', + branchName: '', + context: { + command: 'cm:stacks:export', + module: 'publishing-rules', + userId: 'user-123', + email: 'test@example.com', + sessionId: 'session-123', + apiKey: 'test-api-key', + orgId: 'org-123', + authenticationMethod: 'Basic Auth', + }, + cliLogsPath: '/test/logs', + forceStopMarketplaceAppsPrompt: false, + master_locale: { code: 'en-us' }, + region: { + name: 'us', + cma: 'https://api.contentstack.io', + cda: 'https://cdn.contentstack.io', + uiHost: 'https://app.contentstack.com', + }, + skipStackSettings: false, + skipDependencies: false, + languagesCode: ['en'], + apis: {}, + preserveStackVersion: false, + personalizationEnabled: false, + fetchConcurrency: 5, + writeConcurrency: 5, + developerHubBaseUrl: '', + marketplaceAppEncryptionKey: '', + onlyTSModules: [], + modules: { + types: ['publishing-rules'], + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['stackHeaders', 'created_at'], + }, + }, + } as any; + + exportPublishingRules = new ExportPublishingRules({ + exportConfig: mockExportConfig, + stackAPIClient: mockStackClient, + moduleName: 'publishing-rules', + }); + + sinon.stub(FsUtility.prototype, 'writeFile').resolves(); + sinon.stub(FsUtility.prototype, 'makeDirectory').resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('Constructor', () => { + it('sets context.module to publishing-rules and reads module config', () => { + expect(exportPublishingRules).to.be.instanceOf(ExportPublishingRules); + expect(exportPublishingRules.exportConfig.context.module).to.equal('publishing-rules'); + expect((exportPublishingRules as any).publishingRulesConfig.fileName).to.equal('publishing-rules.json'); + expect((exportPublishingRules as any).publishingRulesConfig.dirName).to.equal('workflows'); + }); + }); + + describe('start()', () => { + it('resolves output path from data, branchName, and publishing-rules dirName', async () => { + const fetchAll = sinon.stub().resolves({ items: [], count: 0 }); + mockStackClient.workflow.returns({ + publishRule: sinon.stub().returns({ fetchAll }), + }); + + await exportPublishingRules.start(); + + const expectedFolder = pResolve(mockExportConfig.data, mockExportConfig.branchName || '', 'workflows'); + expect((FsUtility.prototype.makeDirectory as sinon.SinonStub).calledWith(expectedFolder)).to.be.true; + }); + + it('writes publishing-rules.json with rules omitting invalidKeys when API returns items', async () => { + const writeFileStub = FsUtility.prototype.writeFile as sinon.SinonStub; + const items = [ + { + uid: 'pr-1', + name: 'Rule 1', + stackHeaders: { h: 1 }, + created_at: '2020-01-01', + }, + ]; + mockStackClient.workflow.returns({ + publishRule: sinon.stub().returns({ + fetchAll: sinon.stub().resolves({ items, count: 1 }), + }), + }); + + await exportPublishingRules.start(); + + const expectedPath = pResolve( + mockExportConfig.data, + mockExportConfig.branchName || '', + 'workflows', + 'publishing-rules.json', + ); + expect(writeFileStub.calledOnce).to.be.true; + expect(writeFileStub.firstCall.args[0]).to.equal(expectedPath); + const written = writeFileStub.firstCall.args[1] as Record>; + expect(written['pr-1']).to.deep.equal({ uid: 'pr-1', name: 'Rule 1' }); + expect(written['pr-1'].stackHeaders).to.equal(undefined); + expect(written['pr-1'].created_at).to.equal(undefined); + }); + + it('does not write the rules file when no rules are returned', async () => { + const writeFileStub = FsUtility.prototype.writeFile as sinon.SinonStub; + mockStackClient.workflow.returns({ + publishRule: sinon.stub().returns({ + fetchAll: sinon.stub().resolves({ items: [], count: 0 }), + }), + }); + + await exportPublishingRules.start(); + + expect(writeFileStub.called).to.be.false; + }); + + it('requests the next page when count exceeds items length (pagination)', async () => { + const fetchAll = sinon.stub(); + fetchAll.onFirstCall().resolves({ + items: [ + { uid: 'a', name: 'A' }, + { uid: 'b', name: 'B' }, + ], + count: 3, + }); + fetchAll.onSecondCall().resolves({ + items: [{ uid: 'c', name: 'C' }], + count: 3, + }); + + mockStackClient.workflow.returns({ + publishRule: sinon.stub().returns({ fetchAll }), + }); + + await exportPublishingRules.start(); + + expect(fetchAll.callCount).to.equal(2); + expect(fetchAll.secondCall.args[0]).to.deep.include({ skip: 2, include_count: true }); + + const writeFileStub = FsUtility.prototype.writeFile as sinon.SinonStub; + const written = writeFileStub.firstCall.args[1] as Record>; + expect(Object.keys(written).sort((x, y) => x.localeCompare(y))).to.deep.equal(['a', 'b', 'c']); + }); + }); +}); diff --git a/packages/contentstack-import/package.json b/packages/contentstack-import/package.json index c8c4c56ca..7d52187f6 100644 --- a/packages/contentstack-import/package.json +++ b/packages/contentstack-import/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-import", "description": "Contentstack CLI plugin to import content into stack", - "version": "1.32.2", + "version": "1.33.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-import/src/config/index.ts b/packages/contentstack-import/src/config/index.ts index 76b70e112..e8c9e2179 100644 --- a/packages/contentstack-import/src/config/index.ts +++ b/packages/contentstack-import/src/config/index.ts @@ -41,6 +41,7 @@ const config: DefaultConfig = { 'personalize', 'custom-roles', 'workflows', + 'publishing-rules', 'entries', 'variant-entries', 'labels', @@ -88,6 +89,11 @@ const config: DefaultConfig = { fileName: 'workflows.json', invalidKeys: ['stackHeaders', 'urlPath', 'created_at', 'updated_at', 'created_by', 'updated_by'], }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['stackHeaders', 'urlPath', 'created_at', 'updated_at', 'created_by', 'updated_by'], + }, assets: { dirName: 'assets', assetBatchLimit: 1, @@ -455,5 +461,7 @@ const config: DefaultConfig = { globalModules: ['webhooks'], entriesPublish: true, }; +export const PUBLISHING_RULES_APPROVERS_SKIP_MSG = + 'Skipping import of publish rule approver(s) (roles/users); reconfigure approvers on the target stack.'; export default config; diff --git a/packages/contentstack-import/src/import/modules/base-class.ts b/packages/contentstack-import/src/import/modules/base-class.ts index a0eb77075..1981f1887 100644 --- a/packages/contentstack-import/src/import/modules/base-class.ts +++ b/packages/contentstack-import/src/import/modules/base-class.ts @@ -51,7 +51,8 @@ export type ApiModuleType = | 'delete-entries' | 'create-taxonomies' | 'create-terms' - | 'import-taxonomy'; + | 'import-taxonomy' + | 'create-publishing-rule'; export type ApiOptions = { uid?: string; @@ -374,6 +375,13 @@ export default abstract class BaseClass { .create({ workflow: apiData as WorkflowData }) .then(onSuccess) .catch(onReject); + case 'create-publishing-rule': + return this.stack + .workflow() + .publishRule() + .create({ publishing_rule: omit(apiData, ['uid']) as any }) + .then(onSuccess) + .catch(onReject); case 'create-custom-role': return this.stack .role() diff --git a/packages/contentstack-import/src/import/modules/publishing-rules.ts b/packages/contentstack-import/src/import/modules/publishing-rules.ts new file mode 100644 index 000000000..ca7734585 --- /dev/null +++ b/packages/contentstack-import/src/import/modules/publishing-rules.ts @@ -0,0 +1,275 @@ +import chalk from 'chalk'; +import values from 'lodash/values'; +import isEmpty from 'lodash/isEmpty'; +import { join } from 'node:path'; + +import BaseClass, { ApiOptions } from './base-class'; +import { PUBLISHING_RULES_APPROVERS_SKIP_MSG } from '../../config'; +import { fsUtil, fileHelper, parseErrorPayload, isDuplicatePublishingRuleError } from '../../utils'; +import { log, handleAndLogError } from '@contentstack/cli-utilities'; +import { ModuleClassParams, PublishingRulesConfig } from '../../types'; + +export default class ImportPublishingRules extends BaseClass { + private readonly mapperDirPath: string; + private readonly publishingRulesFolderPath: string; + private readonly publishingRulesUidMapperPath: string; + private readonly createdPublishingRulesPath: string; + private readonly failedPublishingRulesPath: string; + private readonly publishingRulesConfig: PublishingRulesConfig; + private publishingRules: Record; + private publishingRulesUidMapper: Record; + private readonly createdPublishingRules: Record[]; + private readonly failedPublishingRules: Record[]; + private envUidMapper: Record; + private workflowUidMapper: Record; + private readonly stageUidMapper: Record = {}; + + constructor({ importConfig, stackAPIClient }: ModuleClassParams) { + super({ importConfig, stackAPIClient }); + this.importConfig.context.module = 'publishing-rules'; + this.publishingRulesConfig = importConfig.modules['publishing-rules']; + this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'publishing-rules'); + this.publishingRulesFolderPath = join(this.importConfig.backupDir, this.publishingRulesConfig.dirName); + this.publishingRulesUidMapperPath = join(this.mapperDirPath, 'uid-mapping.json'); + this.createdPublishingRulesPath = join(this.mapperDirPath, 'success.json'); + this.failedPublishingRulesPath = join(this.mapperDirPath, 'fails.json'); + this.publishingRules = {}; + this.publishingRulesUidMapper = {}; + this.createdPublishingRules = []; + this.failedPublishingRules = []; + this.envUidMapper = {}; + this.workflowUidMapper = {}; + } + + private static collectOldStageUidToName( + exportedWorkflows: Record, + ): Record { + const map: Record = {}; + for (const workflow of Object.values(exportedWorkflows)) { + for (const stage of workflow.workflow_stages ?? []) { + if (stage.uid && stage.name) { + map[stage.uid] = stage.name; + } + } + } + return map; + } + + /** + * Returns `{ noSuccessMsg: true }` if any rule failed, so the import command skips the generic stack success line. + */ + async start(): Promise<{ noSuccessMsg: true } | void> { + const rulesFilePath = join(this.publishingRulesFolderPath, this.publishingRulesConfig.fileName); + + if (!fileHelper.fileExistsSync(rulesFilePath)) { + log.info(`No Publishing Rules found - '${rulesFilePath}'`, this.importConfig.context); + return; + } + + this.publishingRules = (fsUtil.readFile(rulesFilePath, true) as Record) ?? {}; + if (isEmpty(this.publishingRules)) { + log.info('No Publishing Rules found', this.importConfig.context); + return; + } + + await fsUtil.makeDirectory(this.mapperDirPath); + + this.publishingRulesUidMapper = this.readUidMappingFile(this.publishingRulesUidMapperPath); + this.envUidMapper = this.readMapper('environments'); + this.workflowUidMapper = this.readMapper('workflows'); + + await this.buildStageUidMapper(); + await this.importPublishingRules(); + + if (this.createdPublishingRules?.length) { + fsUtil.writeFile(this.createdPublishingRulesPath, this.createdPublishingRules); + } + if (this.failedPublishingRules?.length) { + fsUtil.writeFile(this.failedPublishingRulesPath, this.failedPublishingRules); + } + + const successCount = this.createdPublishingRules.length; + const failCount = this.failedPublishingRules.length; + + if (failCount > 0 && successCount === 0) { + log.error( + `Publishing rules import failed! ${failCount} rule(s) could not be imported. Check '${this.failedPublishingRulesPath}' for details.`, + this.importConfig.context, + ); + } else if (failCount > 0) { + log.warn( + `Publishing rules import completed with errors. Imported: ${successCount}, Failed: ${failCount}. Check '${this.failedPublishingRulesPath}' for details.`, + this.importConfig.context, + ); + } else { + log.success('Publishing rules have been imported successfully!', this.importConfig.context); + } + + if (failCount > 0) { + return { noSuccessMsg: true }; + } + } + + private readUidMappingFile(path: string): Record { + return fileHelper.fileExistsSync(path) ? (fsUtil.readFile(path, true) as Record) ?? {} : {}; + } + + private readMapper(moduleDir: string): Record { + const p = join(this.importConfig.backupDir, 'mapper', moduleDir, 'uid-mapping.json'); + return this.readUidMappingFile(p); + } + + private async importPublishingRules(): Promise { + const apiContent = values(this.publishingRules) as Record[]; + log.debug(`Importing ${apiContent.length} publishing rule(s)`, this.importConfig.context); + + const onSuccess = ({ response, apiData }: { response: { uid: string }; apiData: { uid: string } }) => { + const { uid } = apiData; + this.createdPublishingRules.push(response as unknown as Record); + this.publishingRulesUidMapper[uid] = response.uid; + log.success(`Publishing rule imported successfully (${uid} → ${response.uid})`, this.importConfig.context); + fsUtil.writeFile(this.publishingRulesUidMapperPath, this.publishingRulesUidMapper); + }; + + const onReject = ({ error, apiData }: { error: unknown; apiData: Record }) => { + const uid = apiData.uid as string; + const parsed = parseErrorPayload(error); + + if (isDuplicatePublishingRuleError(parsed, error)) { + log.info(`Publishing rule '${uid}' already exists`, this.importConfig.context); + return; + } + + this.failedPublishingRules.push(apiData); + handleAndLogError( + error as Error, + { ...this.importConfig.context, publishingRuleUid: uid }, + `Publishing rule '${uid}' failed to import`, + ); + }; + + await this.makeConcurrentCall( + { + apiContent, + processName: 'import publishing rules', + apiParams: { + serializeData: this.serializePublishingRules.bind(this), + reject: onReject, + resolve: onSuccess, + entity: 'create-publishing-rule', + includeParamOnCompletion: true, + }, + concurrencyLimit: this.importConfig.fetchConcurrency || 1, + }, + undefined, + false, + ); + } + + private mergeFetchedWorkflowStages( + workflow: { workflow_stages?: { uid?: string; name?: string }[] }, + oldStageUidToName: Record, + ): void { + for (const newStage of workflow.workflow_stages ?? []) { + const oldUid = Object.keys(oldStageUidToName).find((u) => oldStageUidToName[u] === newStage.name); + if (oldUid && newStage.uid) { + this.stageUidMapper[oldUid] = newStage.uid; + } + } + } + + private async buildStageUidMapper(): Promise { + const wf = this.importConfig.modules.workflows as { dirName: string; fileName: string }; + const workflowsFilePath = join(this.importConfig.backupDir, wf.dirName, wf.fileName); + + if (!fileHelper.fileExistsSync(workflowsFilePath)) { + log.debug('No exported workflows file; stage UID mapping skipped', this.importConfig.context); + return; + } + + const exportedWorkflows = fsUtil.readFile(workflowsFilePath, true) as Record< + string, + { workflow_stages?: { uid?: string; name?: string }[] } + > | null; + if (!exportedWorkflows) return; + + const oldStageUidToName = ImportPublishingRules.collectOldStageUidToName(exportedWorkflows); + + for (const newWorkflowUid of Object.values(this.workflowUidMapper)) { + try { + const workflow = await this.stack.workflow(newWorkflowUid as string).fetch(); + this.mergeFetchedWorkflowStages( + workflow as { workflow_stages?: { uid?: string; name?: string }[] }, + oldStageUidToName, + ); + } catch (error: unknown) { + log.debug(`Stage mapping: could not fetch workflow '${newWorkflowUid}'`, this.importConfig.context); + handleAndLogError(error as Error, { ...this.importConfig.context }); + } + } + + log.debug(`Stage UID mapper: ${Object.keys(this.stageUidMapper).length} entr(y/ies)`, this.importConfig.context); + } + + private stripApprovers(rule: Record): void { + if (rule.approvers == null) return; + + const a = rule.approvers as { roles?: unknown[]; users?: unknown[] }; + const hadContent = (Array.isArray(a.roles) && a.roles.length > 0) || (Array.isArray(a.users) && a.users.length > 0); + if (hadContent) { + log.info(chalk.yellow(PUBLISHING_RULES_APPROVERS_SKIP_MSG), this.importConfig.context); + } + rule.approvers = { roles: [], users: [] }; + } + + private remapReference( + rule: Record, + field: 'workflow' | 'environment', + mapper: Record, + ): void { + const current = rule[field] as string | undefined; + if (!current) return; + const mapped = mapper[current] as string | undefined; + if (mapped) { + rule[field] = mapped; + log.debug(`${field} UID remapped`, this.importConfig.context); + } else { + log.debug(`No ${field} mapping for ${current}; leaving as-is`, this.importConfig.context); + } + } + + serializePublishingRules(apiOptions: ApiOptions): ApiOptions { + const rule = apiOptions.apiData as Record; + const ruleUid = rule.uid as string; + + if (ruleUid in this.publishingRulesUidMapper) { + log.info( + `Publishing rule '${ruleUid}' already exists. Skipping it to avoid duplicates!`, + this.importConfig.context, + ); + apiOptions.entity = undefined; + return apiOptions; + } + + const oldUid = ruleUid; + delete rule.uid; + + this.stripApprovers(rule); + this.remapReference(rule, 'workflow', this.workflowUidMapper); + this.remapReference(rule, 'environment', this.envUidMapper); + + if (rule.workflow_stage) { + const stage = rule.workflow_stage as string; + const mappedStage = this.stageUidMapper[stage]; + if (mappedStage) { + rule.workflow_stage = mappedStage; + log.debug('workflow_stage UID remapped', this.importConfig.context); + } else { + log.debug(`No workflow_stage mapping for ${stage}; leaving as-is`, this.importConfig.context); + } + } + + apiOptions.apiData = { ...rule, uid: oldUid }; + return apiOptions; + } +} diff --git a/packages/contentstack-import/src/types/default-config.ts b/packages/contentstack-import/src/types/default-config.ts index aa4867d29..e27968b9b 100644 --- a/packages/contentstack-import/src/types/default-config.ts +++ b/packages/contentstack-import/src/types/default-config.ts @@ -49,6 +49,11 @@ export default interface DefaultConfig { fileName: string; invalidKeys: string[]; }; + 'publishing-rules': { + dirName: string; + fileName: string; + invalidKeys: string[]; + }; assets: { dirName: string; assetBatchLimit: number; diff --git a/packages/contentstack-import/src/types/index.ts b/packages/contentstack-import/src/types/index.ts index ee9062465..8dac29ffa 100644 --- a/packages/contentstack-import/src/types/index.ts +++ b/packages/contentstack-import/src/types/index.ts @@ -46,6 +46,7 @@ export type Modules = | 'content-types' | 'custom-roles' | 'workflows' + | 'publishing-rules' | 'labels' | 'marketplace-apps' | 'taxonomies' @@ -94,6 +95,12 @@ export interface WorkflowConfig { invalidKeys: string[]; } +export interface PublishingRulesConfig { + dirName: string; + fileName: string; + invalidKeys: string[]; +} + export interface CustomRoleConfig { dirName: string; fileName: string; diff --git a/packages/contentstack-import/src/utils/index.ts b/packages/contentstack-import/src/utils/index.ts index fcf452a26..8232acd5d 100644 --- a/packages/contentstack-import/src/utils/index.ts +++ b/packages/contentstack-import/src/utils/index.ts @@ -33,3 +33,4 @@ export { export * from './common-helper'; export * from './log'; export { lookUpTaxonomy, lookUpTerms } from './taxonomies-helper'; +export { parseErrorPayload, isDuplicatePublishingRuleError } from './publishing-rules-helper'; diff --git a/packages/contentstack-import/src/utils/publishing-rules-helper.ts b/packages/contentstack-import/src/utils/publishing-rules-helper.ts new file mode 100644 index 000000000..b7f3b44ec --- /dev/null +++ b/packages/contentstack-import/src/utils/publishing-rules-helper.ts @@ -0,0 +1,32 @@ +/** + * Helpers for publishing rules import (API error shape, duplicate detection). + */ + +export function parseErrorPayload(error: unknown): { + errors?: Record; + error_message?: string; +} | null { + if (!error || typeof error !== 'object') return null; + const e = error as { message?: string; errors?: Record }; + if (e.errors) return e; + if (e.message && typeof e.message === 'string') { + try { + return JSON.parse(e.message) as { errors?: Record; error_message?: string }; + } catch { + return null; + } + } + return null; +} + +export function isDuplicatePublishingRuleError( + parsed: { errors?: Record; error_message?: string } | null, + raw: unknown, +): boolean { + const errors = parsed?.errors ?? (raw as { errors?: Record })?.errors; + if (errors?.name || errors?.['publishing_rule.name'] || errors?.['publish_rule.name']) { + return true; + } + const msg = parsed?.error_message; + return typeof msg === 'string' && /already exists|duplicate/i.test(msg); +} diff --git a/packages/contentstack-import/test/unit/import/modules/locales.test.ts b/packages/contentstack-import/test/unit/import/modules/locales.test.ts index fc3631e81..1f5da35cf 100644 --- a/packages/contentstack-import/test/unit/import/modules/locales.test.ts +++ b/packages/contentstack-import/test/unit/import/modules/locales.test.ts @@ -51,6 +51,11 @@ describe('ImportLocales', () => { webhooks: { dirName: 'webhooks', fileName: 'webhooks.json' }, releases: { dirName: 'releases', fileName: 'releases.json', invalidKeys: ['uid'] }, workflows: { dirName: 'workflows', fileName: 'workflows.json', invalidKeys: ['uid'] }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['uid'], + }, assets: { dirName: 'assets', assetBatchLimit: 10, diff --git a/packages/contentstack-import/test/unit/import/modules/publishing-rules.test.ts b/packages/contentstack-import/test/unit/import/modules/publishing-rules.test.ts new file mode 100644 index 000000000..26aa3f4a7 --- /dev/null +++ b/packages/contentstack-import/test/unit/import/modules/publishing-rules.test.ts @@ -0,0 +1,333 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { join } from 'node:path'; +import ImportPublishingRules from '../../../../src/import/modules/publishing-rules'; +import { ImportConfig } from '../../../../src/types'; +describe('ImportPublishingRules', () => { + const BACKUP = '/test/backup'; + const rulesFile = join(BACKUP, 'workflows', 'publishing-rules.json'); + const workflowsExportFile = join(BACKUP, 'workflows', 'workflows.json'); + const workflowMapperFile = join(BACKUP, 'mapper', 'workflows', 'uid-mapping.json'); + const envMapperFile = join(BACKUP, 'mapper', 'environments', 'uid-mapping.json'); + const publishingMapperFile = join(BACKUP, 'mapper', 'publishing-rules', 'uid-mapping.json'); + + let importPublishingRules: ImportPublishingRules; + let mockStackClient: any; + let mockImportConfig: ImportConfig; + let fsUtilStub: any; + let fileHelperStub: any; + let makeConcurrentCallStub: sinon.SinonStub; + let logStub: { info: sinon.SinonStub; debug: sinon.SinonStub; success: sinon.SinonStub; error: sinon.SinonStub; warn: sinon.SinonStub }; + beforeEach(() => { + fsUtilStub = { + readFile: sinon.stub(), + writeFile: sinon.stub(), + makeDirectory: sinon.stub().resolves(), + }; + + fileHelperStub = { + fileExistsSync: sinon.stub(), + }; + + sinon.replace(require('../../../../src/utils'), 'fileHelper', fileHelperStub); + sinon.replaceGetter(require('../../../../src/utils'), 'fsUtil', () => fsUtilStub); + + const fetchWorkflowStub = sinon.stub().resolves({ + workflow_stages: [{ uid: 'stage-new', name: 'Review' }], + }); + mockStackClient = { + workflow: sinon.stub().returns({ + fetch: fetchWorkflowStub, + }), + }; + + mockImportConfig = { + apiKey: 'test', + backupDir: BACKUP, + data: '/test/content', + contentVersion: 1, + region: 'us', + fetchConcurrency: 2, + context: { + command: 'cm:stacks:import', + module: 'publishing-rules', + userId: 'user-123', + email: 'test@example.com', + sessionId: 'session-123', + apiKey: 'test', + orgId: 'org-123', + authenticationMethod: 'Basic Auth', + }, + modules: { + workflows: { + dirName: 'workflows', + fileName: 'workflows.json', + invalidKeys: ['uid'], + }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['uid'], + }, + }, + } as any; + + importPublishingRules = new ImportPublishingRules({ + importConfig: mockImportConfig as any, + stackAPIClient: mockStackClient, + moduleName: 'publishing-rules', + }); + + makeConcurrentCallStub = sinon.stub(importPublishingRules as any, 'makeConcurrentCall').resolves(); + + const cliUtilities = require('@contentstack/cli-utilities'); + logStub = { + info: sinon.stub(), + debug: sinon.stub(), + success: sinon.stub(), + error: sinon.stub(), + warn: sinon.stub(), + }; + sinon.stub(cliUtilities, 'log').value(logStub); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('Constructor', () => { + it('sets context.module to publishing-rules and derives exact paths from backupDir and config', () => { + expect(mockImportConfig.context.module).to.equal('publishing-rules'); + expect(importPublishingRules['mapperDirPath']).to.equal(join(BACKUP, 'mapper', 'publishing-rules')); + expect(importPublishingRules['publishingRulesFolderPath']).to.equal(join(BACKUP, 'workflows')); + expect(importPublishingRules['publishingRulesUidMapperPath']).to.equal(publishingMapperFile); + expect(importPublishingRules['createdPublishingRulesPath']).to.equal( + join(BACKUP, 'mapper', 'publishing-rules', 'success.json'), + ); + expect(importPublishingRules['failedPublishingRulesPath']).to.equal( + join(BACKUP, 'mapper', 'publishing-rules', 'fails.json'), + ); + }); + + it('initializes empty rules, mappers, and result arrays', () => { + expect(importPublishingRules['publishingRules']).to.deep.equal({}); + expect(importPublishingRules['publishingRulesUidMapper']).to.deep.equal({}); + expect(importPublishingRules['createdPublishingRules']).to.deep.equal([]); + expect(importPublishingRules['failedPublishingRules']).to.deep.equal([]); + expect(importPublishingRules['envUidMapper']).to.deep.equal({}); + expect(importPublishingRules['workflowUidMapper']).to.deep.equal({}); + expect(importPublishingRules['stageUidMapper']).to.deep.equal({}); + }); + }); + + describe('start()', () => { + it('returns undefined and logs missing file path when rules file does not exist', async () => { + fileHelperStub.fileExistsSync.withArgs(rulesFile).returns(false); + + const result = await importPublishingRules.start(); + + expect(result).to.equal(undefined); + expect(makeConcurrentCallStub.called).to.be.false; + expect(logStub.info.firstCall.args[0]).to.include(rulesFile); + }); + + it('returns undefined when rules file exists but payload is empty; arrays stay empty', async () => { + fileHelperStub.fileExistsSync.withArgs(rulesFile).returns(true); + fsUtilStub.readFile.withArgs(rulesFile, true).returns({}); + + const result = await importPublishingRules.start(); + + expect(result).to.equal(undefined); + expect(makeConcurrentCallStub.called).to.be.false; + expect(importPublishingRules['createdPublishingRules']).to.deep.equal([]); + expect(importPublishingRules['failedPublishingRules']).to.deep.equal([]); + }); + + it('passes one apiContent item per rule and binds serializeData to serializePublishingRules', async () => { + const rules = { + r1: { uid: 'r1', name: 'Rule 1' }, + r2: { uid: 'r2', name: 'Rule 2' }, + }; + fileHelperStub.fileExistsSync.callsFake((p: string) => { + if (p === rulesFile) return true; + if (p === workflowsExportFile || p === workflowMapperFile || p === envMapperFile || p === publishingMapperFile) { + return false; + } + return false; + }); + fsUtilStub.readFile.callsFake((p: string) => { + if (p === rulesFile) return rules; + return {}; + }); + + await importPublishingRules.start(); + + expect(makeConcurrentCallStub.calledOnce).to.be.true; + const callArgs = makeConcurrentCallStub.firstCall.args[0]; + expect(callArgs.apiContent).to.have.length(2); + expect(callArgs.processName).to.equal('import publishing rules'); + expect(callArgs.apiParams.entity).to.equal('create-publishing-rule'); + const serialized = callArgs.apiParams.serializeData({ + apiData: { uid: 'r1', name: 'Rule 1' }, + entity: 'create-publishing-rule', + }); + expect(serialized.apiData).to.deep.include({ name: 'Rule 1', uid: 'r1' }); + expect(serialized.entity).to.equal('create-publishing-rule'); + }); + + it('builds stageUidMapper from exported workflows and fetched target workflow stages by name', async () => { + fileHelperStub.fileExistsSync.callsFake((p: string) => { + if (p === rulesFile) return true; + if (p === workflowsExportFile) return true; + if (p === workflowMapperFile) return true; + if (p === envMapperFile || p === publishingMapperFile) return false; + return false; + }); + fsUtilStub.readFile.callsFake((p: string) => { + if (p === rulesFile) return { r1: { uid: 'r1', name: 'R' } }; + if (p === workflowsExportFile) { + return { + expWf: { workflow_stages: [{ uid: 'stage-old', name: 'Review' }] }, + }; + } + if (p === workflowMapperFile) return { oldWf: 'newWf' }; + return {}; + }); + + await importPublishingRules.start(); + + expect(importPublishingRules['stageUidMapper']).to.deep.equal({ 'stage-old': 'stage-new' }); + expect(mockStackClient.workflow.calledWith('newWf')).to.be.true; + }); + + it('returns { noSuccessMsg: true } when a rule fails to import (non-duplicate error)', async () => { + fileHelperStub.fileExistsSync.callsFake((p: string) => p === rulesFile); + fsUtilStub.readFile.callsFake((p: string) => (p === rulesFile ? { r1: { uid: 'r1', name: 'R' } } : {})); + + makeConcurrentCallStub.callsFake(async (env: any) => { + const { apiParams, apiContent } = env; + for (const element of apiContent) { + apiParams.apiData = element; + let opts = { ...apiParams, apiData: { ...element } }; + opts = apiParams.serializeData(opts); + if (opts.entity) { + await apiParams.reject({ error: new Error('network'), apiData: opts.apiData }); + } + } + }); + + const result = await importPublishingRules.start(); + + expect(result).to.deep.equal({ noSuccessMsg: true }); + expect(importPublishingRules['failedPublishingRules']).to.have.length(1); + expect(importPublishingRules['failedPublishingRules'][0].uid).to.equal('r1'); + expect(String(logStub.error.firstCall?.args[0] ?? '')).to.include('could not be imported'); + }); + + it('returns undefined when import succeeds with no failures', async () => { + fileHelperStub.fileExistsSync.callsFake((p: string) => p === rulesFile); + fsUtilStub.readFile.callsFake((p: string) => (p === rulesFile ? { r1: { uid: 'r1', name: 'R' } } : {})); + + makeConcurrentCallStub.callsFake(async (env: any) => { + const { apiParams, apiContent } = env; + for (const element of apiContent) { + apiParams.apiData = element; + let opts = { ...apiParams, apiData: { ...element } }; + opts = apiParams.serializeData(opts); + if (opts.entity) { + await apiParams.resolve({ response: { uid: 'new-r1' }, apiData: opts.apiData }); + } + } + }); + + const result = await importPublishingRules.start(); + + expect(result).to.equal(undefined); + expect(importPublishingRules['failedPublishingRules']).to.deep.equal([]); + expect(importPublishingRules['publishingRulesUidMapper']).to.deep.equal({ r1: 'new-r1' }); + expect(logStub.success.calledWith('Publishing rules have been imported successfully!', mockImportConfig.context)).to.be + .true; + }); + }); + + describe('serializePublishingRules', () => { + it('clears entity when rule uid already in mapper; leaves apiData.uid unchanged', () => { + importPublishingRules['publishingRulesUidMapper'] = { 'rule-1': 'mapped-1' }; + + const apiOptions: any = { + apiData: { uid: 'rule-1', name: 'N' }, + entity: 'create-publishing-rule', + }; + + const out = importPublishingRules.serializePublishingRules(apiOptions); + + expect(out.entity).to.equal(undefined); + expect(out.apiData).to.deep.equal({ uid: 'rule-1', name: 'N' }); + expect(String(logStub.info.firstCall?.args[0] ?? '')).to.match(/already exists\. Skipping/); + }); + + it('remaps workflow, environment, workflow_stage and strips approvers; apiData carries uid for completion handler', () => { + const pr = importPublishingRules as any; + pr.workflowUidMapper = { wfOld: 'wfNew' }; + pr.envUidMapper = { envOld: 'envNew' }; + Object.keys(pr.stageUidMapper).forEach((k) => delete pr.stageUidMapper[k]); + pr.stageUidMapper.stOld = 'stNew'; + + const apiOptions: any = { + apiData: { + uid: 'pr-1', + name: 'PR', + workflow: 'wfOld', + environment: 'envOld', + workflow_stage: 'stOld', + approvers: { roles: ['r1'], users: ['u1'] }, + }, + entity: 'create-publishing-rule', + }; + + const out = importPublishingRules.serializePublishingRules(apiOptions); + + expect(out.entity).to.equal('create-publishing-rule'); + expect(out.apiData).to.deep.equal({ + uid: 'pr-1', + name: 'PR', + workflow: 'wfNew', + environment: 'envNew', + workflow_stage: 'stNew', + approvers: { roles: [], users: [] }, + }); + const infoArgs = logStub.info.getCalls().map((c) => c.args[0]); + expect(infoArgs.some((msg) => String(msg).includes('Skipping import of publish rule approver'))).to.be.true; + }); + }); + + describe('importPublishingRules callbacks', () => { + beforeEach(() => { + importPublishingRules['publishingRules'] = { r1: { uid: 'r1', name: 'R' } }; + }); + + it('onSuccess updates mapper and persists uid-mapping.json with expected payload', async () => { + await (importPublishingRules as any).importPublishingRules(); + + const onSuccess = makeConcurrentCallStub.firstCall.args[0].apiParams.resolve; + await onSuccess({ response: { uid: 'created-uid', name: 'R' }, apiData: { uid: 'r1', name: 'R' } }); + + expect(importPublishingRules['createdPublishingRules']).to.deep.equal([{ uid: 'created-uid', name: 'R' }]); + expect(importPublishingRules['publishingRulesUidMapper']).to.deep.equal({ r1: 'created-uid' }); + expect(fsUtilStub.writeFile.calledWith(publishingMapperFile, { r1: 'created-uid' })).to.be.true; + }); + + it('onReject for duplicate error does not append to failedPublishingRules', async () => { + await (importPublishingRules as any).importPublishingRules(); + + const onReject = makeConcurrentCallStub.firstCall.args[0].apiParams.reject; + await onReject({ + error: { errors: { name: 'taken' } }, + apiData: { uid: 'r1', name: 'R' }, + }); + + expect(importPublishingRules['failedPublishingRules']).to.deep.equal([]); + expect(logStub.info.calledWith(`Publishing rule 'r1' already exists`, mockImportConfig.context)).to.be.true; + }); + }); +}); diff --git a/packages/contentstack-import/test/unit/utils/extension-helper.test.ts b/packages/contentstack-import/test/unit/utils/extension-helper.test.ts index c9b567218..fa537a84e 100644 --- a/packages/contentstack-import/test/unit/utils/extension-helper.test.ts +++ b/packages/contentstack-import/test/unit/utils/extension-helper.test.ts @@ -54,6 +54,11 @@ describe('Extension Helper', () => { webhooks: { dirName: 'webhooks', fileName: 'webhooks.json' }, releases: { dirName: 'releases', fileName: 'releases.json', invalidKeys: ['uid'] }, workflows: { dirName: 'workflows', fileName: 'workflows.json', invalidKeys: ['uid'] }, + 'publishing-rules': { + dirName: 'workflows', + fileName: 'publishing-rules.json', + invalidKeys: ['uid'], + }, assets: { dirName: 'assets', assetBatchLimit: 10, diff --git a/packages/contentstack-import/test/unit/utils/publishing-rules-helper.test.ts b/packages/contentstack-import/test/unit/utils/publishing-rules-helper.test.ts new file mode 100644 index 000000000..1453b68eb --- /dev/null +++ b/packages/contentstack-import/test/unit/utils/publishing-rules-helper.test.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import { parseErrorPayload, isDuplicatePublishingRuleError } from '../../../src/utils/publishing-rules-helper'; + +describe('publishing-rules-helper', () => { + describe('parseErrorPayload', () => { + it('returns the object when error has errors', () => { + const err = { errors: { name: 'taken' } }; + expect(parseErrorPayload(err)).to.deep.equal({ errors: { name: 'taken' } }); + }); + + it('parses JSON from message string', () => { + const err = { message: JSON.stringify({ error_message: 'already exists' }) }; + expect(parseErrorPayload(err)).to.deep.equal({ error_message: 'already exists' }); + }); + + it('returns null for invalid JSON in message', () => { + expect(parseErrorPayload({ message: 'not-json{' })).to.equal(null); + }); + + it('returns null for non-object', () => { + expect(parseErrorPayload(null)).to.equal(null); + expect(parseErrorPayload('x')).to.equal(null); + }); + + it('returns null when object has no errors or parseable message', () => { + expect(parseErrorPayload({ foo: 1 })).to.equal(null); + }); + }); + + describe('isDuplicatePublishingRuleError', () => { + it('returns true when errors.name is set', () => { + expect(isDuplicatePublishingRuleError({ errors: { name: 'x' } }, {})).to.equal(true); + }); + + it('returns true when errors.publishing_rule.name is set', () => { + expect(isDuplicatePublishingRuleError({ errors: { 'publishing_rule.name': 'x' } }, {})).to.equal(true); + }); + + it('returns true when errors.publish_rule.name is set', () => { + expect(isDuplicatePublishingRuleError({ errors: { 'publish_rule.name': 'x' } }, {})).to.equal(true); + }); + + it('returns true when error_message matches duplicate wording', () => { + expect(isDuplicatePublishingRuleError({ error_message: 'Rule already exists' }, {})).to.equal(true); + }); + + it('reads errors from raw when parsed is null', () => { + const raw = { errors: { name: 'dup' } }; + expect(isDuplicatePublishingRuleError(null, raw)).to.equal(true); + }); + + it('returns false when no duplicate signals', () => { + expect(isDuplicatePublishingRuleError({ errors: { other: 'x' } }, {})).to.equal(false); + expect(isDuplicatePublishingRuleError({ error_message: 'timeout' }, {})).to.equal(false); + }); + }); +}); diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index 31f7a7b2f..26401c0da 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -1,11 +1,11 @@ { "name": "@contentstack/cli-cm-seed", "description": "create a Stack from existing content types, entries, assets, etc.", - "version": "1.15.1", + "version": "1.15.2", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-cm-import": "~1.32.1", + "@contentstack/cli-cm-import": "~1.33.0", "@contentstack/cli-command": "~1.8.1", "@contentstack/cli-utilities": "~1.18.2", "inquirer": "8.2.7", diff --git a/packages/contentstack-variants/src/types/export-config.ts b/packages/contentstack-variants/src/types/export-config.ts index f9336bc96..8b782c2b0 100644 --- a/packages/contentstack-variants/src/types/export-config.ts +++ b/packages/contentstack-variants/src/types/export-config.ts @@ -17,6 +17,7 @@ export type Modules = | 'content-types' | 'custom-roles' | 'workflows' + | 'publishing-rules' | 'labels' | 'marketplace-apps' | 'taxonomies' @@ -88,6 +89,13 @@ export interface DefaultConfig { invalidKeys: string[]; dependencies?: Modules[]; }; + 'publishing-rules': { + dirName: string; + fileName: string; + invalidKeys: string[]; + dependencies?: Modules[]; + limit?: number; + }; globalfields: { dirName: string; fileName: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c288ef05..fc1a86203 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,7 +18,7 @@ importers: version: 9.1.7 pnpm: specifier: ^10.28.0 - version: 10.33.0 + version: 10.33.1 packages/contentstack-audit: dependencies: @@ -108,7 +108,7 @@ importers: packages/contentstack-bootstrap: dependencies: '@contentstack/cli-cm-seed': - specifier: ~1.15.1 + specifier: ~1.15.2 version: link:../contentstack-seed '@contentstack/cli-command': specifier: ~1.8.1 @@ -306,10 +306,10 @@ importers: specifier: ^1.6.0 version: 1.6.0 '@contentstack/cli-cm-export': - specifier: ~1.24.2 + specifier: ~1.25.0 version: link:../contentstack-export '@contentstack/cli-cm-import': - specifier: ~1.32.2 + specifier: ~1.33.0 version: link:../contentstack-import '@contentstack/cli-command': specifier: ~1.8.1 @@ -879,7 +879,7 @@ importers: packages/contentstack-seed: dependencies: '@contentstack/cli-cm-import': - specifier: ~1.32.1 + specifier: ~1.33.0 version: link:../contentstack-import '@contentstack/cli-command': specifier: ~1.8.1 @@ -1022,44 +1022,44 @@ packages: resolution: {integrity: sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.974.2': - resolution: {integrity: sha512-oav5AOAz+1XkwUfp6SrEm42UPDpUP5D4jNYXkDwFR1VfWqYX62+jpytdfzURmJ9McSoJIQwi0OJlC4oCi6t0VQ==} + '@aws-sdk/core@3.974.3': + resolution: {integrity: sha512-W3aJJm2clu8OmsrwMOMnfof13O6LGnbknnZIQeSRbxjqKah2nVvkjbUBBZVhWrt08KC69H7WsINTdrxC/2SXQw==} engines: {node: '>=20.0.0'} '@aws-sdk/crc64-nvme@3.972.7': resolution: {integrity: sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.28': - resolution: {integrity: sha512-87GdRJ2OR0qR4VkMjXN/SZi66DZsunW2qQCbtw9rKw3Y7JurFi6tQWYKOSLY/gOADrU6OxGqFmdw3hKzZqDZOQ==} + '@aws-sdk/credential-provider-env@3.972.29': + resolution: {integrity: sha512-rf+AlUxgTeSzQ/4zoS0D+Bt7XvgpY48PnWG8Yg/N9fdMgyK2Jaqa+6tLZp4MYMIMHkGrfAxnbSeb2YLMGFMg6g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.30': - resolution: {integrity: sha512-6quozmW2PKwBJTUQLb+lk1q8w5Pm45qaqhx4Tld9EIqYYQOVGj+MT0a8NRVS7QgWJj7rzGlB7rQu3KYBFHemJw==} + '@aws-sdk/credential-provider-http@3.972.31': + resolution: {integrity: sha512-TR2/lQ3qKFj2EOrsiASzemsNEz2uzZ/SUBf48+U4Cr9a/FZlHfH/hwAeBJNBp1gMyJNxROJZhT3dn1cO+jnYfQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.32': - resolution: {integrity: sha512-Nkr+UKtczZlocUjc6g96WzQadZSIZO/HVXPki4qbfaVOZYSbfLQKWKfADtJ0kGYsCvSYOZrO66tSc9dkboUt/w==} + '@aws-sdk/credential-provider-ini@3.972.33': + resolution: {integrity: sha512-UwdbJbOrgnOxZbshaNZ4DzX35h5wQd33MNYTGzWhN3ORG9lG9KQbDX6l6tDJSAdaGTktJoZPSritmUoW1rYkRA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.32': - resolution: {integrity: sha512-UxgwT1HmZz1QPXuBy5ZUPJNFXOSlhwdQL61eGhWRthF0xRrT02BCOVJ1p5Ejg5AXfnESTWoKPJ7v/sCkNUtB9g==} + '@aws-sdk/credential-provider-login@3.972.33': + resolution: {integrity: sha512-WyZuPVoDM1HGNl41eVg8HSSXIB+FGkuuK63GhDbh4TMdfWU03AciWvF/QqOVWvJtWVYaLddANJ+aUklVr2ieuw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.33': - resolution: {integrity: sha512-6pGQnEdSeRvBViTQh/FwaRKB38a3Th+W2mVxuvqAd2Z1Ayo3e6eJ5QqJoZwEMwR6xoxkl3wz3qAfiB1xRhMC+w==} + '@aws-sdk/credential-provider-node@3.972.34': + resolution: {integrity: sha512-sPcisURibKU4x0PCWJkWF1KJYm49Cph9dCn/PAnG5FU0wq5Id3g2v7RuEWAtNlKv1Af4gUJYBVGOeNpSEEx41A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.28': - resolution: {integrity: sha512-CRAlD8u6oNBhjnX/3ekVGocarD+lFmEn/qeDzytgIdmwrmwMJGFPqS9lGwEfhOTihZKrQ0xSp3z6paX+iXJJhA==} + '@aws-sdk/credential-provider-process@3.972.29': + resolution: {integrity: sha512-DURisqWS3bUgiwMXTmzymVNGlcRW0FnbPZ3SZknhmxnCXm3n9idkTJ6T+Uir359KRKtJNFLRViskk8HsSVLi1w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.32': - resolution: {integrity: sha512-whhmQghRYOt9mJxFyVMhX7eB8n0oA25OCvqoR7dzFAZjmioCkf7WVB22Bc6llM5cFpBXFX7s4Jv+xVq32VPGWg==} + '@aws-sdk/credential-provider-sso@3.972.33': + resolution: {integrity: sha512-9y9obU4IQWru9f+NiiscUeyCe5ZmQav4FKEb1qfUNrik/C3BzBGUnHQWyPEyXjOX9cb+vx1TYx0qZBtinKdzTA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.32': - resolution: {integrity: sha512-Z0Y0LDaqyQDznlmr9gv6n4+eWKKWNgmi9j5L6RENr6wyOCguhO8FRPmqDbVLSw0DPdMqICKnA3PurJiS8bD6Cw==} + '@aws-sdk/credential-provider-web-identity@3.972.33': + resolution: {integrity: sha512-RazhlN0YAkna2T2p2v4YuuRlVBVRNo8V0SL+9JePTWDndEUAeOBAjYeQfAMbtDyCh120+zA0Op6V0jS4dw2+iw==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.972.10': @@ -1070,8 +1070,8 @@ packages: resolution: {integrity: sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.974.10': - resolution: {integrity: sha512-R9oqyD1hR7aF2UQaYBo90/ILNn8Sq7gl/2Y4WkDDvsaqklqPomso++sFbgYgNmN/Kfx6gqvJwcjSkxJHEBK1tQ==} + '@aws-sdk/middleware-flexible-checksums@3.974.11': + resolution: {integrity: sha512-jTrJFs4SMs9xjih45+QHtU79piovA6CAlofMt4jeknN5ef9zsVEHDtuwCnEe/3eANWewa9fd6Tvc54xEPpQ3RA==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-host-header@3.972.10': @@ -1090,32 +1090,32 @@ packages: resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.31': - resolution: {integrity: sha512-5hS08Fp0Rm+59uGCmkWhZmveXiA7OUV7Wa+IARejdzf9JTZ1qAVeIOE9JoBpsLPvUgEjmsGNHBuFbtGmYyqiqQ==} + '@aws-sdk/middleware-sdk-s3@3.972.32': + resolution: {integrity: sha512-dc2O2x0V5pGJhmdQYQveUIFtMZsur7GrGuSgoKM4oQJuEcfvwnJ3sj+ip6WnxR5l6TrX5zkl4KgcgswOy3wAzQ==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-ssec@3.972.10': resolution: {integrity: sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-user-agent@3.972.32': - resolution: {integrity: sha512-HQ0x9DDKqLZOGhDiL2eicYXXkYT5dogE4mw0lAfHCpJ6t7MM0PNIsJl2TZzWKU9SpBzOMXHRa7K6ZLKUJu1y0w==} + '@aws-sdk/middleware-user-agent@3.972.33': + resolution: {integrity: sha512-mqtT3Fo7xanWMk2SbAcKLGGI/q1GHWNrExBj7cnWP2W2mkTMheXB4ntJvwPZ1UxPrQobrsv2dWFXmaOJeSOiDg==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.0': - resolution: {integrity: sha512-4bI5GHjUiY5R8N6PtchpG6tW2Dl8I2IcZNg3JwqwxHRXjfvQlPoo4VMknG4qkd5W0t3Y20rQ6C7pSR561YG5JQ==} + '@aws-sdk/nested-clients@3.997.1': + resolution: {integrity: sha512-Afc9hc2WZs3X4Jb8dnxyuYiZsLoWRO51roTCRf497gPnAKN2WRdXANu1vaVCTzwnDMOYFXb/cYv4ZSjxqAqcKA==} engines: {node: '>=20.0.0'} - '@aws-sdk/region-config-resolver@3.972.12': - resolution: {integrity: sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ==} + '@aws-sdk/region-config-resolver@3.972.13': + resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.19': - resolution: {integrity: sha512-7Sy8+GhfwUi06NQNLplxuJuXMKJURDsNQfK8yTW6E9wN2J1B+8S5dWZG7vg3InvPPhaXqkcYTr8pzeE+dLjMbQ==} + '@aws-sdk/signature-v4-multi-region@3.996.20': + resolution: {integrity: sha512-MEj6DhEcaO8RgVtFCJ+xpCQnZC3Iesr09avdY75qkMQfckQULu447IegK7Rs1MCGerVBfKnJQ4q+pQq9hI5lng==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1033.0': - resolution: {integrity: sha512-/TsXhqjyRAFb0xVgmbFAha3cJfZdWjnyn6ohJ3AB4E3peLgxNcmKfYr45hruHymyJAydiHoXC3N1a8qgl41cog==} + '@aws-sdk/token-providers@3.1034.0': + resolution: {integrity: sha512-8E+KGcD4ET0H9FXJ2/ZWbfFnQNYEkTZZYJxAs1lkdJlve1AYuqaydInIFfvNgoz5GbYtzbK8/ugsSMu5wPm6kA==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.973.8': @@ -1126,8 +1126,8 @@ packages: resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-endpoints@3.996.7': - resolution: {integrity: sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw==} + '@aws-sdk/util-endpoints@3.996.8': + resolution: {integrity: sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==} engines: {node: '>=20.0.0'} '@aws-sdk/util-locate-window@3.965.5': @@ -1137,8 +1137,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.972.10': resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} - '@aws-sdk/util-user-agent-node@3.973.18': - resolution: {integrity: sha512-Nh4YvAL0Mzv5jBvzXLFL0tLf7WPrRMnYZQ5jlFuyS0xiVJQsObMUKAkbYjmt/e04wpQqUaa+Is7k+mBr89A9yA==} + '@aws-sdk/util-user-agent-node@3.973.19': + resolution: {integrity: sha512-ZAfHjpzdbrzkAftC139JoYGfXzDh5HY+AxRzw8pGJ8cULsf+l721sKAMK8mV5NvRETaW/BwghSwQhGgoNgrxMw==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -3078,8 +3078,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001788: - resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -3533,8 +3533,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.340: - resolution: {integrity: sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==} + electron-to-chromium@1.5.343: + resolution: {integrity: sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==} elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} @@ -5226,8 +5226,8 @@ packages: resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} engines: {node: '>=8'} - node-releases@2.0.37: - resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -5494,8 +5494,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - pnpm@10.33.0: - resolution: {integrity: sha512-EFaLtKavtYyes2MNqQzJUWQXq+vT+rvmc58K55VyjaFJHp21pUTHatjrdXD1xLs9bGN7LLQb/c20f6gjyGSTGQ==} + pnpm@10.33.1: + resolution: {integrity: sha512-Bbo8HV0cGPaN8GRw10BV5i1B/BEKDGYNsbLfsnhTJ/BM8PaDRdRgm8Ugief6A0PDFZOy+VlOLF1dpCYjCsyYIA==} engines: {node: '>=18.12'} hasBin: true @@ -6109,8 +6109,8 @@ packages: resolution: {integrity: sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==} engines: {node: '>=4.0.0'} - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} tar@7.5.13: @@ -6651,17 +6651,17 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.2 - '@aws-sdk/credential-provider-node': 3.972.33 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/credential-provider-node': 3.972.34 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.32 - '@aws-sdk/region-config-resolver': 3.972.12 + '@aws-sdk/middleware-user-agent': 3.972.33 + '@aws-sdk/region-config-resolver': 3.972.13 '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 + '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.18 + '@aws-sdk/util-user-agent-node': 3.973.19 '@smithy/config-resolver': 4.4.17 '@smithy/core': 3.23.16 '@smithy/fetch-http-handler': 5.3.17 @@ -6698,24 +6698,24 @@ snapshots: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.2 - '@aws-sdk/credential-provider-node': 3.972.33 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/credential-provider-node': 3.972.34 '@aws-sdk/middleware-bucket-endpoint': 3.972.10 '@aws-sdk/middleware-expect-continue': 3.972.10 - '@aws-sdk/middleware-flexible-checksums': 3.974.10 + '@aws-sdk/middleware-flexible-checksums': 3.974.11 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-location-constraint': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-sdk-s3': 3.972.31 + '@aws-sdk/middleware-sdk-s3': 3.972.32 '@aws-sdk/middleware-ssec': 3.972.10 - '@aws-sdk/middleware-user-agent': 3.972.32 - '@aws-sdk/region-config-resolver': 3.972.12 - '@aws-sdk/signature-v4-multi-region': 3.996.19 + '@aws-sdk/middleware-user-agent': 3.972.33 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.20 '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 + '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.18 + '@aws-sdk/util-user-agent-node': 3.973.19 '@smithy/config-resolver': 4.4.17 '@smithy/core': 3.23.16 '@smithy/eventstream-serde-browser': 4.2.14 @@ -6753,7 +6753,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.974.2': + '@aws-sdk/core@3.974.3': dependencies: '@aws-sdk/types': 3.973.8 '@aws-sdk/xml-builder': 3.972.18 @@ -6766,6 +6766,7 @@ snapshots: '@smithy/types': 4.14.1 '@smithy/util-base64': 4.3.2 '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.3 '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 @@ -6774,17 +6775,17 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.28': + '@aws-sdk/credential-provider-env@3.972.29': dependencies: - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.30': + '@aws-sdk/credential-provider-http@3.972.31': dependencies: - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/types': 3.973.8 '@smithy/fetch-http-handler': 5.3.17 '@smithy/node-http-handler': 4.6.0 @@ -6795,16 +6796,16 @@ snapshots: '@smithy/util-stream': 4.5.24 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.972.32': + '@aws-sdk/credential-provider-ini@3.972.33': dependencies: - '@aws-sdk/core': 3.974.2 - '@aws-sdk/credential-provider-env': 3.972.28 - '@aws-sdk/credential-provider-http': 3.972.30 - '@aws-sdk/credential-provider-login': 3.972.32 - '@aws-sdk/credential-provider-process': 3.972.28 - '@aws-sdk/credential-provider-sso': 3.972.32 - '@aws-sdk/credential-provider-web-identity': 3.972.32 - '@aws-sdk/nested-clients': 3.997.0 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/credential-provider-env': 3.972.29 + '@aws-sdk/credential-provider-http': 3.972.31 + '@aws-sdk/credential-provider-login': 3.972.33 + '@aws-sdk/credential-provider-process': 3.972.29 + '@aws-sdk/credential-provider-sso': 3.972.33 + '@aws-sdk/credential-provider-web-identity': 3.972.33 + '@aws-sdk/nested-clients': 3.997.1 '@aws-sdk/types': 3.973.8 '@smithy/credential-provider-imds': 4.2.14 '@smithy/property-provider': 4.2.14 @@ -6814,10 +6815,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.972.32': + '@aws-sdk/credential-provider-login@3.972.33': dependencies: - '@aws-sdk/core': 3.974.2 - '@aws-sdk/nested-clients': 3.997.0 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/nested-clients': 3.997.1 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/protocol-http': 5.3.14 @@ -6827,14 +6828,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.972.33': + '@aws-sdk/credential-provider-node@3.972.34': dependencies: - '@aws-sdk/credential-provider-env': 3.972.28 - '@aws-sdk/credential-provider-http': 3.972.30 - '@aws-sdk/credential-provider-ini': 3.972.32 - '@aws-sdk/credential-provider-process': 3.972.28 - '@aws-sdk/credential-provider-sso': 3.972.32 - '@aws-sdk/credential-provider-web-identity': 3.972.32 + '@aws-sdk/credential-provider-env': 3.972.29 + '@aws-sdk/credential-provider-http': 3.972.31 + '@aws-sdk/credential-provider-ini': 3.972.33 + '@aws-sdk/credential-provider-process': 3.972.29 + '@aws-sdk/credential-provider-sso': 3.972.33 + '@aws-sdk/credential-provider-web-identity': 3.972.33 '@aws-sdk/types': 3.973.8 '@smithy/credential-provider-imds': 4.2.14 '@smithy/property-provider': 4.2.14 @@ -6844,20 +6845,20 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.972.28': + '@aws-sdk/credential-provider-process@3.972.29': dependencies: - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.972.32': + '@aws-sdk/credential-provider-sso@3.972.33': dependencies: - '@aws-sdk/core': 3.974.2 - '@aws-sdk/nested-clients': 3.997.0 - '@aws-sdk/token-providers': 3.1033.0 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/nested-clients': 3.997.1 + '@aws-sdk/token-providers': 3.1034.0 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -6866,10 +6867,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.972.32': + '@aws-sdk/credential-provider-web-identity@3.972.33': dependencies: - '@aws-sdk/core': 3.974.2 - '@aws-sdk/nested-clients': 3.997.0 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/nested-clients': 3.997.1 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -6895,12 +6896,12 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.974.10': + '@aws-sdk/middleware-flexible-checksums@3.974.11': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/crc64-nvme': 3.972.7 '@aws-sdk/types': 3.973.8 '@smithy/is-array-buffer': 4.2.2 @@ -6939,9 +6940,9 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.31': + '@aws-sdk/middleware-sdk-s3@3.972.32': dependencies: - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-arn-parser': 3.972.3 '@smithy/core': 3.23.16 @@ -6962,32 +6963,32 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.972.32': + '@aws-sdk/middleware-user-agent@3.972.33': dependencies: - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 + '@aws-sdk/util-endpoints': 3.996.8 '@smithy/core': 3.23.16 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.14.1 '@smithy/util-retry': 4.3.3 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.0': + '@aws-sdk/nested-clients@3.997.1': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.2 + '@aws-sdk/core': 3.974.3 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.32 - '@aws-sdk/region-config-resolver': 3.972.12 - '@aws-sdk/signature-v4-multi-region': 3.996.19 + '@aws-sdk/middleware-user-agent': 3.972.33 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.20 '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 + '@aws-sdk/util-endpoints': 3.996.8 '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.18 + '@aws-sdk/util-user-agent-node': 3.973.19 '@smithy/config-resolver': 4.4.17 '@smithy/core': 3.23.16 '@smithy/fetch-http-handler': 5.3.17 @@ -7017,7 +7018,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/region-config-resolver@3.972.12': + '@aws-sdk/region-config-resolver@3.972.13': dependencies: '@aws-sdk/types': 3.973.8 '@smithy/config-resolver': 4.4.17 @@ -7025,19 +7026,19 @@ snapshots: '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.19': + '@aws-sdk/signature-v4-multi-region@3.996.20': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.31 + '@aws-sdk/middleware-sdk-s3': 3.972.32 '@aws-sdk/types': 3.973.8 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 '@smithy/types': 4.14.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1033.0': + '@aws-sdk/token-providers@3.1034.0': dependencies: - '@aws-sdk/core': 3.974.2 - '@aws-sdk/nested-clients': 3.997.0 + '@aws-sdk/core': 3.974.3 + '@aws-sdk/nested-clients': 3.997.1 '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -7055,7 +7056,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.996.7': + '@aws-sdk/util-endpoints@3.996.8': dependencies: '@aws-sdk/types': 3.973.8 '@smithy/types': 4.14.1 @@ -7074,9 +7075,9 @@ snapshots: bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.973.18': + '@aws-sdk/util-user-agent-node@3.973.19': dependencies: - '@aws-sdk/middleware-user-agent': 3.972.32 + '@aws-sdk/middleware-user-agent': 3.972.33 '@aws-sdk/types': 3.973.8 '@smithy/node-config-provider': 4.3.14 '@smithy/types': 4.14.1 @@ -9996,9 +9997,9 @@ snapshots: browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.20 - caniuse-lite: 1.0.30001788 - electron-to-chromium: 1.5.340 - node-releases: 2.0.37 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.343 + node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) bs-logger@0.2.6: @@ -10080,7 +10081,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001788: {} + caniuse-lite@1.0.30001790: {} capital-case@1.0.4: dependencies: @@ -10558,7 +10559,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.340: {} + electron-to-chromium@1.5.343: {} elegant-spinner@1.0.1: {} @@ -10575,7 +10576,7 @@ snapshots: enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.2 + tapable: 2.3.3 entities@4.5.0: {} @@ -10740,7 +10741,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) eslint-config-xo-space: 0.35.0(eslint@8.57.1) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-mocha: 10.5.0(eslint@8.57.1) eslint-plugin-n: 15.7.0(eslint@8.57.1) eslint-plugin-perfectionist: 2.11.0(eslint@8.57.1)(typescript@5.9.3) @@ -10802,7 +10803,7 @@ snapshots: eslint-config-xo: 0.49.0(eslint@8.57.1) eslint-config-xo-space: 0.35.0(eslint@8.57.1) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsdoc: 50.8.0(eslint@8.57.1) eslint-plugin-mocha: 10.5.0(eslint@8.57.1) eslint-plugin-n: 17.24.0(eslint@8.57.1)(typescript@5.9.3) @@ -10854,7 +10855,7 @@ snapshots: tinyglobby: 0.2.16 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -10959,7 +10960,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -11017,7 +11018,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13005,7 +13006,7 @@ snapshots: dependencies: process-on-spawn: 1.1.0 - node-releases@2.0.37: {} + node-releases@2.0.38: {} normalize-package-data@2.5.0: dependencies: @@ -13392,7 +13393,7 @@ snapshots: pluralize@8.0.0: {} - pnpm@10.33.0: {} + pnpm@10.33.1: {} possible-typed-array-names@1.1.0: {} @@ -14053,7 +14054,7 @@ snapshots: typical: 2.6.1 wordwrapjs: 3.0.0 - tapable@2.3.2: {} + tapable@2.3.3: {} tar@7.5.13: dependencies: