Skip to content
Merged
46 changes: 2 additions & 44 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,46 +1,4 @@
fileignoreconfig:
- filename: pnpm-lock.yaml
checksum: 8eb82c59ad8b81af64587af55c6788d09a34d1b651d030d1ef01dce472716f2a
- filename: packages/contentstack-branches/test/unit/helpers/stub-auth.ts
checksum: 8cafd5994d3ec13ba9af74c80b330bfd14721ea4e0359b456598964a6c2913ce
- filename: packages/contentstack-export/src/config/index.ts
checksum: 0219426b1873decef4d74bef5bfd50d1ed8f2567a2136122d869e4e53e4b4b99
- filename: packages/contentstack-clone/README.md
checksum: fad666bf6290c980406ddd18caec2dd89d9c7a22b422010865199659956ab523
- filename: packages/contentstack-migration/README.md
checksum: ceb7631888ee81711a32e2ac07762957bbf0f9c6c7f3f6bbc5d86326bd4352cc
- filename: packages/contentstack-external-migrate/test/commands/migrate/import.test.ts
checksum: 1d955eb34ab7e7e11cfad35cdbcb00692e39568bc690f46089fcbc0ca22a4852
- filename: packages/contentstack-external-migrate/test/adapters/contentful/export.test.ts
checksum: 118cc140edf3b68191d1ef770c4142f9f5aa7af87a5a1832d6b3edee53d61c83
- filename: packages/contentstack-cli-tsgen/test/unit/helper.test.ts
checksum: 146ff2a85a8f5ec463e51821f54c5f08143fa04209541a51017270e83b6ed46d
- filename: packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts
checksum: 018980aa2b919967b9ef9ab1bdf635d4867fe21593fba5890afa443f440228ff
- filename: packages/contentstack-bulk-operations/src/messages/index.ts
checksum: ba38daefd561d9acd018b6ebcb65847eceab6d375b28988d7b5624dcea9ccfe0
- filename: packages/contentstack-bulk-operations/src/utils/operation-flag-matrix.ts
checksum: 99a3c3eb422a17f73f4c8f15088004fa4c95df3545bdf510310c1f3a20e4c2c2
- filename: packages/contentstack-bulk-operations/src/base-bulk-command.ts
checksum: cb1147ac666b607a093c4c827d143d0bd2de5b6e9d59973f1b7052451e82a89b
- filename: packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts
checksum: 8bca423db4c3815c651ad922d986a53b6271cce3ea27f5987b66ee594151165e
- filename: packages/contentstack-bulk-operations/test/unit/commands/bulk-assets-init.test.ts
checksum: 590b1cfe42d46d0917ac90f363b1ccd05200b180bd9c58c770ffd1f12eb18327
- filename: packages/contentstack-bulk-operations/test/unit/utils/operation-flag-matrix.test.ts
checksum: 35451ca4c03359b06531a8461091f4a3a45c4dea1f8853081fb53e4ce28c1cc3
- filename: packages/contentstack-bulk-operations/test/unit/base-bulk-command.test.ts
checksum: ca699a9d73757d44ded4d9d27beb4f460f911a7c88a14551a9402a3ac0c69873
- filename: packages/contentstack-external-migrate/src/lib/create-stack.ts
checksum: 68a9510db6f2746ac5006c091d276c1ba619a9e15c76a3edae3967e4f9c2dd4e
- filename: packages/contentstack-external-migrate/test/lib/create-stack.test.ts
checksum: 2dcbc359ee275e59e0536f3c325416eac8c43eb341b04da0d3757f5b5e556d9c
- filename: CHANGELOG.md
checksum: 88c7e1dee308fa4ae25e7815ead0842b1aac7ed669b02859cc8b25cba879595d
- filename: packages/contentstack-bulk-operations/test/unit/services/taxonomy-service.test.ts
checksum: abc5ac707341760cf59d5b8b1c4e13cf2c79955e2735c33e2db3ec6bc48eddb6
- filename: packages/contentstack-import/src/import/modules/assets.ts
checksum: cda61a9c90bb39f27c09951b8a2623851296aefd0d3220d066032287a712d899
- filename: packages/contentstack-asset-management/test/unit/utils/cs-assets-api-adapter.test.ts
checksum: 63c6bff4d51842d8fa3cce88545259d0a2c3cfe71df95d303d993f692cee883b
- filename: packages/contentstack-export/src/utils/export-config-handler.ts
checksum: ab7fd2fb63420e7a66856abace57965c1e9aa1033fc18dc8b194cf1d306f0bab
version: '1.0'
5 changes: 4 additions & 1 deletion packages/contentstack-export/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@
"csdxConfig": {
"shortCommandName": {
"cm:stacks:export": "EXPRT"
}
},
"planProtectedFeatures": [
"amAssets"
]
},
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class ExportCommand extends Command {
let exportDir: string = pathValidator('logs');
try {
const { flags } = await this.parse(ExportCommand);
const exportConfig = await setupExportConfig(flags);
const exportConfig = await setupExportConfig(flags, this.context);
// Prepare the context object
const context = this.createExportContext(exportConfig.apiKey, exportConfig.authenticationMethod);
exportConfig.context = { ...context };
Expand Down
74 changes: 56 additions & 18 deletions packages/contentstack-export/src/export/modules/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import {
handleAndLogError,
messageHandler,
CLIProgressManager,
FEATURE,
} from '@contentstack/cli-utilities';
import { PATH_CONSTANTS } from '../../constants';

import config from '../../config';
import { ModuleClassParams } from '../../types';
import { ModuleClassParams, GlobalSummary } from '../../types';
import BaseClass, { CustomPromiseHandler, CustomPromiseHandlerInput } from './base-class';
import { ExportSpaces, type AssetExportCounts } from '@contentstack/cli-asset-management';
import {
Expand Down Expand Up @@ -57,31 +58,31 @@ export default class ExportAssets extends BaseClass {
};
}

/**
* Bug 3 — push real CS Assets entity counts into the final EXPORT summary: override the ASSETS
* module to count downloaded binaries only, and add dedicated ASSET TYPES / FIELDS / FOLDERS rows.
* Drives the global summary directly (no cli-utilities change); the live multibar is unaffected.
* The ASSETS strategy is set to Default so applyStrategyCorrections does not overwrite these.
*/
private applyAssetSummaryCounts(counts: AssetExportCounts): void {
type SummaryModule = { successCount: number; failureCount: number };
type GlobalSummary = {
getModules(): Map<string, SummaryModule>;
registerModule(name: string, totalItems?: number): void;
startModule(name: string): void;
completeModule(name: string, success?: boolean): void;
};
// globalSummary is runtime-accessible but typed private; reach it via a structural cast.
// globalSummary is runtime-accessible but typed private; feature-detect the shape and return null
// so callers degrade instead of throwing if a future cli-utilities version changes it.
private getGlobalSummary(): GlobalSummary | null {
const gs = (CLIProgressManager as unknown as { globalSummary?: GlobalSummary | null }).globalSummary;
// We reach into cli-utilities' summary internals, so feature-detect the shape and DEGRADE
// (skip the count overrides) instead of throwing if a future version changes it.
if (
!gs ||
typeof gs.getModules !== 'function' ||
typeof gs.registerModule !== 'function' ||
typeof gs.startModule !== 'function' ||
typeof gs.completeModule !== 'function'
) {
return null;
}
return gs;
}

/**
* Bug 3 — push real CS Assets entity counts into the final EXPORT summary: override the ASSETS
* module to count downloaded binaries only, and add dedicated ASSET TYPES / FIELDS / FOLDERS rows.
* Drives the global summary directly (no cli-utilities change); the live multibar is unaffected.
* The ASSETS strategy is set to Default so applyStrategyCorrections does not overwrite these.
*/
private applyAssetSummaryCounts(counts: AssetExportCounts): void {
const gs = this.getGlobalSummary();
if (!gs) {
log.debug('Global summary shape unavailable; skipping CS Assets summary count overrides', this.exportConfig.context);
return;
}
Expand Down Expand Up @@ -116,7 +117,44 @@ export default class ExportAssets extends BaseClass {
}
}

// Records the skip in the final summary via the failure channel, which is the only per-module
// slot carrying a message. createNestedProgress is intentionally not used: it would render an
// empty "ASSETS:" live section.
private markAssetsSkippedInSummary(reason: string): void {
const gs = this.getGlobalSummary();
if (!gs) {
log.debug('Global summary shape unavailable; skipping ASSETS skip row', this.exportConfig.context);
return;
}

try {
const name = this.currentModuleName.toUpperCase();
gs.registerModule(name);
gs.startModule(name);
const module = gs.getModules().get(name);
if (module) {
module.failures.push({ item: reason, error: reason });
module.status = 'failed';
module.endTime = Date.now();
}
} catch (e) {
log.debug(`Failed to mark ASSETS as skipped in summary: ${e}`, this.exportConfig.context);
}
}

async start(): Promise<void> {
const csAssetsInPlan = this.exportConfig.planStatus?.[FEATURE.ASSET_MANAGEMENT]?.is_part_of_plan;
if (csAssetsInPlan && this.exportConfig.management_token) {
const warning =
'Skipping Contentstack Assets export: management token authentication is not supported by the Assets APIs. ' +
'Entry-to-asset references will NOT resolve in the exported content. ' +
'Re-run the export with a logged-in session (auth token or OAuth) to export Contentstack Assets.';
cliux.print(`\nWARNING!!! ${warning}`, { color: 'yellow' });
log.error(warning, this.exportConfig.context);
this.markAssetsSkippedInSummary(warning);
return;
}

const linkedWorkspaces = this.exportConfig.linkedWorkspaces ?? [];

if (linkedWorkspaces.length > 0) {
Expand Down
2 changes: 2 additions & 0 deletions packages/contentstack-export/src/types/export-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FeatureStatus } from '@contentstack/cli-utilities';
import { Context, Modules, Region } from '.';
import DefaultConfig from './default-config';

Expand Down Expand Up @@ -36,6 +37,7 @@ export default interface ExportConfig extends DefaultConfig {
skipDependencies?: boolean;
authenticationMethod?: string;
linkedWorkspaces?: Array<{ uid: string; space_uid: string; is_default: boolean }>;
planStatus?: Record<string, FeatureStatus>;
}

type branch = {
Expand Down
15 changes: 15 additions & 0 deletions packages/contentstack-export/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ export interface Context {
authenticationMethod?: string;
}

export interface SummaryModule {
status: string;
successCount: number;
failureCount: number;
failures: Array<{ item: string; error: string }>;
endTime?: number;
}

export interface GlobalSummary {
getModules(): Map<string, SummaryModule>;
registerModule(name: string, totalItems?: number): void;
startModule(name: string): void;
completeModule(name: string, success?: boolean): void;
}

export { default as DefaultConfig } from './default-config';
export { default as ExportConfig } from './export-config';
export * from './marketplace-app';
38 changes: 36 additions & 2 deletions packages/contentstack-export/src/utils/export-config-handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
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,
isFeatureEnabled,
FeatureCtx,
} from '@contentstack/cli-utilities';
import defaultConfig from '../config';
import { readFile, isDirectoryNonEmpty } from './file-helper';
import { askExportDir, askAPIKey } from './interactive';
import login from './basic-login';
import { filter, includes } from 'lodash';
import { ExportConfig } from '../types';

const setupConfig = async (exportCmdFlags: any): Promise<ExportConfig> => {
const setupConfig = async (exportCmdFlags: any, context: any): Promise<ExportConfig> => {
// Set progress supported module FIRST, before any log calls
// This ensures the logger respects the showConsoleLogs setting correctly
configHandler.set('log.progressSupportedModule', 'export');
Expand Down Expand Up @@ -152,6 +160,32 @@ const setupConfig = async (exportCmdFlags: any): Promise<ExportConfig> => {
config.authenticationMethod = authenticationMethod;
log.debug('Export configuration setup completed.', { ...config });

// Deferred plan check — credentials now available after setupExportConfig
const deferredFeatures: string[] = context?.planCheckRequired ?? [];
if (deferredFeatures.length > 0) {
const planCtx: FeatureCtx = {
apiKey: config.apiKey,
managementToken: config.management_token,
authToken: config.auth_token,
};
for (const featureUid of deferredFeatures) {
try {
const status = await isFeatureEnabled(featureUid, planCtx);
if (context) {
context.planStatus[featureUid] = status;
}

log.debug(`[export] Deferred plan status fetched for "${featureUid}".`);
} catch (error) {
log.warn(`[export] Could not fetch deferred plan status for "${featureUid}": ${(error as Error).message}`);
}
}
}

if (context?.planStatus) {
config.planStatus = context.planStatus;
}

return config;
};

Expand Down
Loading