Skip to content

Commit 8827f57

Browse files
author
naman-contentstack
committed
fix: minor fixes in bulk delete and move ops
1 parent 92baaf2 commit 8827f57

6 files changed

Lines changed: 150 additions & 33 deletions

File tree

packages/contentstack-bulk-operations/src/base-bulk-command.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
buildBulkModeResult,
2929
handleOperationError,
3030
fillMissingFlags,
31+
fillMissingAmFlags,
3132
getLogPaths,
3233
clearLogs,
3334
generateBulkPublishStatusUrl,
@@ -51,7 +52,7 @@ import {
5152
* Provides common functionality for bulk-entries and bulk-assets
5253
*/
5354
export abstract class BaseBulkCommand extends Command {
54-
protected abstract resourceType: ResourceType;
55+
protected resourceType?: ResourceType;
5556

5657
// Common flags for all bulk operations
5758
static baseFlags: FlagInput = {
@@ -145,6 +146,15 @@ export abstract class BaseBulkCommand extends Command {
145146

146147
this.parsedFlags = flags;
147148

149+
// AM assets uses a different API surface — prompt for AM-specific flags and skip
150+
// the publish/unpublish stack setup, queue init, and config build.
151+
if (this.resourceType === ResourceType.AM_ASSET) {
152+
this.logger = log;
153+
this.loggerContext = { module: this.id };
154+
this.parsedFlags = await fillMissingAmFlags(flags);
155+
return;
156+
}
157+
148158
const commandName = `cm:stacks:bulk-${this.resourceType === ResourceType.ENTRY ? 'entries' : 'assets'}`;
149159
createLogContext(
150160
this.context?.info?.command || commandName,
@@ -177,7 +187,7 @@ export abstract class BaseBulkCommand extends Command {
177187
await this.setupStack();
178188
await this.initializeComponents();
179189

180-
this.logger.debug($t(messages.INITIALIZING, { resourceType: this.resourceType }), this.loggerContext);
190+
this.logger.debug($t(messages.INITIALIZING, { resourceType: this.resourceType! }), this.loggerContext);
181191
}
182192

183193
/**
@@ -189,7 +199,7 @@ export abstract class BaseBulkCommand extends Command {
189199
const isRetry = !!flags['retry-failed'];
190200

191201
// Load config from log file
192-
const logFileConfig = loadConfigFromLogFile(logPath, isRetry, this.resourceType);
202+
const logFileConfig = loadConfigFromLogFile(logPath, isRetry, this.resourceType!);
193203

194204
if (!logFileConfig) {
195205
throw new Error($t(messages.NO_CONFIG_IN_LOG));
@@ -334,7 +344,7 @@ export abstract class BaseBulkCommand extends Command {
334344
batchResults: this.batchResults,
335345
logger: this.logger,
336346
retryStrategy: this.retryStrategy,
337-
resourceType: this.resourceType,
347+
resourceType: this.resourceType!,
338348
logFolderPath: this.bulkOperationConfig.bulkOperationFolder,
339349
apiKey: this.bulkOperationConfig.apiKey || this.bulkOperationConfig.stackApiKey,
340350
branch: this.bulkOperationConfig.branch,
@@ -352,7 +362,7 @@ export abstract class BaseBulkCommand extends Command {
352362
const flags = this.parsedFlags || (await this.parse(this.constructor as typeof BaseBulkCommand)).flags;
353363
const itemCount = items?.length || 0;
354364

355-
return await confirmOperationUtil(this.bulkOperationConfig, itemCount, this.resourceType, flags.yes);
365+
return await confirmOperationUtil(this.bulkOperationConfig, itemCount, this.resourceType!, flags.yes);
356366
}
357367

358368
/**
@@ -493,7 +503,7 @@ export abstract class BaseBulkCommand extends Command {
493503
const result = await handleRevertOrRetry(
494504
logPath,
495505
isRetry,
496-
this.resourceType,
506+
this.resourceType!,
497507
this.bulkOperationConfig,
498508
flags.yes,
499509
this.executeBulkOperation.bind(this),
@@ -518,14 +528,14 @@ export abstract class BaseBulkCommand extends Command {
518528
targetEnvs: flags.environments as string[],
519529
locales: flags.locales as string[],
520530
contentTypes: flags['content-types'] as string[] | undefined,
521-
resourceType: this.resourceType,
531+
resourceType: this.resourceType!,
522532
deliveryStack: this.deliveryStack!, // Required: initialized via source-alias delivery token
523533
},
524534
this.logger
525535
);
526536

527537
if (itemsToPublish.length === 0) {
528-
this.logger.warn($t(messages.NO_ITEMS_FOUND, { resourceType: this.resourceType }), this.loggerContext);
538+
this.logger.warn($t(messages.NO_ITEMS_FOUND, { resourceType: this.resourceType! }), this.loggerContext);
529539
return;
530540
}
531541

packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-am-assets.ts

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import chalk from 'chalk';
2-
import { Command } from '@contentstack/cli-command';
3-
import { flags, log, createLogContext, handleAndLogError, cliux, FlagInput } from '@contentstack/cli-utilities';
2+
import { flags, log, createLogContext, cliux, handleAndLogError, FlagInput } from '@contentstack/cli-utilities';
43

54
import messages, { $t } from '../../../messages';
5+
import { BaseBulkCommand } from '../../../base-bulk-command';
66
import { AmAssetService } from '../../../services';
77
import {
88
loadAssetUidsFromFile,
99
loadBulkDeleteItemsFromFile,
1010
LoadAssetUidsError,
1111
} from '../../../utils/asset-uids-from-file';
12-
import { AmBulkDeleteItem } from '../../../interfaces';
12+
import { AmBulkDeleteItem, ResourceType } from '../../../interfaces';
1313

1414
const COMMAND_ID = 'cm:stacks:bulk-am-assets';
1515

@@ -18,7 +18,7 @@ type RegionWithOptionalAmUrl = { csAssetsUrl?: string };
1818
/**
1919
* AM bulk delete (job) / bulk move — CS Assets API only; asset UIDs come from a JSON file `{ "uids": [...] }`.
2020
*/
21-
export default class BulkAmAssets extends Command {
21+
export default class BulkAmAssets extends BaseBulkCommand {
2222
static description = messages.BULK_AM_ASSETS_DESCRIPTION;
2323

2424
static examples = [
@@ -31,23 +31,19 @@ export default class BulkAmAssets extends Command {
3131
operation: flags.string({
3232
description: messages.AM_OPERATION_FLAG,
3333
options: ['delete', 'move'],
34-
required: true,
3534
}),
3635
'space-uid': flags.string({
3736
description: messages.AM_SPACE_UID_FLAG,
38-
required: true,
3937
}),
4038
'org-uid': flags.string({
4139
description: messages.AM_ORG_UID_FLAG,
42-
required: true,
4340
}),
4441
workspace: flags.string({
4542
default: 'main',
4643
description: messages.AM_WORKSPACE_FLAG,
4744
}),
4845
'asset-uids-file': flags.string({
4946
description: messages.AM_ASSET_UIDS_FILE_FLAG,
50-
required: true,
5147
}),
5248
locale: flags.string({
5349
description: messages.AM_LOCALE_FLAG,
@@ -62,7 +58,24 @@ export default class BulkAmAssets extends Command {
6258
}),
6359
};
6460

65-
private readonly loggerContext = { module: COMMAND_ID };
61+
protected resourceType = ResourceType.AM_ASSET;
62+
63+
private printAmSummary(op: 'delete' | 'move', opts: { jobId?: string; count?: number; folderUid?: string; notice?: string; error?: string }): void {
64+
if (opts.error) {
65+
log.error($t(messages.AM_OPERATION_FAILED, { operation: op }), this.loggerContext);
66+
log.error(opts.error, this.loggerContext);
67+
} else if (op === 'delete') {
68+
log.success($t(messages.AM_DELETE_SUCCESS), this.loggerContext);
69+
if (opts.jobId) log.info($t(messages.AM_DELETE_JOB_ID, { jobId: opts.jobId }), this.loggerContext);
70+
log.info($t(messages.AM_DELETE_ASYNC_NOTE), this.loggerContext);
71+
} else {
72+
log.success($t(messages.AM_MOVE_SUCCESS), this.loggerContext);
73+
if (opts.count !== undefined && opts.folderUid) {
74+
log.info($t(messages.AM_MOVE_ASSETS_COUNT, { count: opts.count, folderUid: opts.folderUid }), this.loggerContext);
75+
}
76+
}
77+
if (opts.notice) log.info(opts.notice, this.loggerContext);
78+
}
6679

6780
private handleAssetUidsFileError(e: LoadAssetUidsError): void {
6881
const pathShown = e.filePath;
@@ -79,7 +92,7 @@ export default class BulkAmAssets extends Command {
7992

8093
async run(): Promise<void> {
8194
try {
82-
const { flags: f } = await this.parse(BulkAmAssets);
95+
const f = this.parsedFlags;
8396

8497
const amBaseUrl = (this.region as RegionWithOptionalAmUrl).csAssetsUrl?.trim();
8598
if (!amBaseUrl) {
@@ -166,16 +179,17 @@ export default class BulkAmAssets extends Command {
166179
log.info($t(messages.AM_DELETING_ASSETS, { count: deleteRows.length, spaceUid }), this.loggerContext);
167180
const result = await amService.bulkDelete(spaceUid, workspace, deleteRows);
168181
if (!result.success) {
169-
log.error(result.error ?? 'AM bulk delete failed', this.loggerContext);
182+
this.printAmSummary('delete', { error: result.error ?? 'AM bulk delete failed' });
170183
process.exitCode = 1;
171184
return;
172185
}
173-
if (result.notice) {
174-
log.info($t(messages.AM_OPERATION_NOTICE, { notice: result.notice }), this.loggerContext);
175-
}
176-
if (result.jobId) {
177-
log.info($t(messages.AM_DELETE_SUBMITTED, { jobId: result.jobId }), this.loggerContext);
178-
}
186+
this.printAmSummary('delete', { jobId: result.jobId, notice: result.notice });
187+
return;
188+
}
189+
190+
if (f.locale) {
191+
log.error($t(messages.AM_LOCALE_NOT_ALLOWED_FOR_MOVE), this.loggerContext);
192+
process.exitCode = 1;
179193
return;
180194
}
181195

@@ -231,14 +245,11 @@ export default class BulkAmAssets extends Command {
231245
);
232246
const result = await amService.bulkMove(spaceUid, workspace, uids, moveFolderUid);
233247
if (!result.success) {
234-
log.error(result.error ?? 'AM bulk move failed', this.loggerContext);
248+
this.printAmSummary('move', { error: result.error ?? 'AM bulk move failed' });
235249
process.exitCode = 1;
236250
return;
237251
}
238-
if (result.notice) {
239-
log.info($t(messages.AM_OPERATION_NOTICE, { notice: result.notice }), this.loggerContext);
240-
}
241-
log.info($t(messages.AM_MOVE_SUBMITTED), this.loggerContext);
252+
this.printAmSummary('move', { count: uids.length, folderUid: moveFolderUid, notice: result.notice });
242253
} catch (error) {
243254
handleAndLogError(error);
244255
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum ResourceType {
2020
ENTRY = 'entry',
2121
ASSET = 'asset',
2222
TAXONOMY = 'taxonomy',
23+
AM_ASSET = 'am-asset',
2324
}
2425

2526
export enum FilterType {

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,25 @@ const amBulkAssetsMsg = {
238238
AM_WORKSPACE_FLAG: 'AM workspace query parameter (default: main)',
239239
AM_ASSET_UIDS_FILE_FLAG:
240240
'Path to UTF-8 JSON file: exactly `{ "uids": ["uid1", "uid2"] }` (non-empty string array, no trimming; large lists: see docs for NODE_OPTIONS)',
241-
AM_LOCALE_FLAG: 'Locale code for bulk delete (single locale per run)',
242-
AM_TARGET_FOLDER_FLAG: 'Destination AM folder UID (required for move)',
241+
AM_LOCALE_FLAG: 'Locale code for bulk delete only (single locale per run). Not applicable for move — move always relocates all locale variants of an asset.',
242+
AM_LOCALE_NOT_ALLOWED_FOR_MOVE: '--locale is not applicable for the move operation. Move always relocates all locale variants of an asset. Remove --locale and try again.',
243+
AM_TARGET_FOLDER_FLAG: 'Destination AM folder UID for bulk move. Use "root" to move assets to the root folder.',
243244
AM_INVALID_OPERATION: 'Invalid operation: {operation}. Must be delete or move',
244245
AM_CONFIRM_SUMMARY: 'Proceed with AM {operation} on {count} item(s)?',
246+
AM_DELETE_SUCCESS: 'AM bulk delete job submitted successfully!',
247+
AM_DELETE_JOB_ID: 'Job ID: {jobId}',
248+
AM_DELETE_ASYNC_NOTE: 'The job runs asynchronously — check the Asset Management console for status.',
249+
AM_MOVE_SUCCESS: 'AM bulk move completed successfully!',
250+
AM_MOVE_ASSETS_COUNT: '{count} asset(s) moved to folder: {folderUid}',
251+
AM_OPERATION_FAILED: 'AM {operation} failed.',
252+
253+
// Interactive prompts
254+
AM_SELECT_OPERATION: 'Select AM operation:',
255+
AM_ENTER_SPACE_UID: 'Enter AM space UID:',
256+
AM_ENTER_ORG_UID: 'Enter organization UID:',
257+
AM_ENTER_ASSET_UIDS_FILE: 'Enter path to asset UIDs JSON file (e.g. ./assets.json):',
258+
AM_ENTER_LOCALE: 'Enter locale code for bulk delete (e.g. en-us):',
259+
AM_ENTER_TARGET_FOLDER: 'Enter target folder UID for bulk move (use "root" to move to the root folder):',
245260
};
246261

247262
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
buildBulkModeResult,
3636
handleOperationError,
3737
} from './command-helpers';
38-
import { fillMissingFlags } from './interactive';
38+
import { fillMissingFlags, fillMissingAmFlags } from './interactive';
3939
import {
4040
RATE_LIMITER_CONSTANTS,
4141
RETRY_STRATEGY_CONSTANTS,
@@ -98,6 +98,7 @@ export {
9898
buildBulkModeResult,
9999
handleOperationError,
100100
fillMissingFlags,
101+
fillMissingAmFlags,
101102
fetchTaxonomyList,
102103
RATE_LIMITER_CONSTANTS,
103104
RETRY_STRATEGY_CONSTANTS,

packages/contentstack-bulk-operations/src/utils/interactive.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,82 @@ export async function fillMissingFlags(flags: any): Promise<any> {
223223

224224
return updatedFlags;
225225
}
226+
227+
/**
228+
* Fills in missing flags for the bulk-am-assets command by prompting the user.
229+
* Handles AM-specific required flags including operation-conditional ones
230+
* (locale for delete, target-folder-uid for move).
231+
*/
232+
export async function fillMissingAmFlags(flags: any): Promise<any> {
233+
const f = { ...flags };
234+
235+
const needsLocale = f.operation === 'delete' && !f.locale;
236+
const needsFolderUid = f.operation === 'move' && !f['target-folder-uid'];
237+
const needsPrompt =
238+
!f.operation || !f['space-uid'] || !f['org-uid'] || !f['asset-uids-file'] || needsLocale || needsFolderUid;
239+
240+
if (!needsPrompt) return f;
241+
242+
cliux.print(messages.INTERACTIVE_MODE_START, { color: 'cyan' });
243+
244+
if (!f.operation) {
245+
f.operation = await cliux.inquire<string>({
246+
type: 'list',
247+
name: 'operation',
248+
message: messages.AM_SELECT_OPERATION,
249+
choices: [
250+
{ name: 'Delete (AM bulk delete)', value: 'delete' },
251+
{ name: 'Move (AM bulk move)', value: 'move' },
252+
],
253+
});
254+
}
255+
256+
if (!f['space-uid']) {
257+
f['space-uid'] = await cliux.inquire<string>({
258+
type: 'input',
259+
name: 'spaceUid',
260+
message: messages.AM_ENTER_SPACE_UID,
261+
validate: (v: string) => (!v?.trim() ? messages.SPACE_UID_REQUIRED : true),
262+
});
263+
}
264+
265+
if (!f['org-uid']) {
266+
f['org-uid'] = await cliux.inquire<string>({
267+
type: 'input',
268+
name: 'orgUid',
269+
message: messages.AM_ENTER_ORG_UID,
270+
validate: (v: string) => (!v?.trim() ? messages.ORG_UID_REQUIRED : true),
271+
});
272+
}
273+
274+
if (!f['asset-uids-file']) {
275+
f['asset-uids-file'] = await cliux.inquire<string>({
276+
type: 'input',
277+
name: 'assetUidsFile',
278+
message: messages.AM_ENTER_ASSET_UIDS_FILE,
279+
validate: (v: string) => (!v?.trim() ? messages.AM_ASSET_UIDS_FILE_REQUIRED : true),
280+
});
281+
}
282+
283+
if (f.operation === 'delete' && !f.locale) {
284+
f.locale = await cliux.inquire<string>({
285+
type: 'input',
286+
name: 'locale',
287+
message: messages.AM_ENTER_LOCALE,
288+
validate: (v: string) => (!v?.trim() ? messages.AM_LOCALE_REQUIRED : true),
289+
});
290+
}
291+
292+
if (f.operation === 'move' && !f['target-folder-uid']) {
293+
f['target-folder-uid'] = await cliux.inquire<string>({
294+
type: 'input',
295+
name: 'targetFolderUid',
296+
message: messages.AM_ENTER_TARGET_FOLDER,
297+
validate: (v: string) => (!v?.trim() ? messages.TARGET_FOLDER_REQUIRED : true),
298+
});
299+
}
300+
301+
cliux.print(messages.INTERACTIVE_MODE_COMPLETE, { color: 'green' });
302+
303+
return f;
304+
}

0 commit comments

Comments
 (0)