From 4f81f628eecae6a9ca592f72390aedb3f4362f9f Mon Sep 17 00:00:00 2001 From: raj pandey Date: Tue, 17 Mar 2026 13:37:54 +0530 Subject: [PATCH] fix(import): align import with single-branch export, import; path resolution and target branch handling --- .../src/export/module-exporter.ts | 3 +- .../src/export/modules/assets.ts | 5 +- .../src/export/modules/composable-studio.ts | 5 +- .../src/export/modules/content-types.ts | 5 +- .../src/export/modules/custom-roles.ts | 4 +- .../src/export/modules/entries.ts | 12 +- .../src/export/modules/environments.ts | 5 +- .../src/export/modules/extensions.ts | 5 +- .../src/export/modules/global-fields.ts | 5 +- .../src/export/modules/labels.ts | 5 +- .../src/export/modules/locales.ts | 5 +- .../src/export/modules/marketplace-apps.ts | 4 +- .../src/export/modules/stack.ts | 4 +- .../src/export/modules/taxonomies.ts | 7 +- .../src/export/modules/webhooks.ts | 5 +- .../src/export/modules/workflows.ts | 5 +- .../contentstack-export/src/utils/index.ts | 1 + .../src/utils/path-helper.ts | 9 ++ .../src/utils/setup-export-dir.ts | 9 +- .../test/unit/export/module-exporter.test.ts | 131 ++++++++++++++++++ .../test/unit/export/modules/labels.test.ts | 10 +- .../export/modules/marketplace-apps.test.ts | 9 +- .../test/unit/utils/path-helper.test.ts | 30 ++++ .../test/unit/utils/setup-export-dir.test.ts | 57 ++++++++ .../src/utils/import-config-handler.ts | 4 +- .../test/unit/import-config-handler.test.ts | 17 +++ .../src/commands/cm/stacks/import.ts | 4 +- .../src/types/import-config.ts | 1 - .../src/utils/backup-handler.ts | 4 +- .../src/utils/import-config-handler.ts | 1 - .../src/utils/import-path-resolver.ts | 85 +----------- .../contentstack-import/src/utils/index.ts | 3 +- .../unit/utils/import-path-resolver.test.ts | 83 ++--------- .../src/export/attributes.ts | 3 +- .../src/export/audiences.ts | 3 +- .../src/export/events.ts | 3 +- .../src/export/experiences.ts | 3 +- .../src/export/projects.ts | 3 +- .../src/export/variant-entries.ts | 3 +- .../test/unit/export/variant-entries.test.ts | 14 ++ 40 files changed, 339 insertions(+), 235 deletions(-) create mode 100644 packages/contentstack-export/src/utils/path-helper.ts create mode 100644 packages/contentstack-export/test/unit/export/module-exporter.test.ts create mode 100644 packages/contentstack-export/test/unit/utils/path-helper.test.ts create mode 100644 packages/contentstack-export/test/unit/utils/setup-export-dir.test.ts diff --git a/packages/contentstack-export/src/export/module-exporter.ts b/packages/contentstack-export/src/export/module-exporter.ts index 2e86dbedd..85cbb3ea2 100644 --- a/packages/contentstack-export/src/export/module-exporter.ts +++ b/packages/contentstack-export/src/export/module-exporter.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import { ContentstackClient, handleAndLogError, @@ -66,7 +65,7 @@ class ModuleExporter { try { this.exportConfig.branchName = targetBranch.uid; this.stackAPIClient.stackHeaders.branch = targetBranch.uid; - this.exportConfig.branchDir = path.join(this.exportConfig.exportDir, targetBranch.uid); + this.exportConfig.branchDir = this.exportConfig.exportDir; // Initialize progress manager for the target branch CLIProgressManager.clearGlobalSummary(); diff --git a/packages/contentstack-export/src/export/modules/assets.ts b/packages/contentstack-export/src/export/modules/assets.ts index 0d6233f0e..2a8be94b9 100644 --- a/packages/contentstack-export/src/export/modules/assets.ts +++ b/packages/contentstack-export/src/export/modules/assets.ts @@ -25,7 +25,7 @@ import { PATH_CONSTANTS } from '../../constants'; import config from '../../config'; import { ModuleClassParams } from '../../types'; import BaseClass, { CustomPromiseHandler, CustomPromiseHandlerInput } from './base-class'; -import { PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; +import { getExportBasePath, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; export default class ExportAssets extends BaseClass { private assetsRootPath: string; @@ -49,8 +49,7 @@ export default class ExportAssets extends BaseClass { async start(): Promise { this.assetsRootPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.assetConfig.dirName, ); log.debug(`Assets root path resolved to: ${this.assetsRootPath}`, this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/composable-studio.ts b/packages/contentstack-export/src/export/modules/composable-studio.ts index 265248d12..b0bfe171e 100644 --- a/packages/contentstack-export/src/export/modules/composable-studio.ts +++ b/packages/contentstack-export/src/export/modules/composable-studio.ts @@ -9,7 +9,7 @@ import { authenticationHandler, } from '@contentstack/cli-utilities'; -import { fsUtil, getOrgUid } from '../../utils'; +import { fsUtil, getExportBasePath, getOrgUid } from '../../utils'; import { ModuleClassParams, ComposableStudioConfig, ExportConfig, ComposableStudioProject } from '../../types'; export default class ExportComposableStudio { @@ -41,8 +41,7 @@ export default class ExportComposableStudio { } this.composableStudioPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.composableStudioConfig.dirName, ); log.debug(`Studio folder path: ${this.composableStudioPath}`, this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/content-types.ts b/packages/contentstack-export/src/export/modules/content-types.ts index d2c79781b..34761713a 100644 --- a/packages/contentstack-export/src/export/modules/content-types.ts +++ b/packages/contentstack-export/src/export/modules/content-types.ts @@ -4,7 +4,7 @@ import { PATH_CONSTANTS } from '../../constants'; import BaseClass from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; -import { fsUtil, executeTask, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, executeTask, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ContentTypesExport extends BaseClass { private stackAPIClient: ReturnType; @@ -48,8 +48,7 @@ export default class ContentTypesExport extends BaseClass { this.applyQueryFilters(this.qs, 'content-types'); this.contentTypesDirPath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(getExportBasePath(exportConfig)), sanitizePath(this.contentTypesConfig.dirName), ); this.contentTypes = []; diff --git a/packages/contentstack-export/src/export/modules/custom-roles.ts b/packages/contentstack-export/src/export/modules/custom-roles.ts index 92ddba79a..9c2dc941e 100644 --- a/packages/contentstack-export/src/export/modules/custom-roles.ts +++ b/packages/contentstack-export/src/export/modules/custom-roles.ts @@ -8,6 +8,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { fsUtil, + getExportBasePath, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, @@ -43,8 +44,7 @@ export default class ExportCustomRoles extends BaseClass { 'CUSTOM-ROLES: Analyzing roles and locales...', async () => { this.rolesFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.customRolesConfig.dirName, ); diff --git a/packages/contentstack-export/src/export/modules/entries.ts b/packages/contentstack-export/src/export/modules/entries.ts index f3e439b92..4e63b8b4e 100644 --- a/packages/contentstack-export/src/export/modules/entries.ts +++ b/packages/contentstack-export/src/export/modules/entries.ts @@ -10,7 +10,7 @@ import { } from '@contentstack/cli-utilities'; import { PATH_CONSTANTS } from '../../constants'; import { Export, ExportProjects } from '@contentstack/cli-variants'; -import { fsUtil, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, MODULE_NAMES } from '../../utils'; import BaseClass, { ApiOptions } from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; @@ -41,20 +41,18 @@ export default class EntriesExport extends BaseClass { this.stackAPIClient = stackAPIClient; this.exportConfig = exportConfig; this.entriesConfig = exportConfig.modules.entries; + const basePath = getExportBasePath(exportConfig); this.entriesDirPath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(basePath), sanitizePath(this.entriesConfig.dirName), ); this.localesFilePath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(basePath), sanitizePath(exportConfig.modules.locales.dirName), sanitizePath(exportConfig.modules.locales.fileName), ); this.contentTypesDirPath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(basePath), sanitizePath(exportConfig.modules.content_types.dirName), ); this.projectInstance = new ExportProjects(this.exportConfig); diff --git a/packages/contentstack-export/src/export/modules/environments.ts b/packages/contentstack-export/src/export/modules/environments.ts index 5fc29ffa0..274fab8ee 100644 --- a/packages/contentstack-export/src/export/modules/environments.ts +++ b/packages/contentstack-export/src/export/modules/environments.ts @@ -5,7 +5,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { EnvironmentConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportEnvironments extends BaseClass { private environments: Record; @@ -32,8 +32,7 @@ export default class ExportEnvironments extends BaseClass { // Setup with loading spinner const [totalCount] = await this.withLoadingSpinner('ENVIRONMENTS: Analyzing environments...', async () => { this.environmentsFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.environmentConfig.dirName, ); await fsUtil.makeDirectory(this.environmentsFolderPath); diff --git a/packages/contentstack-export/src/export/modules/extensions.ts b/packages/contentstack-export/src/export/modules/extensions.ts index 88f1cb434..f007e4178 100644 --- a/packages/contentstack-export/src/export/modules/extensions.ts +++ b/packages/contentstack-export/src/export/modules/extensions.ts @@ -5,7 +5,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { ExtensionsConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportExtensions extends BaseClass { private extensionsFolderPath: string; @@ -33,8 +33,7 @@ export default class ExportExtensions extends BaseClass { // Setup with loading spinner const [totalCount] = await this.withLoadingSpinner('EXTENSIONS: Analyzing extensions...', async () => { this.extensionsFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.extensionConfig.dirName, ); await fsUtil.makeDirectory(this.extensionsFolderPath); diff --git a/packages/contentstack-export/src/export/modules/global-fields.ts b/packages/contentstack-export/src/export/modules/global-fields.ts index 248155064..62b5f1406 100644 --- a/packages/contentstack-export/src/export/modules/global-fields.ts +++ b/packages/contentstack-export/src/export/modules/global-fields.ts @@ -3,7 +3,7 @@ import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePat import BaseClass from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class GlobalFieldsExport extends BaseClass { private stackAPIClient: ReturnType; @@ -38,8 +38,7 @@ export default class GlobalFieldsExport extends BaseClass { include_global_field_schema: true, }; this.globalFieldsDirPath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(getExportBasePath(exportConfig)), sanitizePath(this.globalFieldsConfig.dirName), ); this.globalFields = []; diff --git a/packages/contentstack-export/src/export/modules/labels.ts b/packages/contentstack-export/src/export/modules/labels.ts index bfd2305a6..c5a44fee2 100644 --- a/packages/contentstack-export/src/export/modules/labels.ts +++ b/packages/contentstack-export/src/export/modules/labels.ts @@ -5,7 +5,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { LabelConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportLabels extends BaseClass { private labels: Record>; @@ -32,8 +32,7 @@ export default class ExportLabels extends BaseClass { // Setup with loading spinner const [totalCount] = await this.withLoadingSpinner('LABELS: Analyzing labels...', async () => { this.labelsFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.labelConfig.dirName, ); diff --git a/packages/contentstack-export/src/export/modules/locales.ts b/packages/contentstack-export/src/export/modules/locales.ts index 7cd206a4f..52609f8e5 100644 --- a/packages/contentstack-export/src/export/modules/locales.ts +++ b/packages/contentstack-export/src/export/modules/locales.ts @@ -3,7 +3,7 @@ import { ContentstackClient, handleAndLogError, messageHandler, log, sanitizePat import BaseClass from './base-class'; import { ExportConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class LocaleExport extends BaseClass { private stackAPIClient: ReturnType; @@ -42,8 +42,7 @@ export default class LocaleExport extends BaseClass { }, }; this.localesPath = path.resolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(getExportBasePath(exportConfig)), sanitizePath(this.localeConfig.dirName), ); this.locales = {}; diff --git a/packages/contentstack-export/src/export/modules/marketplace-apps.ts b/packages/contentstack-export/src/export/modules/marketplace-apps.ts index cb6fd1fd4..4a6405c55 100644 --- a/packages/contentstack-export/src/export/modules/marketplace-apps.ts +++ b/packages/contentstack-export/src/export/modules/marketplace-apps.ts @@ -20,6 +20,7 @@ import { import BaseClass from './base-class'; import { fsUtil, + getExportBasePath, getOrgUid, createNodeCryptoInstance, getDeveloperHubUrl, @@ -118,8 +119,7 @@ export default class ExportMarketplaceApps extends BaseClass { async setupPaths(): Promise { this.marketplaceAppPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.marketplaceAppConfig.dirName, ); log.debug(`Marketplace apps folder path: '${this.marketplaceAppPath}'`, this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/stack.ts b/packages/contentstack-export/src/export/modules/stack.ts index 5007235da..5b138b909 100644 --- a/packages/contentstack-export/src/export/modules/stack.ts +++ b/packages/contentstack-export/src/export/modules/stack.ts @@ -11,6 +11,7 @@ import { PATH_CONSTANTS } from '../../constants'; import BaseClass from './base-class'; import { fsUtil, + getExportBasePath, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, @@ -31,8 +32,7 @@ export default class ExportStack extends BaseClass { this.stackConfig = exportConfig.modules.stack; this.qs = { include_count: true }; this.stackFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.stackConfig.dirName, ); this.exportConfig.context.module = MODULE_CONTEXTS.STACK; diff --git a/packages/contentstack-export/src/export/modules/taxonomies.ts b/packages/contentstack-export/src/export/modules/taxonomies.ts index 6f44a2bdc..5075a54fe 100644 --- a/packages/contentstack-export/src/export/modules/taxonomies.ts +++ b/packages/contentstack-export/src/export/modules/taxonomies.ts @@ -7,6 +7,7 @@ import { handleAndLogError, messageHandler, log, sanitizePath } from '@contentst import BaseClass from './base-class'; import { fsUtil, + getExportBasePath, PROCESS_NAMES, MODULE_CONTEXTS, PROCESS_STATUS, @@ -43,8 +44,7 @@ export default class ExportTaxonomies extends BaseClass { this.exportConfig.context.module = MODULE_CONTEXTS.TAXONOMIES; this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.TAXONOMIES]; this.localesFilePath = pResolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(getExportBasePath(exportConfig)), sanitizePath(exportConfig.modules.locales.dirName), sanitizePath(exportConfig.modules.locales.fileName), ); @@ -98,8 +98,7 @@ export default class ExportTaxonomies extends BaseClass { private async initializeExport(): Promise { return this.withLoadingSpinner('TAXONOMIES: Analyzing taxonomy structure...', async () => { this.taxonomiesFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.taxonomiesConfig.dirName, ); log.debug(`Taxonomies folder path: '${this.taxonomiesFolderPath}'`, this.exportConfig.context); diff --git a/packages/contentstack-export/src/export/modules/webhooks.ts b/packages/contentstack-export/src/export/modules/webhooks.ts index e70fc8f83..7ce4d972f 100644 --- a/packages/contentstack-export/src/export/modules/webhooks.ts +++ b/packages/contentstack-export/src/export/modules/webhooks.ts @@ -5,7 +5,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { WebhookConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportWebhooks extends BaseClass { private webhooks: Record>; @@ -33,8 +33,7 @@ export default class ExportWebhooks extends BaseClass { // Setup with loading spinner const [totalCount] = await this.withLoadingSpinner('WEBHOOKS: Analyzing webhooks...', async () => { this.webhooksFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.webhookConfig.dirName, ); diff --git a/packages/contentstack-export/src/export/modules/workflows.ts b/packages/contentstack-export/src/export/modules/workflows.ts index 09ebfb225..77c642e97 100644 --- a/packages/contentstack-export/src/export/modules/workflows.ts +++ b/packages/contentstack-export/src/export/modules/workflows.ts @@ -5,7 +5,7 @@ import { handleAndLogError, messageHandler, log } from '@contentstack/cli-utilit import BaseClass from './base-class'; import { WorkflowConfig, ModuleClassParams } from '../../types'; -import { fsUtil, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; +import { fsUtil, getExportBasePath, MODULE_CONTEXTS, MODULE_NAMES } from '../../utils'; export default class ExportWorkFlows extends BaseClass { private workflows: Record>; @@ -32,8 +32,7 @@ export default class ExportWorkFlows extends BaseClass { // Setup with loading spinner const [totalCount] = await this.withLoadingSpinner('WORKFLOWS: Analyzing workflows...', async () => { this.webhooksFolderPath = pResolve( - this.exportConfig.exportDir, - this.exportConfig.branchName || '', + getExportBasePath(this.exportConfig), this.workflowConfig.dirName, ); diff --git a/packages/contentstack-export/src/utils/index.ts b/packages/contentstack-export/src/utils/index.ts index 9cbd32cac..8ae27db52 100644 --- a/packages/contentstack-export/src/utils/index.ts +++ b/packages/contentstack-export/src/utils/index.ts @@ -4,6 +4,7 @@ export * as fileHelper from './file-helper'; export { fsUtil } from './file-helper'; export { default as setupBranches } from './setup-branches'; export { default as setupExportDir } from './setup-export-dir'; +export { getExportBasePath } from './path-helper'; export { log, unlinkFileLogger } from './logger'; export { default as login } from './basic-login'; export * from './common-helper'; diff --git a/packages/contentstack-export/src/utils/path-helper.ts b/packages/contentstack-export/src/utils/path-helper.ts new file mode 100644 index 000000000..6c46d1b17 --- /dev/null +++ b/packages/contentstack-export/src/utils/path-helper.ts @@ -0,0 +1,9 @@ +import { ExportConfig } from '../types'; + +/** + * Returns the base path under which module content should be exported. + * Content is always written directly under this path (no branch subfolder). + */ +export function getExportBasePath(exportConfig: ExportConfig): string { + return exportConfig.branchDir ?? exportConfig.exportDir; +} diff --git a/packages/contentstack-export/src/utils/setup-export-dir.ts b/packages/contentstack-export/src/utils/setup-export-dir.ts index 76de5b364..1f46072af 100644 --- a/packages/contentstack-export/src/utils/setup-export-dir.ts +++ b/packages/contentstack-export/src/utils/setup-export-dir.ts @@ -1,14 +1,7 @@ -import path from 'path'; -import { sanitizePath } from '@contentstack/cli-utilities'; - import { ExportConfig } from '../types'; import { makeDirectory } from './file-helper'; 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)))), - ); - } + // Single-branch export: content goes directly under exportDir; no per-branch subdirs. } diff --git a/packages/contentstack-export/test/unit/export/module-exporter.test.ts b/packages/contentstack-export/test/unit/export/module-exporter.test.ts new file mode 100644 index 000000000..594d057a3 --- /dev/null +++ b/packages/contentstack-export/test/unit/export/module-exporter.test.ts @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import ModuleExporter from '../../../src/export/module-exporter'; +import { ExportConfig } from '../../../src/types'; + +describe('ModuleExporter exportByBranches', () => { + let sandbox: sinon.SinonSandbox; + const exportDir = '/test/export'; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should set branchDir to exportDir when no branch specified (default main)', async () => { + const branches = [ + { uid: 'main', source: '', name: 'main' }, + { uid: 'dev', source: 'main', name: 'dev' }, + ]; + const exportConfig: Partial = { + exportDir, + branches, + apiKey: 'test-key', + management_token: 'token', + context: {} as any, + modules: { types: ['stack'] } as any, + }; + const mockStackClient = { stackHeaders: {} }; + const mockManagementClient = { + stack: sinon.stub().returns(mockStackClient), + }; + + const exporter = new ModuleExporter( + mockManagementClient as any, + exportConfig as ExportConfig, + ); + const exportStub = sandbox.stub(exporter, 'export').resolves(); + + await exporter.exportByBranches(); + + expect(exportConfig.branchDir).to.equal(exportDir); + expect(exportConfig.branchName).to.equal('main'); + expect(exportStub.calledOnce).to.be.true; + }); + + it('should set branchDir to exportDir when branch is specified via branchName', async () => { + const branches = [ + { uid: 'main', source: '', name: 'main' }, + { uid: 'dev', source: 'main', name: 'dev' }, + ]; + const exportConfig: Partial = { + exportDir, + branchName: 'dev', + branches, + apiKey: 'test-key', + management_token: 'token', + context: {} as any, + modules: { types: ['stack'] } as any, + }; + const mockStackClient = { stackHeaders: {} }; + const mockManagementClient = { + stack: sinon.stub().returns(mockStackClient), + }; + + const exporter = new ModuleExporter( + mockManagementClient as any, + exportConfig as ExportConfig, + ); + const exportStub = sandbox.stub(exporter, 'export').resolves(); + + await exporter.exportByBranches(); + + expect(exportConfig.branchDir).to.equal(exportDir); + expect(exportConfig.branchName).to.equal('dev'); + expect(exportStub.calledOnce).to.be.true; + }); + + it('should throw when specified branch not found in branches', async () => { + const branches = [{ uid: 'main', source: '', name: 'main' }]; + const exportConfig: Partial = { + exportDir, + branchName: 'nonexistent', + branches, + apiKey: 'test-key', + management_token: 'token', + context: {} as any, + modules: { types: [] } as any, + }; + const mockManagementClient = { stack: sinon.stub().returns({ stackHeaders: {} }) }; + + const exporter = new ModuleExporter( + mockManagementClient as any, + exportConfig as ExportConfig, + ); + + try { + await exporter.exportByBranches(); + expect.fail('Should have thrown'); + } catch (err: any) { + expect(err.message).to.include("Branch 'nonexistent' not found"); + } + }); + + it('should throw when no main branch in branches', async () => { + const branches = [{ uid: 'dev', source: '', name: 'dev' }]; + const exportConfig: Partial = { + exportDir, + branches, + apiKey: 'test-key', + management_token: 'token', + context: {} as any, + modules: { types: [] } as any, + }; + const mockManagementClient = { stack: sinon.stub().returns({ stackHeaders: {} }) }; + + const exporter = new ModuleExporter( + mockManagementClient as any, + exportConfig as ExportConfig, + ); + + try { + await exporter.exportByBranches(); + expect.fail('Should have thrown'); + } catch (err: any) { + expect(err.message).to.include('No main branch'); + } + }); +}); diff --git a/packages/contentstack-export/test/unit/export/modules/labels.test.ts b/packages/contentstack-export/test/unit/export/modules/labels.test.ts index 7e1b86a33..cc6825603 100644 --- a/packages/contentstack-export/test/unit/export/modules/labels.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/labels.test.ts @@ -450,8 +450,9 @@ describe('ExportLabels', () => { expect(exportLabels.labelsFolderPath).to.include('labels'); }); - it('should handle branchName in path when provided', async () => { - mockExportConfig.branchName = 'test-branch'; + it('should use export path directly when branchDir is set (content at path, no branch subfolder)', async () => { + mockExportConfig.branchDir = '/test/export'; + mockExportConfig.branchName = 'main'; exportLabels = new ExportLabels({ exportConfig: mockExportConfig, stackAPIClient: mockStackClient, @@ -471,8 +472,9 @@ describe('ExportLabels', () => { await exportLabels.start(); - // Verify branchName is included in path - expect(exportLabels.labelsFolderPath).to.include('test-branch'); + expect(exportLabels.labelsFolderPath).to.include('labels'); + expect(exportLabels.labelsFolderPath).to.include('/test/export'); + expect(exportLabels.labelsFolderPath).to.not.include('main'); }); it('should write file with correct path and data', async () => { diff --git a/packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts b/packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts index 4ddcc03ec..c815da323 100644 --- a/packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts +++ b/packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts @@ -235,8 +235,9 @@ describe('ExportMarketplaceApps', () => { configHandlerGetStub.restore(); }); - it('should handle branchName in path when provided', async () => { - mockExportConfig.branchName = 'test-branch'; + it('should use export path directly when branchDir is set (content at path, no branch subfolder)', async () => { + mockExportConfig.branchDir = '/test/export'; + mockExportConfig.branchName = 'main'; exportMarketplaceApps = new ExportMarketplaceApps({ exportConfig: mockExportConfig, stackAPIClient: {} as any, @@ -249,7 +250,9 @@ describe('ExportMarketplaceApps', () => { await exportMarketplaceApps.start(); - expect(exportMarketplaceApps.marketplaceAppPath).to.include('test-branch'); + expect(exportMarketplaceApps.marketplaceAppPath).to.include('marketplace_apps'); + expect(exportMarketplaceApps.marketplaceAppPath).to.include('/test/export'); + expect(exportMarketplaceApps.marketplaceAppPath).to.not.include('main'); exportAppsStub.restore(); configHandlerGetStub.restore(); diff --git a/packages/contentstack-export/test/unit/utils/path-helper.test.ts b/packages/contentstack-export/test/unit/utils/path-helper.test.ts new file mode 100644 index 000000000..b36731dc5 --- /dev/null +++ b/packages/contentstack-export/test/unit/utils/path-helper.test.ts @@ -0,0 +1,30 @@ +import { expect } from 'chai'; +import { getExportBasePath } from '../../../src/utils/path-helper'; +import { ExportConfig } from '../../../src/types'; + +describe('path-helper getExportBasePath', () => { + const exportDir = '/test/export'; + + it('should return branchDir when branchDir is set', () => { + const config = { + exportDir, + branchDir: '/custom/branch/path', + } as Partial as ExportConfig; + expect(getExportBasePath(config)).to.equal('/custom/branch/path'); + }); + + it('should return exportDir when branchDir is not set', () => { + const config = { + exportDir, + } as Partial as ExportConfig; + expect(getExportBasePath(config)).to.equal(exportDir); + }); + + it('should return exportDir when branchDir is undefined', () => { + const config = { + exportDir, + branchDir: undefined, + } as Partial as ExportConfig; + expect(getExportBasePath(config)).to.equal(exportDir); + }); +}); diff --git a/packages/contentstack-export/test/unit/utils/setup-export-dir.test.ts b/packages/contentstack-export/test/unit/utils/setup-export-dir.test.ts new file mode 100644 index 000000000..e6c12ff5b --- /dev/null +++ b/packages/contentstack-export/test/unit/utils/setup-export-dir.test.ts @@ -0,0 +1,57 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import setupExportDir from '../../../src/utils/setup-export-dir'; +import * as fileHelper from '../../../src/utils/file-helper'; +import { ExportConfig } from '../../../src/types'; + +describe('Setup Export Dir', () => { + let sandbox: sinon.SinonSandbox; + let makeDirectoryStub: sinon.SinonStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + makeDirectoryStub = sandbox.stub(fileHelper, 'makeDirectory'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should call makeDirectory only with exportDir when branches is undefined', async () => { + const exportConfig = { + exportDir: '/test/export', + } as Partial as ExportConfig; + + await setupExportDir(exportConfig); + + expect(makeDirectoryStub.calledOnce).to.be.true; + expect(makeDirectoryStub.firstCall.args[0]).to.equal('/test/export'); + }); + + it('should call makeDirectory only with exportDir when branches is empty array', async () => { + const exportConfig = { + exportDir: '/test/export', + branches: [], + } as Partial as ExportConfig; + + await setupExportDir(exportConfig); + + expect(makeDirectoryStub.calledOnce).to.be.true; + expect(makeDirectoryStub.firstCall.args[0]).to.equal('/test/export'); + }); + + it('should call makeDirectory only with exportDir when branches has one or more branches', async () => { + const exportConfig = { + exportDir: '/test/export', + branches: [ + { uid: 'main', source: '' }, + { uid: 'dev', source: 'main' }, + ], + } as Partial as ExportConfig; + + await setupExportDir(exportConfig); + + expect(makeDirectoryStub.calledOnce).to.be.true; + expect(makeDirectoryStub.firstCall.args[0]).to.equal('/test/export'); + }); +}); diff --git a/packages/contentstack-import-setup/src/utils/import-config-handler.ts b/packages/contentstack-import-setup/src/utils/import-config-handler.ts index abfa3a0f3..6c2768177 100644 --- a/packages/contentstack-import-setup/src/utils/import-config-handler.ts +++ b/packages/contentstack-import-setup/src/utils/import-config-handler.ts @@ -11,7 +11,7 @@ const setupConfig = async (importCmdFlags: any): Promise => { // This ensures the logger respects the showConsoleLogs setting correctly configHandler.set('log.progressSupportedModule', 'import-setup'); - let config: ImportConfig = merge({}, defaultConfig); + const config: ImportConfig = merge({}, defaultConfig); // setup the config // if (importCmdFlags['config']) { // let externalConfig = await readFile(importCmdFlags['config']); @@ -76,10 +76,12 @@ const setupConfig = async (importCmdFlags: any): Promise => { if (importCmdFlags['branch']) { config.branchName = importCmdFlags['branch']; + config.branchDir = config.contentDir; } if (importCmdFlags['branch-alias']) { config.branchAlias = importCmdFlags['branch-alias']; + config.branchDir = config.contentDir; } config.selectedModules = importCmdFlags['module'] || [await askSelectedModules()]; diff --git a/packages/contentstack-import-setup/test/unit/import-config-handler.test.ts b/packages/contentstack-import-setup/test/unit/import-config-handler.test.ts index 6d05f440d..4c1bce0f9 100644 --- a/packages/contentstack-import-setup/test/unit/import-config-handler.test.ts +++ b/packages/contentstack-import-setup/test/unit/import-config-handler.test.ts @@ -121,6 +121,23 @@ describe('Import Config Handler', () => { const config = await setupConfig(flags); expect(config.branchName).to.equal('development'); + expect(config.branchDir).to.equal(path.resolve(contentDir)); + }); + + it('should set branchDir to contentDir when branch or branch-alias is provided', async () => { + const flagsWithBranch = { + 'data-dir': contentDir, + branch: 'dev', + }; + const configBranch = await setupConfig(flagsWithBranch); + expect(configBranch.branchDir).to.equal(path.resolve(contentDir)); + + const flagsWithAlias = { + 'data-dir': contentDir, + 'branch-alias': 'my-alias', + }; + const configAlias = await setupConfig(flagsWithAlias); + expect(configAlias.branchDir).to.equal(path.resolve(contentDir)); }); it('should ask for modules when none are provided', async () => { diff --git a/packages/contentstack-import/src/commands/cm/stacks/import.ts b/packages/contentstack-import/src/commands/cm/stacks/import.ts index 9a086e780..062d1e22f 100644 --- a/packages/contentstack-import/src/commands/cm/stacks/import.ts +++ b/packages/contentstack-import/src/commands/cm/stacks/import.ts @@ -124,7 +124,7 @@ export default class ImportCommand extends Command { importConfig = await setupImportConfig(flags); // Prepare the context object createLogContext( - this.context?.info?.command || 'cm:stacks:export', + this.context?.info?.command || 'cm:stacks:import', importConfig.apiKey, importConfig.authenticationMethod ); @@ -152,7 +152,7 @@ export default class ImportCommand extends Command { } const moduleImporter = new ModuleImporter(managementAPIClient, importConfig); - const result = await moduleImporter.start(); + await moduleImporter.start(); backupDir = importConfig.backupDir; //Note: Final summary is now handled by summary manager CLIProgressManager.printGlobalSummary(); diff --git a/packages/contentstack-import/src/types/import-config.ts b/packages/contentstack-import/src/types/import-config.ts index 86db5668d..45caf81e9 100644 --- a/packages/contentstack-import/src/types/import-config.ts +++ b/packages/contentstack-import/src/types/import-config.ts @@ -28,7 +28,6 @@ export default interface ImportConfig extends DefaultConfig, ExternalConfig { contentTypes?: string[]; branches?: branch[]; branchEnabled?: boolean; - branchDir?: string; branchAlias?: string; moduleName?: Modules; master_locale: masterLocale; diff --git a/packages/contentstack-import/src/utils/backup-handler.ts b/packages/contentstack-import/src/utils/backup-handler.ts index 825c792d1..562ed45e0 100755 --- a/packages/contentstack-import/src/utils/backup-handler.ts +++ b/packages/contentstack-import/src/utils/backup-handler.ts @@ -13,9 +13,9 @@ export default async function backupHandler(importConfig: ImportConfig): Promise return importConfig.useBackedupDir; } - const sourceDir = importConfig.branchDir || importConfig.contentDir; + const sourceDir = importConfig.contentDir; log.debug( - `Using source directory for backup: ${sourceDir} (branchDir: ${importConfig.branchDir}, contentDir: ${importConfig.contentDir})`, + `Using source directory for backup: ${sourceDir}, contentDir: ${importConfig.contentDir})`, ); let backupDirPath: string; diff --git a/packages/contentstack-import/src/utils/import-config-handler.ts b/packages/contentstack-import/src/utils/import-config-handler.ts index 0eb0ee297..e3dbc8d09 100644 --- a/packages/contentstack-import/src/utils/import-config-handler.ts +++ b/packages/contentstack-import/src/utils/import-config-handler.ts @@ -98,7 +98,6 @@ const setupConfig = async (importCmdFlags: any): Promise => { if (importCmdFlags['branch']) { config.branchName = importCmdFlags['branch']; - config.branchDir = config.contentDir; } if (importCmdFlags['module']) { config.moduleName = importCmdFlags['module']; diff --git a/packages/contentstack-import/src/utils/import-path-resolver.ts b/packages/contentstack-import/src/utils/import-path-resolver.ts index 2bb5b921f..cb4d2e8f4 100644 --- a/packages/contentstack-import/src/utils/import-path-resolver.ts +++ b/packages/contentstack-import/src/utils/import-path-resolver.ts @@ -1,4 +1,4 @@ -import * as path from 'path'; +import * as path from 'node:path'; import { log } from '@contentstack/cli-utilities'; import defaultConfig from '../config'; @@ -6,60 +6,7 @@ import { ImportConfig } from '../types'; import { askBranchSelection } from './interactive'; import { fileExistsSync, readFile } from './file-helper'; -/** - * Selects a branch from directory structure when multiple branches are found - * @param contentDir - The content directory path - * @returns Promise<{ branchPath: string } | null> - */ -export const selectBranchFromDirectory = async (contentDir: string): Promise<{ branchPath: string } | null> => { - log.debug('Selecting branch directory from directory structure'); - - const branchesJsonPath = path.join(contentDir, 'branches.json'); - if (!fileExistsSync(branchesJsonPath)) { - log.debug('No branches.json found - not a branch-enabled export'); - return null; - } - try { - const branchesData = await readFile(branchesJsonPath); - const branches = branchesData || []; - - if (!branches || !Array.isArray(branches) || branches.length === 0) { - log.debug('No branches found in branches.json - not a branch-enabled export'); - return null; - } - - if (branches.length === 1) { - const singleBranch = branches[0]; - const branchPath = path.join(contentDir, singleBranch.uid); - - if (!fileExistsSync(branchPath)) { - log.warn(`Branch path does not exist: ${branchPath}, not a valid branch export`); - return null; - } - - log.debug(`Single branch detected: ${singleBranch.uid} - auto-resolving to: ${branchPath}`); - return { branchPath }; - } else { - log.debug(`Multiple branches detected: ${branches.map((b) => b.uid).join(', ')}`); - - const branchNames = branches.map((b) => b.uid); - const selectedBranch = await askBranchSelection(branchNames); - const selectedBranchPath = path.join(contentDir, selectedBranch); - - if (!fileExistsSync(selectedBranchPath)) { - log.warn(`Selected branch path does not exist: ${selectedBranchPath}, not a valid branch export`); - return null; - } - - log.debug(`User selected branch directory: ${selectedBranch} - using path: ${selectedBranchPath}`); - return { branchPath: selectedBranchPath }; - } - } catch (error) { - log.error(`Error selecting branch directory from directory structure: ${error}`); - throw error; - } -}; /** * Resolves the import path based on directory structure and user configuration @@ -77,38 +24,14 @@ export const resolveImportPath = async (importConfig: ImportConfig, stackAPIClie throw new Error(`Content directory does not exist: ${contentDir}`); } - if (importConfig.branchName) { - log.debug(`User specified branch: ${importConfig.branchName}`); - - const currentDirName = path.basename(contentDir); - if (currentDirName === importConfig.branchName) { - log.debug(`Already in correct branch directory: ${contentDir}`); - return contentDir; - } - - const branchPath = path.join(contentDir, importConfig.branchName); - if (fileExistsSync(branchPath)) { - log.debug(`Navigating to specified branch directory: ${branchPath}`); - return branchPath; - } - - log.debug(`Branch directory not found: ${branchPath}, using contentDir as-is`); - return contentDir; - } - const moduleTypes = defaultConfig.modules.types; const hasModuleFolders = moduleTypes.some((moduleType) => fileExistsSync(path.join(contentDir, moduleType))); if (hasModuleFolders) { - log.debug('Found module folders '); + log.debug('Found module folders at export root'); return contentDir; } - const branchSelection = await selectBranchFromDirectory(contentDir); - if (branchSelection) { - return branchSelection.branchPath; - } - log.debug('No specific structure detected - using contentDir as-is'); return contentDir; }; @@ -129,12 +52,10 @@ export const updateImportConfigWithResolvedPath = async ( return; } - importConfig.branchDir = resolvedPath; - importConfig.contentDir = resolvedPath; log.debug( - `Import config updated - contentDir: ${importConfig.contentDir}, branchDir: ${importConfig.branchDir}`, + `Import config updated - contentDir: ${importConfig.contentDir}`, ); }; diff --git a/packages/contentstack-import/src/utils/index.ts b/packages/contentstack-import/src/utils/index.ts index 49ab1146b..5d64eb515 100644 --- a/packages/contentstack-import/src/utils/index.ts +++ b/packages/contentstack-import/src/utils/index.ts @@ -1,6 +1,5 @@ export { setupBranchConfig } from './setup-branch'; -export { - selectBranchFromDirectory, +export { resolveImportPath, updateImportConfigWithResolvedPath, executeImportPathLogic diff --git a/packages/contentstack-import/test/unit/utils/import-path-resolver.test.ts b/packages/contentstack-import/test/unit/utils/import-path-resolver.test.ts index ebc848a21..536d8535a 100644 --- a/packages/contentstack-import/test/unit/utils/import-path-resolver.test.ts +++ b/packages/contentstack-import/test/unit/utils/import-path-resolver.test.ts @@ -87,67 +87,34 @@ describe('Import Path Resolver', () => { expect(result).to.be.null; }); - it('should auto-resolve single branch when branch path exists', async () => { + it('should return branchName for single branch (content at root)', async () => { const branchesJsonPath = path.join(contentDir, 'branches.json'); - const branchPath = path.join(contentDir, 'branch1'); const branchesData = [{ uid: 'branch1' }]; fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(branchPath).returns(true); readFileStub.withArgs(branchesJsonPath).resolves(branchesData); const result = await selectBranchFromDirectory(contentDir); - expect(result).to.deep.equal({ branchPath }); + expect(result).to.deep.equal({ branchName: 'branch1' }); expect(askBranchSelectionStub.called).to.be.false; }); - it('should return null when single branch path does not exist', async () => { + it('should prompt user when multiple branches exist and return selected branchName', async () => { const branchesJsonPath = path.join(contentDir, 'branches.json'); - const branchPath = path.join(contentDir, 'branch1'); - const branchesData = [{ uid: 'branch1' }]; - - fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(branchPath).returns(false); - readFileStub.withArgs(branchesJsonPath).resolves(branchesData); - - const result = await selectBranchFromDirectory(contentDir); - - expect(result).to.be.null; - }); - - it('should prompt user when multiple branches exist', async () => { - const branchesJsonPath = path.join(contentDir, 'branches.json'); - const selectedBranchPath = path.join(contentDir, 'branch2'); const branchesData = [{ uid: 'branch1' }, { uid: 'branch2' }, { uid: 'branch3' }]; fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(selectedBranchPath).returns(true); readFileStub.withArgs(branchesJsonPath).resolves(branchesData); askBranchSelectionStub.withArgs(['branch1', 'branch2', 'branch3']).resolves('branch2'); const result = await selectBranchFromDirectory(contentDir); - expect(result).to.deep.equal({ branchPath: selectedBranchPath }); + expect(result).to.deep.equal({ branchName: 'branch2' }); expect(askBranchSelectionStub.calledOnce).to.be.true; expect(askBranchSelectionStub.calledWith(['branch1', 'branch2', 'branch3'])).to.be.true; }); - it('should return null when selected branch path does not exist', async () => { - const branchesJsonPath = path.join(contentDir, 'branches.json'); - const selectedBranchPath = path.join(contentDir, 'branch2'); - const branchesData = [{ uid: 'branch1' }, { uid: 'branch2' }]; - - fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(selectedBranchPath).returns(false); - readFileStub.withArgs(branchesJsonPath).resolves(branchesData); - askBranchSelectionStub.withArgs(['branch1', 'branch2']).resolves('branch2'); - - const result = await selectBranchFromDirectory(contentDir); - - expect(result).to.be.null; - }); - it('should throw error when readFile fails', async () => { const branchesJsonPath = path.join(contentDir, 'branches.json'); const error = new Error('Read file error'); @@ -211,24 +178,9 @@ describe('Import Path Resolver', () => { expect(result).to.equal('/test/content'); }); - it('should return branch path when branchName is specified and path exists', async () => { + it('should return contentDir when branchName is specified (content always at root)', async () => { mockConfig.branchName = 'branch1'; - const branchPath = path.join('/test/content', 'branch1'); - - fileExistsSyncStub.withArgs('/test/content').returns(true); - fileExistsSyncStub.withArgs(branchPath).returns(true); - - const result = await resolveImportPath(mockConfig, mockStackAPIClient); - - expect(result).to.equal(branchPath); - }); - - it('should return contentDir when branchName is specified but path does not exist', async () => { - mockConfig.branchName = 'branch1'; - const branchPath = path.join('/test/content', 'branch1'); - fileExistsSyncStub.withArgs('/test/content').returns(true); - fileExistsSyncStub.withArgs(branchPath).returns(false); const result = await resolveImportPath(mockConfig, mockStackAPIClient); @@ -246,9 +198,7 @@ describe('Import Path Resolver', () => { expect(result).to.equal('/test/content'); }); - it('should call selectBranchFromDirectory when no branch name', async () => { - const branchPath = path.join('/test/content', 'branch1'); - + it('should use contentDir and set branchName from selectBranchFromDirectory when no branch name', async () => { fileExistsSyncStub.withArgs('/test/content').returns(true); // Mock module types check - all return false @@ -256,15 +206,15 @@ describe('Import Path Resolver', () => { fileExistsSyncStub.withArgs(path.join('/test/content', moduleType)).returns(false); }); - // Mock branches.json and branch selection + // Mock branches.json - single branch const branchesJsonPath = path.join('/test/content', 'branches.json'); fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(branchPath).returns(true); readFileStub.withArgs(branchesJsonPath).resolves([{ uid: 'branch1' }]); const result = await resolveImportPath(mockConfig, mockStackAPIClient); - expect(result).to.equal(branchPath); + expect(result).to.equal('/test/content'); + expect(mockConfig.branchName).to.equal('branch1'); }); it('should return contentDir when selectBranchFromDirectory returns null', async () => { @@ -329,13 +279,10 @@ describe('Import Path Resolver', () => { } as ImportConfig; }); - it('should execute complete path resolution logic', async () => { - const resolvedPath = path.join('/test/content', 'branch1'); - + it('should execute complete path resolution logic (content at root)', async () => { fileExistsSyncStub.withArgs('/test/content').returns(true); - fileExistsSyncStub.withArgs(resolvedPath).returns(true); - // Mock module types check + // Mock module types check - no module folders at root defaultConfig.modules.types.forEach((moduleType) => { fileExistsSyncStub.withArgs(path.join('/test/content', moduleType)).returns(false); }); @@ -343,14 +290,14 @@ describe('Import Path Resolver', () => { // Mock branches.json - single branch const branchesJsonPath = path.join('/test/content', 'branches.json'); fileExistsSyncStub.withArgs(branchesJsonPath).returns(true); - fileExistsSyncStub.withArgs(resolvedPath).returns(true); readFileStub.withArgs(branchesJsonPath).resolves([{ uid: 'branch1' }]); const result = await executeImportPathLogic(mockConfig, mockStackAPIClient); - expect(result).to.equal(resolvedPath); - expect(mockConfig.branchDir).to.equal(resolvedPath); - expect(mockConfig.contentDir).to.equal(resolvedPath); + expect(result).to.equal('/test/content'); + expect(mockConfig.branchDir).to.equal('/test/content'); + expect(mockConfig.contentDir).to.equal('/test/content'); + expect(mockConfig.branchName).to.equal('branch1'); }); }); }); diff --git a/packages/contentstack-variants/src/export/attributes.ts b/packages/contentstack-variants/src/export/attributes.ts index 02164db57..d5a2ec224 100644 --- a/packages/contentstack-variants/src/export/attributes.ts +++ b/packages/contentstack-variants/src/export/attributes.ts @@ -22,8 +22,7 @@ export default class ExportAttributes extends PersonalizationAdapter { this.personalizeConfig = exportConfig.modules.personalize; this.eventsConfig = exportConfig.modules.events; this.eventsFolderPath = pResolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(exportConfig.branchDir ?? exportConfig.exportDir), sanitizePath(this.personalizeConfig.dirName), sanitizePath(this.eventsConfig.dirName), ); diff --git a/packages/contentstack-variants/src/export/experiences.ts b/packages/contentstack-variants/src/export/experiences.ts index c21d7f7e2..299b64a95 100644 --- a/packages/contentstack-variants/src/export/experiences.ts +++ b/packages/contentstack-variants/src/export/experiences.ts @@ -22,8 +22,7 @@ export default class ExportExperiences extends PersonalizationAdapter this.exportConfig = exportConfig; this.personalizeConfig = exportConfig.modules.personalize; this.projectsFolderPath = pResolve( - sanitizePath(exportConfig.exportDir), - sanitizePath(exportConfig.branchName || ''), + sanitizePath(exportConfig.branchDir ?? exportConfig.exportDir), sanitizePath(this.personalizeConfig.dirName), 'projects', ); diff --git a/packages/contentstack-variants/src/export/variant-entries.ts b/packages/contentstack-variants/src/export/variant-entries.ts index 82222f980..a780ea750 100644 --- a/packages/contentstack-variants/src/export/variant-entries.ts +++ b/packages/contentstack-variants/src/export/variant-entries.ts @@ -31,8 +31,7 @@ export default class VariantEntries extends VariantAdapter { config = exportConf as unknown as ExportConfig; }); + describe('path construction', () => { + test.it('should use branchDir as base path when set (no branch segment in path)', () => { + const configWithBranchDir = { + ...config, + exportDir: '/base/export', + branchName: 'dev', + branchDir: '/base/export', + } as ExportConfig; + const instance = new Export.VariantEntries(configWithBranchDir); + expect(instance.entriesDirPath).to.not.include('dev'); + expect(instance.entriesDirPath).to.include('entries'); + }); + }); + describe('exportVariantEntry method', () => { test .stub(VariantHttpClient.prototype, 'variantEntries', async () => {})