Skip to content

Commit 603e651

Browse files
Merge pull request #2289 from contentstack/feat/DX-3767
feat: add progress manager in import setup module
2 parents f55dfdf + 80c76da commit 603e651

22 files changed

Lines changed: 1485 additions & 800 deletions

File tree

.talismanrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,9 @@ fileignoreconfig:
202202
- filename: packages/contentstack-import/test/unit/utils/logger.test.ts
203203
checksum: 794e06e657a7337c8f094d6042fb04c779683f97b860efae14e075098d2af024
204204
- filename: packages/contentstack-import-setup/src/import/modules/taxonomies.ts
205-
checksum: 49dd8e754a0d3635585a74e943ab097593f061089a7cddc22683ec6caddbb3c5
205+
checksum: c1bccc885b3f41f187f150c739b4bbd1608b01f09b0d9be0ad9214127cac071d
206+
- filename: packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts
207+
checksum: 06035980b36802260f190af6e63632efe167f5b336693163f59268f3e788fba1
208+
- filename: packages/contentstack-import-setup/src/utils/constants.ts
209+
checksum: fcfabb4c53ee822e05903db77595413842d656b55e2869bae97bb6c0e0e209c3
206210
version: '1.0'

package-lock.json

Lines changed: 697 additions & 697 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/contentstack-import-setup/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@contentstack/cli-cm-import-setup",
33
"description": "Contentstack CLI plugin to setup the mappers and configurations for the import command",
4-
"version": "2.0.0-beta",
4+
"version": "2.0.0-beta.1",
55
"author": "Contentstack",
66
"bugs": "https://github.com/contentstack/cli/issues",
77
"dependencies": {

packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
ContentstackClient,
1010
pathValidator,
1111
formatError,
12+
CLIProgressManager,
13+
configHandler,
1214
} from '@contentstack/cli-utilities';
1315

1416
import { ImportConfig } from '../../../types';
@@ -71,9 +73,27 @@ export default class ImportSetupCommand extends Command {
7173
importSetupConfig.host = this.cmaHost;
7274
importSetupConfig.region = this.region;
7375
importSetupConfig.developerHubBaseUrl = this.developerHubUrl;
76+
77+
// Prepare the context object
78+
const context = this.createImportContext(importSetupConfig.apiKey, importSetupConfig.authenticationMethod);
79+
importSetupConfig.context = { ...context };
80+
81+
if (flags.branch) {
82+
CLIProgressManager.initializeGlobalSummary(
83+
`IMPORT-SETUP-${flags.branch}`,
84+
flags.branch,
85+
`Setting up import for "${flags.branch}" branch...`,
86+
);
87+
} else {
88+
CLIProgressManager.initializeGlobalSummary(`IMPORT-SETUP`, flags.branch, 'Setting up import...');
89+
}
90+
7491
const managementAPIClient: ContentstackClient = await managementSDKClient(importSetupConfig);
7592
const importSetup = new ImportSetup(importSetupConfig, managementAPIClient);
7693
await importSetup.start();
94+
95+
CLIProgressManager.printGlobalSummary();
96+
7797
log(
7898
importSetupConfig,
7999
`Backup folder and mapper files have been successfully created for the stack using the API key ${importSetupConfig.apiKey}.`,
@@ -85,11 +105,24 @@ export default class ImportSetupCommand extends Command {
85105
'success',
86106
);
87107
} catch (error) {
108+
CLIProgressManager.printGlobalSummary();
88109
log(
89110
{ data: '' } as ImportConfig,
90111
`Failed to create the backup folder and mapper files: ${formatError(error)}`,
91112
'error',
92113
);
93114
}
94115
}
116+
117+
private createImportContext(apiKey: string, authenticationMethod?: string): any {
118+
return {
119+
command: this.context?.info?.command || 'cm:stacks:import-setup',
120+
module: '',
121+
userId: configHandler.get('userUid') || '',
122+
sessionId: this.context?.sessionId,
123+
apiKey: apiKey || '',
124+
orgId: configHandler.get('oauthOrgUid') || '',
125+
authenticationMethod: authenticationMethod || 'Basic Auth',
126+
};
127+
}
95128
}

packages/contentstack-import-setup/src/import/modules/assets.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AssetRecord, ImportConfig, ModuleClassParams } from '../../types';
55
import { isEmpty, orderBy, values } from 'lodash';
66
import { formatError, FsUtility, sanitizePath } from '@contentstack/cli-utilities';
77
import BaseImportSetup from './base-setup';
8+
import { MODULE_NAMES, MODULE_CONTEXTS, PROCESS_NAMES, PROCESS_STATUS } from '../../utils';
89

910
export default class AssetImportSetup extends BaseImportSetup {
1011
private assetsFilePath: string;
@@ -20,6 +21,7 @@ export default class AssetImportSetup extends BaseImportSetup {
2021

2122
constructor({ config, stackAPIClient, dependencies }: ModuleClassParams) {
2223
super({ config, stackAPIClient, dependencies });
24+
this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ASSETS];
2325
this.assetsFolderPath = join(sanitizePath(this.config.contentDir), 'assets');
2426
this.assetsFilePath = join(sanitizePath(this.config.contentDir), 'assets', 'assets.json');
2527
this.assetsConfig = config.modules.assets;
@@ -39,10 +41,51 @@ export default class AssetImportSetup extends BaseImportSetup {
3941
*/
4042
async start() {
4143
try {
44+
const progress = this.createNestedProgress(this.currentModuleName);
45+
46+
// Analyze to get chunk count
47+
const indexerCount = await this.withLoadingSpinner('ASSETS: Analyzing import data...', async () => {
48+
const basePath = this.assetsFolderPath;
49+
const fs = new FsUtility({ basePath, indexFileName: 'assets.json' });
50+
const indexer = fs.indexFileContent;
51+
return values(indexer).length;
52+
});
53+
54+
if (indexerCount === 0) {
55+
log(this.config, 'No assets found in the content folder.', 'info');
56+
return;
57+
}
58+
59+
// Add processes - use a large number for total assets since we don't know exact count
60+
// The progress will update as we process each asset
61+
progress.addProcess(PROCESS_NAMES.ASSETS_MAPPER_GENERATION, 1);
62+
progress.addProcess(PROCESS_NAMES.ASSETS_FETCH_AND_MAP, indexerCount * 10); // Estimate: ~10 assets per chunk
63+
64+
// Create mapper directory
65+
progress
66+
.startProcess(PROCESS_NAMES.ASSETS_MAPPER_GENERATION)
67+
.updateStatus(
68+
PROCESS_STATUS.ASSETS_MAPPER_GENERATION.GENERATING,
69+
PROCESS_NAMES.ASSETS_MAPPER_GENERATION,
70+
);
4271
fsUtil.makeDirectory(this.mapperDirPath);
72+
this.progressManager?.tick(true, 'mapper directory created', null, PROCESS_NAMES.ASSETS_MAPPER_GENERATION);
73+
progress.completeProcess(PROCESS_NAMES.ASSETS_MAPPER_GENERATION, true);
74+
75+
// Fetch and map assets
76+
progress
77+
.startProcess(PROCESS_NAMES.ASSETS_FETCH_AND_MAP)
78+
.updateStatus(
79+
PROCESS_STATUS.ASSETS_FETCH_AND_MAP.FETCHING,
80+
PROCESS_NAMES.ASSETS_FETCH_AND_MAP,
81+
);
4382
await this.fetchAndMapAssets();
83+
progress.completeProcess(PROCESS_NAMES.ASSETS_FETCH_AND_MAP, true);
84+
85+
this.completeProgress(true);
4486
log(this.config, `The required setup files for the asset have been generated successfully.`, 'success');
4587
} catch (error) {
88+
this.completeProgress(false, error?.message || 'Assets mapper generation failed');
4689
log(this.config, `Error occurred while generating the asset mapper: ${formatError(error)}.`, 'error');
4790
}
4891
}
@@ -67,17 +110,21 @@ export default class AssetImportSetup extends BaseImportSetup {
67110
if (items.length === 1) {
68111
this.assetUidMapper[uid] = items[0].uid;
69112
this.assetUrlMapper[url] = items[0].url;
113+
this.progressManager?.tick(true, `asset: ${title}`, null, PROCESS_NAMES.ASSETS_FETCH_AND_MAP);
70114
log(this.config, `Mapped asset successfully: '${title}'`, 'info');
71115
} else if (items.length > 1) {
72116
this.duplicateAssets[uid] = items.map((asset: any) => {
73117
return { uid: asset.uid, title: asset.title, url: asset.url };
74118
});
119+
this.progressManager?.tick(true, `asset: ${title} (duplicate)`, null, PROCESS_NAMES.ASSETS_FETCH_AND_MAP);
75120
log(this.config, `Multiple assets found with the title '${title}'.`, 'info');
76121
} else {
122+
this.progressManager?.tick(false, `asset: ${title}`, 'Not found in stack', PROCESS_NAMES.ASSETS_FETCH_AND_MAP);
77123
log(this.config, `Asset with title '${title}' not found in the stack!`, 'info');
78124
}
79125
};
80126
const onReject = ({ error, apiData: { title } = undefined }: any) => {
127+
this.progressManager?.tick(false, `asset: ${title}`, formatError(error), PROCESS_NAMES.ASSETS_FETCH_AND_MAP);
81128
log(this.config, `Failed to map the asset '${title}'.`, 'error');
82129
log(this.config, formatError(error), 'error');
83130
};

packages/contentstack-import-setup/src/import/modules/base-setup.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { log, fsUtil } from '../../utils';
22
import { ApiOptions, CustomPromiseHandler, EnvType, ImportConfig, ModuleClassParams } from '../../types';
33
import { chunk, entries, isEmpty, isEqual, last } from 'lodash';
4+
import { CLIProgressManager, configHandler } from '@contentstack/cli-utilities';
45

56
export default class BaseImportSetup {
67
public config: ImportConfig;
78
public stackAPIClient: ModuleClassParams['stackAPIClient'];
89
public dependencies: ModuleClassParams['dependencies'];
10+
protected progressManager: CLIProgressManager | null = null;
11+
protected currentModuleName: string = '';
912

1013
constructor({ config, stackAPIClient, dependencies }: ModuleClassParams) {
1114
this.config = config;
@@ -205,4 +208,48 @@ export default class BaseImportSetup {
205208
return Promise.resolve();
206209
}
207210
}
211+
212+
/**
213+
* Create simple progress manager
214+
*/
215+
protected createSimpleProgress(moduleName: string, total?: number): CLIProgressManager {
216+
this.currentModuleName = moduleName;
217+
const logConfig = configHandler.get('log') || {};
218+
const showConsoleLogs = logConfig.showConsoleLogs ?? false;
219+
this.progressManager = CLIProgressManager.createSimple(moduleName, total, showConsoleLogs);
220+
return this.progressManager;
221+
}
222+
223+
/**
224+
* Create nested progress manager
225+
*/
226+
protected createNestedProgress(moduleName: string): CLIProgressManager {
227+
this.currentModuleName = moduleName;
228+
const logConfig = configHandler.get('log') || {};
229+
const showConsoleLogs = logConfig.showConsoleLogs ?? false;
230+
this.progressManager = CLIProgressManager.createNested(moduleName, showConsoleLogs);
231+
return this.progressManager;
232+
}
233+
234+
/**
235+
* Complete progress manager
236+
*/
237+
protected completeProgress(success: boolean = true, error?: string): void {
238+
this.progressManager?.complete(success, error);
239+
this.progressManager = null;
240+
}
241+
242+
/**
243+
* Show a loading spinner before initializing progress
244+
*/
245+
protected async withLoadingSpinner<T>(message: string, action: () => Promise<T>): Promise<T> {
246+
const logConfig = configHandler.get('log') || {};
247+
const showConsoleLogs = logConfig.showConsoleLogs ?? false;
248+
249+
if (showConsoleLogs) {
250+
// If console logs are enabled, don't show spinner, just execute the action
251+
return await action();
252+
}
253+
return await CLIProgressManager.withLoadingSpinner(message, action);
254+
}
208255
}

packages/contentstack-import-setup/src/import/modules/content-types.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,52 @@ import { join } from 'path';
44
import { ImportConfig, ModuleClassParams } from '../../types';
55
import ExtensionImportSetup from './extensions';
66
import BaseImportSetup from './base-setup';
7+
import { MODULE_NAMES, MODULE_CONTEXTS, PROCESS_NAMES, PROCESS_STATUS } from '../../utils';
78

89
export default class ContentTypesImportSetup extends BaseImportSetup {
910
constructor(options: ModuleClassParams) {
1011
super(options);
12+
this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.CONTENT_TYPES];
1113
}
1214

1315
async start() {
1416
try {
15-
await this.setupDependencies();
17+
const progress = this.createNestedProgress(this.currentModuleName);
18+
19+
// Add processes
20+
progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_DEPENDENCY_SETUP, this.dependencies?.length || 0);
21+
progress.addProcess(PROCESS_NAMES.CONTENT_TYPES_MAPPER_GENERATION, 1);
22+
23+
// Setup dependencies
24+
if (this.dependencies && this.dependencies.length > 0) {
25+
progress
26+
.startProcess(PROCESS_NAMES.CONTENT_TYPES_DEPENDENCY_SETUP)
27+
.updateStatus(
28+
PROCESS_STATUS.CONTENT_TYPES_DEPENDENCY_SETUP.SETTING_UP,
29+
PROCESS_NAMES.CONTENT_TYPES_DEPENDENCY_SETUP,
30+
);
31+
32+
await this.setupDependencies();
33+
34+
this.progressManager?.tick(true, 'dependencies setup', null, PROCESS_NAMES.CONTENT_TYPES_DEPENDENCY_SETUP);
35+
progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_DEPENDENCY_SETUP, true);
36+
}
37+
38+
// Mapper generation
39+
progress
40+
.startProcess(PROCESS_NAMES.CONTENT_TYPES_MAPPER_GENERATION)
41+
.updateStatus(
42+
PROCESS_STATUS.CONTENT_TYPES_MAPPER_GENERATION.GENERATING,
43+
PROCESS_NAMES.CONTENT_TYPES_MAPPER_GENERATION,
44+
);
45+
46+
this.progressManager?.tick(true, 'mapper generation', null, PROCESS_NAMES.CONTENT_TYPES_MAPPER_GENERATION);
47+
progress.completeProcess(PROCESS_NAMES.CONTENT_TYPES_MAPPER_GENERATION, true);
48+
49+
this.completeProgress(true);
1650
log(this.config, `The required setup files for content types have been generated successfully.`, 'success');
1751
} catch (error) {
52+
this.completeProgress(false, error?.message || 'Content types mapper generation failed');
1853
log(this.config, `Error occurred while generating the content type mapper: ${error.message}.`, 'error');
1954
}
2055
}

packages/contentstack-import-setup/src/import/modules/entries.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
import { log } from '../../utils';
22
import { ModuleClassParams } from '../../types';
33
import BaseImportSetup from './base-setup';
4+
import { MODULE_NAMES, MODULE_CONTEXTS, PROCESS_NAMES, PROCESS_STATUS } from '../../utils';
45

56
export default class EntriesImportSetup extends BaseImportSetup {
67
constructor(options: ModuleClassParams) {
78
super(options);
9+
this.currentModuleName = MODULE_NAMES[MODULE_CONTEXTS.ENTRIES];
810
}
911

1012
async start() {
1113
try {
14+
const progress = this.createSimpleProgress(this.currentModuleName, 1);
15+
16+
progress.updateStatus('Setting up dependencies...');
1217
await this.setupDependencies();
18+
19+
this.progressManager?.tick(true, 'entries mapper setup', null);
20+
this.completeProgress(true);
21+
1322
log(this.config, `The required setup files for entries have been generated successfully.`, 'success');
1423
} catch (error) {
24+
this.completeProgress(false, error?.message || 'Entries mapper generation failed');
1525
log(this.config, `Error occurred while generating the entry mapper: ${error.message}.`, 'error');
1626
}
1727
}

0 commit comments

Comments
 (0)