Skip to content

Commit baecb48

Browse files
Merge pull request #196 from contentstack/fix/DX-8552
fix: minor fixes in bulk delete and move ops
2 parents 8f53ebf + 651fb20 commit baecb48

13 files changed

Lines changed: 679 additions & 124 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Command } from '@contentstack/cli-command';
2+
import { handleAndLogError } from '@contentstack/cli-utilities';
3+
4+
import { fillMissingCsAssetsFlags } from './utils';
5+
import type { CsAssetsFlags } from './interfaces';
6+
7+
/**
8+
* Thin base command for CS Assets operations.
9+
* Handles flag prompting in init() and exposes typed parsedFlags / loggerContext.
10+
* Deliberately does NOT inherit BaseBulkCommand — CS Assets operations use a different API
11+
* surface with no stack setup, queue managers, or rate limiters.
12+
*/
13+
export abstract class BaseCsAssetsCommand extends Command {
14+
protected parsedFlags!: CsAssetsFlags;
15+
protected loggerContext!: { module: string };
16+
17+
protected async init(): Promise<void> {
18+
await super.init();
19+
const { flags } = await this.parse(this.constructor as typeof BaseCsAssetsCommand);
20+
this.loggerContext = { module: this.id ?? 'cm:stacks:bulk-am-assets' };
21+
this.parsedFlags = (await fillMissingCsAssetsFlags(flags)) as CsAssetsFlags;
22+
}
23+
24+
async catch(error: Error): Promise<void> {
25+
handleAndLogError(error);
26+
}
27+
28+
abstract run(): Promise<void>;
29+
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,14 @@ export abstract class BaseBulkCommand extends Command {
145145

146146
this.parsedFlags = flags;
147147

148-
const commandName = `cm:stacks:bulk-${this.resourceType === ResourceType.ENTRY ? 'entries' : 'assets'}`;
149148
createLogContext(
150-
this.context?.info?.command || commandName,
149+
this.context?.info?.command || this.id,
151150
flags['stack-api-key'] || '',
152151
flags.alias ? 'Management Token' : 'Basic Auth'
153152
);
154153

155154
this.logger = log;
156-
this.loggerContext = { module: commandName };
155+
this.loggerContext = { module: this.id };
157156

158157
// Check for revert/retry EARLY - all config comes from log file
159158
const isRevertOrRetry = flags.revert || flags['retry-failed'];

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

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
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';
6-
import { AmAssetService } from '../../../services';
5+
import { BaseCsAssetsCommand } from '../../../base-am-command';
6+
import { CsAssetsService } from '../../../services';
77
import {
88
loadAssetUidsFromFile,
99
loadBulkDeleteItemsFromFile,
1010
LoadAssetUidsError,
1111
} from '../../../utils/asset-uids-from-file';
12-
import { AmBulkDeleteItem } from '../../../interfaces';
12+
import { generateCsAssetsJobStatusUrl } from '../../../utils/bulk-publish-url-generator';
13+
import { CsAssetsBulkDeleteItem } from '../../../interfaces';
1314

1415
const COMMAND_ID = 'cm:stacks:bulk-am-assets';
1516

16-
type RegionWithOptionalAmUrl = { csAssetsUrl?: string };
17+
type RegionWithOptionalCsAssetsUrl = { csAssetsUrl?: string };
1718

1819
/**
19-
* AM bulk delete (job) / bulk move — CS Assets API only; asset UIDs come from a JSON file `{ "uids": [...] }`.
20+
* CS Assets bulk delete (job) / bulk move; asset UIDs come from a JSON file `{ "uids": [...] }`.
2021
*/
21-
export default class BulkAmAssets extends Command {
22-
static description = messages.BULK_AM_ASSETS_DESCRIPTION;
22+
export default class BulkCsAssets extends BaseCsAssetsCommand {
23+
static description = messages.BULK_CS_ASSETS_DESCRIPTION;
2324

2425
static examples = [
2526
'<%= config.bin %> <%= command.id %> --operation delete --space-uid am123 --org-uid bltcOrg --locale en-us --asset-uids-file ./assets.json',
@@ -29,31 +30,27 @@ export default class BulkAmAssets extends Command {
2930

3031
static flags: FlagInput = {
3132
operation: flags.string({
32-
description: messages.AM_OPERATION_FLAG,
33+
description: messages.CS_ASSETS_OPERATION_FLAG,
3334
options: ['delete', 'move'],
34-
required: true,
3535
}),
3636
'space-uid': flags.string({
37-
description: messages.AM_SPACE_UID_FLAG,
38-
required: true,
37+
description: messages.CS_ASSETS_SPACE_UID_FLAG,
3938
}),
4039
'org-uid': flags.string({
41-
description: messages.AM_ORG_UID_FLAG,
42-
required: true,
40+
description: messages.CS_ASSETS_ORG_UID_FLAG,
4341
}),
4442
workspace: flags.string({
4543
default: 'main',
46-
description: messages.AM_WORKSPACE_FLAG,
44+
description: messages.CS_ASSETS_WORKSPACE_FLAG,
4745
}),
4846
'asset-uids-file': flags.string({
49-
description: messages.AM_ASSET_UIDS_FILE_FLAG,
50-
required: true,
47+
description: messages.CS_ASSETS_ASSET_UIDS_FILE_FLAG,
5148
}),
5249
locale: flags.string({
53-
description: messages.AM_LOCALE_FLAG,
50+
description: messages.CS_ASSETS_LOCALE_FLAG,
5451
}),
5552
'target-folder-uid': flags.string({
56-
description: messages.AM_TARGET_FOLDER_FLAG,
53+
description: messages.CS_ASSETS_TARGET_FOLDER_FLAG,
5754
}),
5855
yes: flags.boolean({
5956
char: 'y',
@@ -62,66 +59,77 @@ export default class BulkAmAssets extends Command {
6259
}),
6360
};
6461

65-
private readonly loggerContext = { module: COMMAND_ID };
62+
private printCsAssetsSummary(
63+
op: 'delete' | 'move',
64+
opts: { jobId?: string; count?: number; folderUid?: string; notice?: string; error?: string; spaceUid?: string }
65+
): void {
66+
if (opts.error) {
67+
log.error($t(messages.CS_ASSETS_OPERATION_FAILED, { operation: op }), this.loggerContext);
68+
log.error(opts.error, this.loggerContext);
69+
} else if (op === 'delete') {
70+
log.success($t(messages.CS_ASSETS_DELETE_SUCCESS), this.loggerContext);
71+
if (opts.jobId) log.info($t(messages.CS_ASSETS_DELETE_JOB_ID, { jobId: opts.jobId }), this.loggerContext);
72+
log.info($t(messages.CS_ASSETS_DELETE_ASYNC_NOTE), this.loggerContext);
73+
const statusUrl = generateCsAssetsJobStatusUrl(opts.spaceUid);
74+
if (statusUrl) log.info(statusUrl, this.loggerContext);
75+
} else {
76+
log.success($t(messages.CS_ASSETS_MOVE_SUCCESS), this.loggerContext);
77+
if (opts.count !== undefined && opts.folderUid) {
78+
log.info(
79+
$t(messages.CS_ASSETS_MOVE_ASSETS_COUNT, { count: opts.count, folderUid: opts.folderUid }),
80+
this.loggerContext
81+
);
82+
}
83+
const statusUrl = generateCsAssetsJobStatusUrl(opts.spaceUid);
84+
if (statusUrl) log.info(statusUrl, this.loggerContext);
85+
}
86+
if (opts.notice) log.info(opts.notice, this.loggerContext);
87+
}
6688

6789
private handleAssetUidsFileError(e: LoadAssetUidsError): void {
6890
const pathShown = e.filePath;
6991
if (e.kind === 'READ') {
7092
log.error(
71-
$t(messages.AM_ASSET_UIDS_FILE_READ_FAILED, { path: pathShown, detail: e.message }),
93+
$t(messages.CS_ASSETS_ASSET_UIDS_FILE_READ_FAILED, { path: pathShown, detail: e.message }),
7294
this.loggerContext
7395
);
7496
} else {
75-
log.error($t(messages.AM_ASSET_UIDS_FILE_INVALID, { path: pathShown, detail: e.message }), this.loggerContext);
97+
log.error(
98+
$t(messages.CS_ASSETS_ASSET_UIDS_FILE_INVALID, { path: pathShown, detail: e.message }),
99+
this.loggerContext
100+
);
76101
}
77102
process.exitCode = 1;
78103
}
79104

80105
async run(): Promise<void> {
81106
try {
82-
const { flags: f } = await this.parse(BulkAmAssets);
107+
const f = this.parsedFlags;
83108

84-
const amBaseUrl = (this.region as RegionWithOptionalAmUrl).csAssetsUrl?.trim();
85-
if (!amBaseUrl) {
86-
log.error($t(messages.AM_URL_NOT_CONFIGURED), this.loggerContext);
109+
const csAssetsBaseUrl = (this.region as RegionWithOptionalCsAssetsUrl).csAssetsUrl?.trim();
110+
if (!csAssetsBaseUrl) {
111+
log.error($t(messages.CS_ASSETS_URL_NOT_CONFIGURED), this.loggerContext);
87112
process.exitCode = 1;
88113
return;
89114
}
90115

91116
const op = f.operation;
92117
if (op !== 'delete' && op !== 'move') {
93-
log.error($t(messages.AM_INVALID_OPERATION, { operation: String(op ?? '') }), this.loggerContext);
118+
log.error($t(messages.CS_ASSETS_INVALID_OPERATION, { operation: String(op ?? '') }), this.loggerContext);
94119
process.exitCode = 1;
95120
return;
96121
}
97122

98-
const spaceUid = (f['space-uid'] ?? '').trim();
99-
if (!spaceUid) {
100-
log.error($t(messages.SPACE_UID_REQUIRED), this.loggerContext);
101-
process.exitCode = 1;
102-
return;
103-
}
123+
const spaceUid = f['space-uid'].trim();
124+
const orgUid = f['org-uid'].trim();
125+
const assetUidsPath = f['asset-uids-file'].trim();
104126

105-
const orgUid = (f['org-uid'] ?? '').trim();
106-
if (!orgUid) {
107-
log.error($t(messages.ORG_UID_REQUIRED), this.loggerContext);
108-
process.exitCode = 1;
109-
return;
110-
}
111-
112-
const assetUidsPath = (f['asset-uids-file'] ?? '').trim();
113-
if (!assetUidsPath) {
114-
log.error($t(messages.AM_ASSET_UIDS_FILE_REQUIRED), this.loggerContext);
115-
process.exitCode = 1;
116-
return;
117-
}
118-
119-
let deleteRows: AmBulkDeleteItem[];
127+
let deleteRows: CsAssetsBulkDeleteItem[];
120128

121129
if (op === 'delete') {
122130
const locale = (f.locale ?? '').trim();
123131
if (!locale) {
124-
log.error($t(messages.AM_LOCALE_REQUIRED), this.loggerContext);
132+
log.error($t(messages.CS_ASSETS_LOCALE_REQUIRED), this.loggerContext);
125133
process.exitCode = 1;
126134
return;
127135
}
@@ -138,18 +146,18 @@ export default class BulkAmAssets extends Command {
138146
}
139147

140148
createLogContext(this.context?.info?.command || COMMAND_ID, spaceUid, 'OAuth/Token');
141-
const amService = new AmAssetService(amBaseUrl, spaceUid, orgUid);
149+
const csAssetsService = new CsAssetsService(csAssetsBaseUrl, spaceUid, orgUid);
142150
const workspace = f.workspace ?? 'main';
143151

144152
if (!f.yes) {
145153
console.log(chalk.yellow(`\n${$t(messages.OPERATION_CONFIG_HEADER)}\n`));
146-
console.log(' Operation: AM bulk delete');
154+
console.log(' Operation: CS Assets bulk delete');
147155
console.log(` Space UID: ${spaceUid}`);
148156
console.log(` Organization UID: ${orgUid}`);
149157
console.log(` Workspace: ${workspace}`);
150158
console.log(` Locale: ${locale}`);
151159
console.log(` Asset UIDs file: ${assetUidsPath}`);
152-
console.log(` Total AM delete entries: ${deleteRows.length}\n`);
160+
console.log(` Total CS Assets delete entries: ${deleteRows.length}\n`);
153161

154162
const confirmed: boolean = await cliux.inquire({
155163
type: 'confirm',
@@ -163,19 +171,20 @@ export default class BulkAmAssets extends Command {
163171
}
164172
}
165173

166-
log.info($t(messages.AM_DELETING_ASSETS, { count: deleteRows.length, spaceUid }), this.loggerContext);
167-
const result = await amService.bulkDelete(spaceUid, workspace, deleteRows);
174+
log.info($t(messages.CS_ASSETS_DELETING_ASSETS, { count: deleteRows.length, spaceUid }), this.loggerContext);
175+
const result = await csAssetsService.bulkDelete(spaceUid, workspace, deleteRows);
168176
if (!result.success) {
169-
log.error(result.error ?? 'AM bulk delete failed', this.loggerContext);
177+
this.printCsAssetsSummary('delete', { error: result.error ?? 'CS Assets bulk delete failed', spaceUid });
170178
process.exitCode = 1;
171179
return;
172180
}
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-
}
181+
this.printCsAssetsSummary('delete', { jobId: result.jobId, notice: result.notice, spaceUid });
182+
return;
183+
}
184+
185+
if (f.locale) {
186+
log.error($t(messages.CS_ASSETS_LOCALE_NOT_ALLOWED_FOR_MOVE), this.loggerContext);
187+
process.exitCode = 1;
179188
return;
180189
}
181190

@@ -200,12 +209,12 @@ export default class BulkAmAssets extends Command {
200209
}
201210

202211
createLogContext(this.context?.info?.command || COMMAND_ID, spaceUid, 'OAuth/Token');
203-
const amService = new AmAssetService(amBaseUrl, spaceUid, orgUid);
212+
const csAssetsService = new CsAssetsService(csAssetsBaseUrl, spaceUid, orgUid);
204213
const workspace = f.workspace ?? 'main';
205214

206215
if (!f.yes) {
207216
console.log(chalk.yellow(`\n${$t(messages.OPERATION_CONFIG_HEADER)}\n`));
208-
console.log(' Operation: AM bulk move');
217+
console.log(' Operation: CS Assets bulk move');
209218
console.log(` Space UID: ${spaceUid}`);
210219
console.log(` Organization UID: ${orgUid}`);
211220
console.log(` Workspace: ${workspace}`);
@@ -226,19 +235,21 @@ export default class BulkAmAssets extends Command {
226235
}
227236

228237
log.info(
229-
$t(messages.AM_MOVING_ASSETS, { count: uids.length, targetFolderUid: moveFolderUid }),
238+
$t(messages.CS_ASSETS_MOVING_ASSETS, { count: uids.length, targetFolderUid: moveFolderUid }),
230239
this.loggerContext
231240
);
232-
const result = await amService.bulkMove(spaceUid, workspace, uids, moveFolderUid);
241+
const result = await csAssetsService.bulkMove(spaceUid, workspace, uids, moveFolderUid);
233242
if (!result.success) {
234-
log.error(result.error ?? 'AM bulk move failed', this.loggerContext);
243+
this.printCsAssetsSummary('move', { error: result.error ?? 'CS Assets bulk move failed', spaceUid });
235244
process.exitCode = 1;
236245
return;
237246
}
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);
247+
this.printCsAssetsSummary('move', {
248+
count: uids.length,
249+
folderUid: moveFolderUid,
250+
notice: result.notice,
251+
spaceUid,
252+
});
242253
} catch (error) {
243254
handleAndLogError(error);
244255
}

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

Lines changed: 18 additions & 5 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+
CS_ASSETS = 'cs-assets',
2324
}
2425

2526
export enum FilterType {
@@ -197,7 +198,7 @@ export interface CommandFlags {
197198
// Asset-specific flags
198199
'folder-uid'?: string;
199200

200-
/** AM bulk delete/move */
201+
/** CS Assets bulk delete/move */
201202
'space-uid'?: string;
202203
'org-uid'?: string;
203204
workspace?: string;
@@ -256,20 +257,32 @@ export interface AssetPublishData {
256257
publish_details?: PublishDetails[];
257258
}
258259

259-
/** One row for AM bulk-delete payload `{ uid, locale }[]`. */
260-
export interface AmBulkDeleteItem {
260+
/** One row for CS Assets bulk-delete payload `{ uid, locale }[]`. */
261+
export interface CsAssetsBulkDeleteItem {
261262
uid: string;
262263
locale: string;
263264
}
264265

265-
/** Normalized outcome from AM bulk delete/move calls (CLI layer). */
266-
export interface AmBulkOperationResult {
266+
/** Normalized outcome from CS Assets bulk delete/move calls (CLI layer). */
267+
export interface CsAssetsBulkOperationResult {
267268
success: boolean;
268269
notice?: string;
269270
jobId?: string;
270271
error?: string;
271272
}
272273

274+
/** Typed flags for the bulk-am-assets command. */
275+
export interface CsAssetsFlags {
276+
operation: string;
277+
'space-uid': string;
278+
'org-uid': string;
279+
workspace: string;
280+
'asset-uids-file': string;
281+
locale?: string;
282+
'target-folder-uid'?: string;
283+
yes: boolean;
284+
}
285+
273286
export interface BulkJobResult {
274287
success: number;
275288
failed: number;

0 commit comments

Comments
 (0)