Skip to content

Commit 9da3756

Browse files
committed
feat: Integrated CLIProgressManager and SummaryManager in extensions & global-fields
1 parent 1584e5d commit 9da3756

2 files changed

Lines changed: 381 additions & 220 deletions

File tree

packages/contentstack-import/src/import/modules/extensions.ts

Lines changed: 154 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default class ImportExtensions extends BaseClass {
2626
constructor({ importConfig, stackAPIClient }: ModuleClassParams) {
2727
super({ importConfig, stackAPIClient });
2828
this.importConfig.context.module = 'extensions';
29+
this.currentModuleName = 'Extensions';
2930
this.extensionsConfig = importConfig.modules.extensions;
3031
this.mapperDirPath = join(this.importConfig.backupDir, 'mapper', 'extensions');
3132
this.extensionsFolderPath = join(this.importConfig.backupDir, this.extensionsConfig.dirName);
@@ -45,82 +46,49 @@ export default class ImportExtensions extends BaseClass {
4546
* @returns {Promise<void>} Promise<void>
4647
*/
4748
async start(): Promise<void> {
48-
log.debug('Checking for extensions folder existence', this.importConfig.context);
49-
50-
//Step1 check folder exists or not
51-
if (fileHelper.fileExistsSync(this.extensionsFolderPath)) {
52-
log.debug(`Found extensions folder: ${this.extensionsFolderPath}`, this.importConfig.context);
53-
54-
this.extensions = fsUtil.readFile(join(this.extensionsFolderPath, 'extensions.json'), true) as Record<
55-
string,
56-
Record<string, unknown>
57-
>;
58-
59-
// Check if extensions file was read successfully
60-
if (!this.extensions) {
61-
log.info(`No extensions found in file - '${join(this.extensionsFolderPath, 'extensions.json')}'`, this.importConfig.context);
49+
try {
50+
log.debug('Starting Create process...', this.importConfig.context);
51+
const [extensionsCount] = await this.analyzeExtensions();
52+
if (extensionsCount === 0) {
53+
log.info('No extensions found to import', this.importConfig.context);
6254
return;
6355
}
64-
65-
const extensionsCount = Object.keys(this.extensions || {}).length;
66-
log.debug(`Loaded ${extensionsCount} extension items from file`, this.importConfig.context);
67-
} else {
68-
log.info(`No Extensions Found - '${this.extensionsFolderPath}'`, this.importConfig.context);
69-
return;
70-
}
7156

72-
log.debug('Creating extensions mapper directory', this.importConfig.context);
73-
await fsUtil.makeDirectory(this.mapperDirPath);
74-
75-
log.debug('Loading existing extensions UID data', this.importConfig.context);
76-
this.extUidMapper = fileHelper.fileExistsSync(this.extUidMapperPath)
77-
? (fsUtil.readFile(join(this.extUidMapperPath), true) as Record<string, unknown>) || {}
78-
: {};
57+
const progress = this.createNestedProgress(this.currentModuleName);
58+
progress.addProcess('Create', extensionsCount);
7959

80-
if (this.extUidMapper && Object.keys(this.extUidMapper || {}).length > 0) {
81-
const extUidCount = Object.keys(this.extUidMapper || {}).length;
82-
log.debug(`Loaded existing extensions UID data: ${extUidCount} items`, this.importConfig.context);
83-
} else {
84-
log.debug('No existing extensions UID data found', this.importConfig.context);
85-
}
60+
await this.prepareExtensionMapper();
61+
log.debug('Checking content types in extension scope', this.importConfig.context);
8662

87-
// Check whether the scope of an extension contains content-types in scope
88-
// Remove the scope and store the scope with uid in pending extensions
89-
log.debug('Checking content types in extension scope', this.importConfig.context);
90-
this.getContentTypesInScope();
91-
92-
log.debug('Starting extensions import', this.importConfig.context);
93-
await this.importExtensions();
94-
95-
// Update the uid of the extension
96-
log.debug('Updating extension UIDs', this.importConfig.context);
97-
this.updateUidExtension();
98-
99-
// Note: if any extensions present, then update it
100-
if (this.importConfig.replaceExisting && this.existingExtensions.length > 0) {
101-
log.debug(`Replacing ${this.existingExtensions.length} existing extensions`, this.importConfig.context);
102-
await this.replaceExtensions().catch((error: Error) => {
103-
log.debug('Error replacing extensions', this.importConfig.context);
104-
handleAndLogError(error, { ...this.importConfig.context});
105-
});
106-
}
63+
this.getContentTypesInScope();
10764

108-
log.debug('Processing extensions import results', this.importConfig.context);
109-
if (this.extSuccess?.length) {
110-
fsUtil.writeFile(this.extSuccessPath, this.extSuccess);
111-
log.debug(`Written ${this.extSuccess.length} successful extensions to file`, this.importConfig.context);
112-
}
65+
progress.startProcess('Create').updateStatus('Importing extensions...', 'Create');
66+
log.debug('Starting Create', this.importConfig.context);
67+
await this.importExtensions();
68+
progress.completeProcess('Create', true);
11369

114-
if (this.extFailed?.length) {
115-
fsUtil.writeFile(this.extFailsPath, this.extFailed);
116-
log.debug(`Written ${this.extFailed.length} failed extensions to file`, this.importConfig.context);
117-
}
70+
log.debug('Updating extension UIDs', this.importConfig.context);
71+
this.updateUidExtension();
72+
73+
if (this.importConfig.replaceExisting && this.existingExtensions.length > 0) {
74+
progress.addProcess('Update', this.existingExtensions.length);
75+
progress.startProcess('Update').updateStatus('Updating existing extensions...', 'Update');
76+
await this.replaceExtensions();
77+
progress.completeProcess('Update', true);
78+
}
11879

80+
await this.processExtensionResults();
81+
82+
this.completeProgress(true);
11983
log.success('Extensions have been imported successfully!', this.importConfig.context);
84+
} catch (error) {
85+
this.completeProgress(false, error?.message || 'Create failed');
86+
handleAndLogError(error, { ...this.importConfig.context });
87+
}
12088
}
12189

12290
async importExtensions(): Promise<any> {
123-
log.debug('Starting extensions import process', this.importConfig.context);
91+
log.debug('Starting Create process', this.importConfig.context);
12492
if (this.extensions === undefined || isEmpty(this.extensions)) {
12593
log.info('No Extensions Found', this.importConfig.context);
12694
return;
@@ -132,30 +100,48 @@ export default class ImportExtensions extends BaseClass {
132100
const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }: any) => {
133101
this.extSuccess.push(response);
134102
this.extUidMapper[uid] = response.uid;
103+
this.progressManager?.tick(true, `extension: ${title || uid}`, null, 'Create');
135104
log.success(`Extension '${title}' imported successfully`, this.importConfig.context);
136105
log.debug(`Extension import completed: ${title} (${uid})`, this.importConfig.context);
137106
fsUtil.writeFile(this.extUidMapperPath, this.extUidMapper);
138107
};
139108

140109
const onReject = ({ error, apiData }: any) => {
141-
const { title } = apiData;
110+
const { title, uid } = apiData;
142111
log.debug(`Extension '${title}' import failed`, this.importConfig.context);
143-
112+
144113
if (error?.errors?.title) {
145114
if (this.importConfig.replaceExisting) {
146115
this.existingExtensions.push(apiData);
116+
this.progressManager?.tick(
117+
true,
118+
`extension: ${title || uid} (marked for replacement)`,
119+
null,
120+
'Create',
121+
);
147122
log.debug(`Extension '${title}' marked for replacement`, this.importConfig.context);
123+
} else {
124+
this.progressManager?.tick(true, `extension: ${title || uid} (already exists)`, null, 'Create');
148125
}
149126
if (!this.importConfig.skipExisting) {
150127
log.info(`Extension '${title}' already exists`, this.importConfig.context);
151128
}
152129
} else {
153130
this.extFailed.push(apiData);
131+
this.progressManager?.tick(
132+
false,
133+
`extension: ${title || uid}`,
134+
error?.message || 'Failed to import extension',
135+
'Create',
136+
);
154137
handleAndLogError(error, { ...this.importConfig.context, title }, `Extension '${title}' failed to be import`);
155138
}
156139
};
157140

158-
log.debug(`Using concurrency limit: ${this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1}`, this.importConfig.context);
141+
log.debug(
142+
`Using concurrency limit: ${this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1}`,
143+
this.importConfig.context,
144+
);
159145
await this.makeConcurrentCall(
160146
{
161147
apiContent,
@@ -171,28 +157,35 @@ export default class ImportExtensions extends BaseClass {
171157
undefined,
172158
false,
173159
);
174-
175-
log.debug('Extensions import process completed', this.importConfig.context);
160+
161+
log.debug('Create process completed', this.importConfig.context);
176162
}
177163

178164
async replaceExtensions(): Promise<any> {
179165
log.debug(`Replacing ${this.existingExtensions.length} existing extensions`, this.importConfig.context);
180-
166+
181167
const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }: any) => {
182168
this.extSuccess.push(response);
183169
this.extUidMapper[uid] = response.uid;
184-
log.success(`Extension '${title}' replaced successfully`, this.importConfig.context);
185-
log.debug(`Extension replacement completed: ${title} (${uid})`, this.importConfig.context);
170+
this.progressManager?.tick(true, `extension: ${title || uid} (updated)`, null, 'Update');
171+
log.success(`Extension '${title}' updated successfully`, this.importConfig.context);
172+
log.debug(`Extension update completed: ${title} (${uid})`, this.importConfig.context);
186173
fsUtil.writeFile(this.extUidMapperPath, this.extUidMapper);
187174
};
188175

189176
const onReject = ({ error, apiData }: any) => {
190-
log.debug(`Extension '${apiData.title}' replacement failed`, this.importConfig.context);
177+
const { title, uid } = apiData;
191178
this.extFailed.push(apiData);
192-
handleAndLogError(error, { ...this.importConfig.context, title: apiData.title }, `Extension '${apiData.title}' failed to replace`);
179+
this.progressManager?.tick(
180+
false,
181+
`extension: ${title || uid}`,
182+
error?.message || 'Failed to update extension',
183+
'Update',
184+
);
185+
log.debug(`Extension '${title}' update failed`, this.importConfig.context);
186+
handleAndLogError(error, { ...this.importConfig.context, title }, `Extension '${title}' failed to be updated`);
193187
};
194188

195-
log.debug(`Using concurrency limit for replacement: ${this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1}`, this.importConfig.context);
196189
await this.makeConcurrentCall(
197190
{
198191
apiContent: this.existingExtensions,
@@ -207,7 +200,7 @@ export default class ImportExtensions extends BaseClass {
207200
},
208201
this.replaceExtensionHandler.bind(this),
209202
);
210-
203+
211204
log.debug('Extensions replacement process completed', this.importConfig.context);
212205
}
213206

@@ -221,10 +214,10 @@ export default class ImportExtensions extends BaseClass {
221214
isLastRequest: boolean;
222215
}) {
223216
log.debug(`Processing extension replacement: ${extension.title}`, this.importConfig.context);
224-
217+
225218
return new Promise(async (resolve, reject) => {
226219
log.debug(`Searching for existing extension: ${extension.title}`, this.importConfig.context);
227-
220+
228221
const { items: [extensionsInStack] = [] }: any = await this.stack
229222
.extension()
230223
.query({ query: { title: extension.title } })
@@ -237,18 +230,21 @@ export default class ImportExtensions extends BaseClass {
237230
});
238231
reject(true);
239232
});
240-
233+
241234
if (extensionsInStack) {
242-
log.debug(`Found existing extension in stack: ${extension.title} (${extensionsInStack.uid})`, this.importConfig.context);
243-
235+
log.debug(
236+
`Found existing extension in stack: ${extension.title} (${extensionsInStack.uid})`,
237+
this.importConfig.context,
238+
);
239+
244240
const extensionPayload = this.stack.extension(extension.uid);
245241
Object.assign(extensionPayload, extensionsInStack, cloneDeep(extension), {
246242
uid: extensionsInStack.uid,
247243
urlPath: extensionsInStack.urlPath,
248244
_version: extensionsInStack._version,
249245
stackHeaders: extensionsInStack.stackHeaders,
250246
});
251-
247+
252248
log.debug(`Updating extension: ${extension.title}`, this.importConfig.context);
253249
return extensionPayload
254250
.update()
@@ -281,46 +277,113 @@ export default class ImportExtensions extends BaseClass {
281277

282278
getContentTypesInScope() {
283279
log.debug('Processing content types in extension scope', this.importConfig.context);
284-
280+
285281
const extension = values(this.extensions);
286282
let processedExtensions = 0;
287-
283+
288284
extension.forEach((ext: ExtensionType) => {
289285
let ct: any = ext?.scope?.content_types || [];
290286
if ((ct.length === 1 && ct[0] !== '$all') || ct?.length > 1) {
291-
log.info(`Removing the content-types ${ct.join(',')} from the extension ${ext.title} ...`, this.importConfig.context);
287+
log.info(
288+
`Removing the content-types ${ct.join(',')} from the extension ${ext.title} ...`,
289+
this.importConfig.context,
290+
);
292291
log.debug(`Extension '${ext.title}' has ${ct.length} content types in scope`, this.importConfig.context);
293-
292+
294293
const { uid, scope } = ext;
295294
this.extensionObject.push({ uid, scope });
296295
delete ext.scope;
297296
this.extensions[ext.uid] = ext;
298297
processedExtensions++;
299298
}
300299
});
301-
300+
302301
log.debug(`Processed ${processedExtensions} extensions with content type scope`, this.importConfig.context);
303302
log.debug(`Total extensions with pending scope: ${this.extensionObject.length}`, this.importConfig.context);
304303
}
305304

306305
updateUidExtension() {
307306
log.debug('Updating extension UIDs in pending extensions', this.importConfig.context);
308-
307+
309308
let updatedCount = 0;
310309
for (let i in this.extensionObject) {
311310
const originalUid = this.extensionObject[i].uid as string;
312311
this.extensionObject[i].uid = this.extUidMapper[originalUid];
313312
if (this.extUidMapper[originalUid]) {
314313
updatedCount++;
315-
log.debug(`Updated extension UID: ${originalUid}${this.extUidMapper[originalUid]}`, this.importConfig.context);
314+
log.debug(
315+
`Updated extension UID: ${originalUid}${this.extUidMapper[originalUid]}`,
316+
this.importConfig.context,
317+
);
316318
}
317319
}
318-
320+
319321
log.debug(`Updated ${updatedCount} extension UIDs in pending extensions`, this.importConfig.context);
320-
322+
321323
if (this.extensionObject.length > 0) {
322324
fsUtil.writeFile(this.extPendingPath, this.extensionObject);
323325
log.debug(`Written ${this.extensionObject.length} pending extensions to file`, this.importConfig.context);
324326
}
325327
}
328+
329+
private async analyzeExtensions(): Promise<[number]> {
330+
return this.withLoadingSpinner('EXTENSIONS: Analyzing import data...', async () => {
331+
log.debug('Checking for extensions folder existence', this.importConfig.context);
332+
333+
if (!fileHelper.fileExistsSync(this.extensionsFolderPath)) {
334+
log.info(`No Extensions Found - '${this.extensionsFolderPath}'`, this.importConfig.context);
335+
return [0];
336+
}
337+
338+
log.debug(`Found extensions folder: ${this.extensionsFolderPath}`, this.importConfig.context);
339+
340+
this.extensions = fsUtil.readFile(join(this.extensionsFolderPath, 'extensions.json'), true) as Record<
341+
string,
342+
Record<string, unknown>
343+
>;
344+
345+
if (!this.extensions) {
346+
log.info(
347+
`No extensions found in file - '${join(this.extensionsFolderPath, 'extensions.json')}'`,
348+
this.importConfig.context,
349+
);
350+
return [0];
351+
}
352+
353+
const count = Object.keys(this.extensions).length;
354+
log.debug(`Loaded ${count} extension items from file`, this.importConfig.context);
355+
return [count];
356+
});
357+
}
358+
359+
private async prepareExtensionMapper(): Promise<void> {
360+
log.debug('Creating extensions mapper directory', this.importConfig.context);
361+
await fsUtil.makeDirectory(this.mapperDirPath);
362+
363+
log.debug('Loading existing extensions UID data', this.importConfig.context);
364+
this.extUidMapper = fileHelper.fileExistsSync(this.extUidMapperPath)
365+
? (fsUtil.readFile(this.extUidMapperPath, true) as Record<string, unknown>) || {}
366+
: {};
367+
368+
const count = Object.keys(this.extUidMapper || {}).length;
369+
if (count > 0) {
370+
log.debug(`Loaded existing extensions UID data: ${count} items`, this.importConfig.context);
371+
} else {
372+
log.debug('No existing extensions UID data found', this.importConfig.context);
373+
}
374+
}
375+
376+
private async processExtensionResults(): Promise<void> {
377+
log.debug('Processing Create results', this.importConfig.context);
378+
379+
if (this.extSuccess?.length) {
380+
fsUtil.writeFile(this.extSuccessPath, this.extSuccess);
381+
log.debug(`Written ${this.extSuccess.length} successful extensions to file`, this.importConfig.context);
382+
}
383+
384+
if (this.extFailed?.length) {
385+
fsUtil.writeFile(this.extFailsPath, this.extFailed);
386+
log.debug(`Written ${this.extFailed.length} failed extensions to file`, this.importConfig.context);
387+
}
388+
}
326389
}

0 commit comments

Comments
 (0)