Skip to content

Commit 367a07d

Browse files
authored
Merge pull request #308 from contentstack/feat/DX-9314
fix(export): AM 2.0 export under management token — detect via plan-check, skip assets + loud warn (DX-9314)
2 parents 8bcbb4b + 2d66ea1 commit 367a07d

10 files changed

Lines changed: 1082 additions & 1268 deletions

File tree

.talismanrc

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,4 @@
11
fileignoreconfig:
2-
- filename: pnpm-lock.yaml
3-
checksum: 8eb82c59ad8b81af64587af55c6788d09a34d1b651d030d1ef01dce472716f2a
4-
- filename: packages/contentstack-branches/test/unit/helpers/stub-auth.ts
5-
checksum: 8cafd5994d3ec13ba9af74c80b330bfd14721ea4e0359b456598964a6c2913ce
6-
- filename: packages/contentstack-export/src/config/index.ts
7-
checksum: 0219426b1873decef4d74bef5bfd50d1ed8f2567a2136122d869e4e53e4b4b99
8-
- filename: packages/contentstack-clone/README.md
9-
checksum: fad666bf6290c980406ddd18caec2dd89d9c7a22b422010865199659956ab523
10-
- filename: packages/contentstack-migration/README.md
11-
checksum: ceb7631888ee81711a32e2ac07762957bbf0f9c6c7f3f6bbc5d86326bd4352cc
12-
- filename: packages/contentstack-external-migrate/test/commands/migrate/import.test.ts
13-
checksum: 1d955eb34ab7e7e11cfad35cdbcb00692e39568bc690f46089fcbc0ca22a4852
14-
- filename: packages/contentstack-external-migrate/test/adapters/contentful/export.test.ts
15-
checksum: 118cc140edf3b68191d1ef770c4142f9f5aa7af87a5a1832d6b3edee53d61c83
16-
- filename: packages/contentstack-cli-tsgen/test/unit/helper.test.ts
17-
checksum: 146ff2a85a8f5ec463e51821f54c5f08143fa04209541a51017270e83b6ed46d
18-
- filename: packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts
19-
checksum: 018980aa2b919967b9ef9ab1bdf635d4867fe21593fba5890afa443f440228ff
20-
- filename: packages/contentstack-bulk-operations/src/messages/index.ts
21-
checksum: c8acefdd18f499a82d839e3cf99e34af9dd464f63f92282739411174ae7b7c37
22-
- filename: packages/contentstack-bulk-operations/src/utils/operation-flag-matrix.ts
23-
checksum: 99a3c3eb422a17f73f4c8f15088004fa4c95df3545bdf510310c1f3a20e4c2c2
24-
- filename: packages/contentstack-bulk-operations/src/base-bulk-command.ts
25-
checksum: cb1147ac666b607a093c4c827d143d0bd2de5b6e9d59973f1b7052451e82a89b
26-
- filename: packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts
27-
checksum: 8bca423db4c3815c651ad922d986a53b6271cce3ea27f5987b66ee594151165e
28-
- filename: packages/contentstack-bulk-operations/test/unit/commands/bulk-assets-init.test.ts
29-
checksum: 590b1cfe42d46d0917ac90f363b1ccd05200b180bd9c58c770ffd1f12eb18327
30-
- filename: packages/contentstack-bulk-operations/test/unit/utils/operation-flag-matrix.test.ts
31-
checksum: 35451ca4c03359b06531a8461091f4a3a45c4dea1f8853081fb53e4ce28c1cc3
32-
- filename: packages/contentstack-bulk-operations/test/unit/base-bulk-command.test.ts
33-
checksum: ca699a9d73757d44ded4d9d27beb4f460f911a7c88a14551a9402a3ac0c69873
34-
- filename: packages/contentstack-external-migrate/src/lib/create-stack.ts
35-
checksum: 68a9510db6f2746ac5006c091d276c1ba619a9e15c76a3edae3967e4f9c2dd4e
36-
- filename: packages/contentstack-external-migrate/test/lib/create-stack.test.ts
37-
checksum: 2dcbc359ee275e59e0536f3c325416eac8c43eb341b04da0d3757f5b5e556d9c
38-
- filename: CHANGELOG.md
39-
checksum: 88c7e1dee308fa4ae25e7815ead0842b1aac7ed669b02859cc8b25cba879595d
40-
- filename: packages/contentstack-bulk-operations/test/unit/services/taxonomy-service.test.ts
41-
checksum: abc5ac707341760cf59d5b8b1c4e13cf2c79955e2735c33e2db3ec6bc48eddb6
42-
- filename: packages/contentstack-import/src/import/modules/assets.ts
43-
checksum: cda61a9c90bb39f27c09951b8a2623851296aefd0d3220d066032287a712d899
44-
- filename: packages/contentstack-asset-management/test/unit/utils/cs-assets-api-adapter.test.ts
45-
checksum: 63c6bff4d51842d8fa3cce88545259d0a2c3cfe71df95d303d993f692cee883b
46-
- filename: packages/contentstack-asset-management/src/utils/cs-assets-api-adapter.ts
47-
checksum: bc4a53f96be6a10786e00133245c7bdc43c965c8a98b753e3879e1110cf9c601
48-
- filename: packages/contentstack-bulk-operations/test/unit/commands/bulk-assets-cs-assets.test.ts
49-
checksum: 1b6b5b0302d3efd9508e2e5d4fe3783da97a110735ca3106ce829cfe93874150
2+
- filename: pnpm-lock.yaml
3+
checksum: a90a8f0dbfa98da53ecb9e63f021ae8a1ac44c03cfe5dccd52a539c948d1625a
504
version: '1.0'

packages/contentstack-bulk-operations/src/messages/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ const csAssetsBulkMsg = {
255255
CS_ASSETS_MOVE_ASSETS_COUNT: '{count} asset(s) moved to folder: {folderUid}',
256256
CS_ASSETS_OPERATION_FAILED: 'CS Assets {operation} failed.',
257257
CS_ASSETS_BATCH_SUMMARY: 'Dispatched in {batchesTotal} batch(es) of up to 100 — {batchesSucceeded} succeeded.',
258-
CS_ASSETS_PARTIAL_FAILURE: 'CS Assets {operation} partially failed: {batchesFailed} of {batchesTotal} batch(es) failed.',
258+
CS_ASSETS_PARTIAL_FAILURE:
259+
'CS Assets {operation} partially failed: {batchesFailed} of {batchesTotal} batch(es) failed.',
259260
CS_ASSETS_FAILED_BATCH: 'Batch {batchIndex} ({count} item(s)) failed: {error}',
260261
CS_ASSETS_FAILED_UIDS_WRITTEN:
261262
'Uids whose {operation} request did not confirm success written to: {path} (these requests failed to return success — the server may or may not have applied them).',

packages/contentstack-export/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@
9090
"csdxConfig": {
9191
"shortCommandName": {
9292
"cm:stacks:export": "EXPRT"
93-
}
93+
},
94+
"planProtectedFeatures": [
95+
"amAssets"
96+
]
9497
},
9598
"repository": {
9699
"type": "git",

packages/contentstack-export/src/commands/cm/stacks/export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default class ExportCommand extends Command {
111111
let exportDir: string = pathValidator('logs');
112112
try {
113113
const { flags } = await this.parse(ExportCommand);
114-
const exportConfig = await setupExportConfig(flags);
114+
const exportConfig = await setupExportConfig(flags, this.context);
115115
// Prepare the context object
116116
const context = this.createExportContext(exportConfig.apiKey, exportConfig.authenticationMethod);
117117
exportConfig.context = { ...context };

packages/contentstack-export/src/export/modules/assets.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import {
2020
handleAndLogError,
2121
messageHandler,
2222
CLIProgressManager,
23+
FEATURE,
2324
} from '@contentstack/cli-utilities';
2425
import { PATH_CONSTANTS } from '../../constants';
2526

2627
import config from '../../config';
27-
import { ModuleClassParams } from '../../types';
28+
import { ModuleClassParams, GlobalSummary } from '../../types';
2829
import BaseClass, { CustomPromiseHandler, CustomPromiseHandlerInput } from './base-class';
2930
import { ExportSpaces, type AssetExportCounts } from '@contentstack/cli-asset-management';
3031
import {
@@ -57,31 +58,31 @@ export default class ExportAssets extends BaseClass {
5758
};
5859
}
5960

60-
/**
61-
* Bug 3 — push real CS Assets entity counts into the final EXPORT summary: override the ASSETS
62-
* module to count downloaded binaries only, and add dedicated ASSET TYPES / FIELDS / FOLDERS rows.
63-
* Drives the global summary directly (no cli-utilities change); the live multibar is unaffected.
64-
* The ASSETS strategy is set to Default so applyStrategyCorrections does not overwrite these.
65-
*/
66-
private applyAssetSummaryCounts(counts: AssetExportCounts): void {
67-
type SummaryModule = { successCount: number; failureCount: number };
68-
type GlobalSummary = {
69-
getModules(): Map<string, SummaryModule>;
70-
registerModule(name: string, totalItems?: number): void;
71-
startModule(name: string): void;
72-
completeModule(name: string, success?: boolean): void;
73-
};
74-
// globalSummary is runtime-accessible but typed private; reach it via a structural cast.
61+
// globalSummary is runtime-accessible but typed private; feature-detect the shape and return null
62+
// so callers degrade instead of throwing if a future cli-utilities version changes it.
63+
private getGlobalSummary(): GlobalSummary | null {
7564
const gs = (CLIProgressManager as unknown as { globalSummary?: GlobalSummary | null }).globalSummary;
76-
// We reach into cli-utilities' summary internals, so feature-detect the shape and DEGRADE
77-
// (skip the count overrides) instead of throwing if a future version changes it.
7865
if (
7966
!gs ||
8067
typeof gs.getModules !== 'function' ||
8168
typeof gs.registerModule !== 'function' ||
8269
typeof gs.startModule !== 'function' ||
8370
typeof gs.completeModule !== 'function'
8471
) {
72+
return null;
73+
}
74+
return gs;
75+
}
76+
77+
/**
78+
* Bug 3 — push real CS Assets entity counts into the final EXPORT summary: override the ASSETS
79+
* module to count downloaded binaries only, and add dedicated ASSET TYPES / FIELDS / FOLDERS rows.
80+
* Drives the global summary directly (no cli-utilities change); the live multibar is unaffected.
81+
* The ASSETS strategy is set to Default so applyStrategyCorrections does not overwrite these.
82+
*/
83+
private applyAssetSummaryCounts(counts: AssetExportCounts): void {
84+
const gs = this.getGlobalSummary();
85+
if (!gs) {
8586
log.debug('Global summary shape unavailable; skipping CS Assets summary count overrides', this.exportConfig.context);
8687
return;
8788
}
@@ -116,7 +117,42 @@ export default class ExportAssets extends BaseClass {
116117
}
117118
}
118119

120+
// Records the skip in the final summary via the failure channel, which is the only per-module
121+
// slot carrying a message. createNestedProgress is intentionally not used: it would render an
122+
// empty "ASSETS:" live section.
123+
private markAssetsSkippedInSummary(reason: string): void {
124+
const gs = this.getGlobalSummary();
125+
if (!gs) {
126+
log.debug('Global summary shape unavailable; skipping ASSETS skip row', this.exportConfig.context);
127+
return;
128+
}
129+
130+
try {
131+
const name = this.currentModuleName.toUpperCase();
132+
gs.registerModule(name);
133+
gs.startModule(name);
134+
const module = gs.getModules().get(name);
135+
if (module) {
136+
module.failures.push({ item: reason, error: reason });
137+
module.status = 'failed';
138+
module.endTime = Date.now();
139+
}
140+
} catch (e) {
141+
log.debug(`Failed to mark ASSETS as skipped in summary: ${e}`, this.exportConfig.context);
142+
}
143+
}
144+
119145
async start(): Promise<void> {
146+
const csAssetsInPlan = this.exportConfig.planStatus?.[FEATURE.ASSET_MANAGEMENT]?.is_part_of_plan;
147+
if (csAssetsInPlan && this.exportConfig.management_token) {
148+
const warning =
149+
'Skipping Contentstack Assets export: management token authentication is not supported by the Assets APIs. ' +
150+
'Entry-to-asset references will NOT resolve in the exported content. ' +
151+
'Re-run the export with a logged-in session (auth token or OAuth) to export Contentstack Assets.';
152+
this.markAssetsSkippedInSummary(warning);
153+
return;
154+
}
155+
120156
const linkedWorkspaces = this.exportConfig.linkedWorkspaces ?? [];
121157

122158
if (linkedWorkspaces.length > 0) {

packages/contentstack-export/src/types/export-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FeatureStatus } from '@contentstack/cli-utilities';
12
import { Context, Modules, Region } from '.';
23
import DefaultConfig from './default-config';
34

@@ -36,6 +37,7 @@ export default interface ExportConfig extends DefaultConfig {
3637
skipDependencies?: boolean;
3738
authenticationMethod?: string;
3839
linkedWorkspaces?: Array<{ uid: string; space_uid: string; is_default: boolean }>;
40+
planStatus?: Record<string, FeatureStatus>;
3941
}
4042

4143
type branch = {

packages/contentstack-export/src/types/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,21 @@ export interface Context {
180180
authenticationMethod?: string;
181181
}
182182

183+
export interface SummaryModule {
184+
status: string;
185+
successCount: number;
186+
failureCount: number;
187+
failures: Array<{ item: string; error: string }>;
188+
endTime?: number;
189+
}
190+
191+
export interface GlobalSummary {
192+
getModules(): Map<string, SummaryModule>;
193+
registerModule(name: string, totalItems?: number): void;
194+
startModule(name: string): void;
195+
completeModule(name: string, success?: boolean): void;
196+
}
197+
183198
export { default as DefaultConfig } from './default-config';
184199
export { default as ExportConfig } from './export-config';
185200
export * from './marketplace-app';

packages/contentstack-export/src/utils/export-config-handler.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import merge from 'merge';
22
import * as path from 'path';
3-
import { configHandler, isAuthenticated, cliux, sanitizePath, log } from '@contentstack/cli-utilities';
3+
import {
4+
configHandler,
5+
isAuthenticated,
6+
cliux,
7+
sanitizePath,
8+
log,
9+
isFeatureEnabled,
10+
FeatureCtx,
11+
} from '@contentstack/cli-utilities';
412
import defaultConfig from '../config';
513
import { readFile, isDirectoryNonEmpty } from './file-helper';
614
import { askExportDir, askAPIKey } from './interactive';
715
import login from './basic-login';
816
import { filter, includes } from 'lodash';
917
import { ExportConfig } from '../types';
1018

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

163+
// Deferred plan check — credentials now available after setupExportConfig
164+
const deferredFeatures: string[] = context?.planCheckRequired ?? [];
165+
if (deferredFeatures.length > 0) {
166+
const planCtx: FeatureCtx = {
167+
apiKey: config.apiKey,
168+
managementToken: config.management_token,
169+
authToken: config.auth_token,
170+
};
171+
for (const featureUid of deferredFeatures) {
172+
try {
173+
const status = await isFeatureEnabled(featureUid, planCtx);
174+
if (context) {
175+
context.planStatus[featureUid] = status;
176+
}
177+
178+
log.debug(`[export] Deferred plan status fetched for "${featureUid}".`);
179+
} catch (error) {
180+
log.warn(`[export] Could not fetch deferred plan status for "${featureUid}": ${(error as Error).message}`);
181+
}
182+
}
183+
}
184+
185+
if (context?.planStatus) {
186+
config.planStatus = context.planStatus;
187+
}
188+
155189
return config;
156190
};
157191

packages/contentstack-import/test/unit/import/modules/entries.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,8 +2196,10 @@ describe('EntriesImport', () => {
21962196
await entriesImport['publishEntries']({ cTUid: 'simple_ct', locale: 'en-us' });
21972197

21982198
expect(makeConcurrentCallStub.called).to.be.true;
2199-
// Should pass 1 entry with all publish details (serializePublishEntries aggregates them into one API call)
2200-
expect(makeConcurrentCallStub.getCall(0).args[0].apiContent).to.have.lengthOf(1);
2199+
// One entry per publish_details item, each carrying that item's locale
2200+
const apiContent = makeConcurrentCallStub.getCall(0).args[0].apiContent;
2201+
expect(apiContent).to.have.lengthOf(3);
2202+
expect(apiContent.map((e: any) => e.locale)).to.deep.equal(['en-us', 'en-us', 'fr-fr']);
22012203
});
22022204

22032205
it('should handle entries without publish details', async () => {

0 commit comments

Comments
 (0)