@contentstack/cli-cm-import 1.9.1 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  It is Contentstack’s CLI plugin to import content in the stack. To learn how to export and import content in Contentstack, refer to the [Migration guide](https://www.contentstack.com/docs/developers/cli/migration/).
4
4
 
5
- [![License](https://img.shields.io/npm/l/@contentstack/cli)](https://github.com/contentstack/cli/blob/main/LICENSE)
5
+ [![License](https://img.shields.io/npm/l/@contentstack/cli)](https://github.com/contentstack/cli/blob/main/LICENSE)it -m
6
6
 
7
7
  <!-- toc -->
8
8
  * [Usage](#usage)
@@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import
47
47
  $ csdx COMMAND
48
48
  running command...
49
49
  $ csdx (--version)
50
- @contentstack/cli-cm-import/1.9.1 linux-x64 node-v18.18.0
50
+ @contentstack/cli-cm-import/1.10.1 linux-x64 node-v18.18.2
51
51
  $ csdx --help [COMMAND]
52
52
  USAGE
53
53
  $ csdx COMMAND
@@ -81,6 +81,8 @@ FLAGS
81
81
  -y, --yes [optional] Override marketplace prompts
82
82
  --import-webhook-status=<option> [default: disable] [optional] Webhook state
83
83
  <options: disable|current>
84
+ --replace-existing Replaces the existing module in the target stack.
85
+ --skip-existing Skips the module exists warning messages.
84
86
 
85
87
  DESCRIPTION
86
88
  Import content from a stack
@@ -126,6 +128,8 @@ FLAGS
126
128
  -y, --yes [optional] Override marketplace prompts
127
129
  --import-webhook-status=<option> [default: disable] [optional] Webhook state
128
130
  <options: disable|current>
131
+ --replace-existing Replaces the existing module in the target stack.
132
+ --skip-existing Skips the module exists warning messages.
129
133
 
130
134
  DESCRIPTION
131
135
  Import content from a stack
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
4
4
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
5
5
  const cli_command_1 = require("@contentstack/cli-command");
6
6
  const cli_utilities_1 = require("@contentstack/cli-utilities");
7
+ const log_1 = require("../../../utils/log");
7
8
  const import_1 = require("../../../import");
8
9
  const utils_1 = require("../../../utils");
9
10
  class ImportCommand extends cli_command_1.Command {
@@ -25,6 +26,7 @@ class ImportCommand extends cli_command_1.Command {
25
26
  (0, utils_1.log)(importConfig, `The log has been stored at '${node_path_1.default.join(importConfig.backupDir, 'logs', 'import')}'`, 'success');
26
27
  }
27
28
  catch (error) {
29
+ (0, log_1.trace)(error, 'error', true);
28
30
  (0, utils_1.log)({ data: backupDir }, `Failed to import stack content - ${(0, utils_1.formatError)(error)}`, 'error');
29
31
  (0, utils_1.log)({ data: backupDir }, `The log has been stored at ${{ data: backupDir } ? node_path_1.default.join(backupDir || __dirname, 'logs', 'import') : node_path_1.default.join(__dirname, 'logs')}`, 'info');
30
32
  }
@@ -82,6 +84,7 @@ ImportCommand.flags = {
82
84
  parse: (0, cli_utilities_1.printFlagDeprecation)(['-A', '--auth-token']),
83
85
  }),
84
86
  module: cli_utilities_1.flags.string({
87
+ required: false,
85
88
  char: 'm',
86
89
  description: '[optional] specific module name',
87
90
  parse: (0, cli_utilities_1.printFlagDeprecation)(['-m'], ['--module']),
@@ -107,6 +110,16 @@ ImportCommand.flags = {
107
110
  required: false,
108
111
  description: '[optional] Override marketplace prompts',
109
112
  }),
113
+ 'replace-existing': cli_utilities_1.flags.boolean({
114
+ required: false,
115
+ description: 'Replaces the existing module in the target stack.',
116
+ dependsOn: ['module'],
117
+ }),
118
+ 'skip-existing': cli_utilities_1.flags.boolean({
119
+ required: false,
120
+ default: false,
121
+ description: 'Skips the module exists warning messages.',
122
+ }),
110
123
  };
111
124
  ImportCommand.aliases = ['cm:import'];
112
125
  ImportCommand.usage = 'cm:stacks:import [-c <value>] [-k <value>] [-d <value>] [-a <value>] [--module <value>] [--backup-dir <value>] [--branch <value>] [--import-webhook-status disable|current]';
@@ -367,6 +367,7 @@ const config = {
367
367
  stacks: '/stacks/',
368
368
  labels: '/labels/',
369
369
  },
370
+ overwriteSupportedModules: ['extensions', 'global-fields', 'content-types'],
370
371
  rateLimit: 5,
371
372
  preserveStackVersion: false,
372
373
  entriesPublish: true,
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const cli_utilities_1 = require("@contentstack/cli-utilities");
5
- const utils_1 = require("../utils");
6
5
  const modules_1 = tslib_1.__importDefault(require("./modules"));
7
6
  const modules_js_1 = tslib_1.__importDefault(require("./modules-js"));
7
+ const utils_1 = require("../utils");
8
8
  class ModuleImporter {
9
9
  constructor(managementAPIClient, importConfig) {
10
10
  this.managementAPIClient = managementAPIClient;
@@ -43,6 +43,8 @@ class ModuleImporter {
43
43
  // To support the old config
44
44
  this.importConfig.data = backupDir;
45
45
  }
46
+ // NOTE init log
47
+ (0, utils_1.initLogger)(this.importConfig);
46
48
  await (0, utils_1.sanitizeStack)(this.stackAPIClient);
47
49
  return this.import();
48
50
  }
@@ -58,7 +60,7 @@ class ModuleImporter {
58
60
  (0, utils_1.log)(this.importConfig, `Starting import of ${moduleName} module`, 'info');
59
61
  // import the modules by name
60
62
  // calls the module runner which inturn calls the module itself
61
- // Todo: Implement a mechanism to determine whether module is new or old
63
+ // NOTE: Implement a mechanism to determine whether module is new or old
62
64
  if (this.importConfig.contentVersion === 2) {
63
65
  return (0, modules_1.default)({
64
66
  stackAPIClient: this.stackAPIClient,
@@ -3,7 +3,7 @@ import { ImportConfig, ModuleClassParams } from '../../types';
3
3
  export type AdditionalKeys = {
4
4
  backupDir: string;
5
5
  };
6
- export type ApiModuleType = 'create-assets' | 'replace-assets' | 'publish-assets' | 'create-assets-folder' | 'create-extensions' | 'create-locale' | 'update-locale' | 'create-gfs' | 'create-cts' | 'update-cts' | 'update-gfs' | 'create-environments' | 'create-labels' | 'update-labels' | 'create-webhooks' | 'create-workflows' | 'create-custom-role' | 'create-entries' | 'update-entries' | 'publish-entries' | 'delete-entries';
6
+ export type ApiModuleType = 'create-assets' | 'replace-assets' | 'publish-assets' | 'create-assets-folder' | 'create-extensions' | 'update-extensions' | 'create-locale' | 'update-locale' | 'create-gfs' | 'create-cts' | 'update-cts' | 'update-gfs' | 'create-environments' | 'create-labels' | 'update-labels' | 'create-webhooks' | 'create-workflows' | 'create-custom-role' | 'create-entries' | 'update-entries' | 'publish-entries' | 'delete-entries';
7
7
  export type ApiOptions = {
8
8
  uid?: string;
9
9
  url?: string;
@@ -57,6 +57,15 @@ export default class EntriesImport extends BaseClass {
57
57
  * @returns {ApiOptions} ApiOptions
58
58
  */
59
59
  serializeEntries(apiOptions: ApiOptions): ApiOptions;
60
+ replaceEntries({ cTUid, locale }: {
61
+ cTUid: string;
62
+ locale: string;
63
+ }): Promise<void>;
64
+ replaceEntriesHandler({ apiParams, element: entry, isLastRequest, }: {
65
+ apiParams: ApiOptions;
66
+ element: Record<string, string>;
67
+ isLastRequest: boolean;
68
+ }): Promise<unknown>;
60
69
  populateEntryUpdatePayload(): {
61
70
  cTUid: string;
62
71
  locale: string;
@@ -56,6 +56,14 @@ class EntriesImport extends base_class_1.default {
56
56
  for (let entryRequestOption of entryRequestOptions) {
57
57
  await this.createEntries(entryRequestOption);
58
58
  }
59
+ if (this.importConfig.replaceExisting && (0, lodash_1.indexOf)(this.importConfig.overwriteSupportedModules, 'entries') !== -1) {
60
+ // Note: Instead of using entryRequestOptions, we can prepare request options for replace, to avoid unnecessary operations
61
+ for (let entryRequestOption of entryRequestOptions) {
62
+ await this.replaceEntries(entryRequestOption).catch((error) => {
63
+ (0, utils_1.log)(this.importConfig, `Error while replacing the existing entries ${(0, utils_1.formatError)(error)}`, 'error');
64
+ });
65
+ }
66
+ }
59
67
  await utils_1.fileHelper.writeLargeFile(path.join(this.entriesMapperPath, 'uid-mapping.json'), this.entriesUidMapper); // TBD: manages mapper in one file, should find an alternative
60
68
  utils_1.fsUtil.writeFile(path.join(this.entriesMapperPath, 'failed-entries.json'), this.failedEntries);
61
69
  if (this.autoCreatedEntries.length > 0) {
@@ -129,7 +137,8 @@ class EntriesImport extends base_class_1.default {
129
137
  * @returns {ApiOptions} ApiOptions
130
138
  */
131
139
  serializeUpdateCTs(apiOptions) {
132
- const { apiData: contentType } = apiOptions;
140
+ const { apiData } = apiOptions;
141
+ const contentType = (0, lodash_1.cloneDeep)(apiData);
133
142
  if (contentType.field_rules) {
134
143
  delete contentType.field_rules;
135
144
  }
@@ -201,6 +210,15 @@ class EntriesImport extends base_class_1.default {
201
210
  keepMetadata: false,
202
211
  omitKeys: this.entriesConfig.invalidKeys,
203
212
  });
213
+ // create file instance for existing entries
214
+ const existingEntriesFileHelper = new cli_utilities_1.FsUtility({
215
+ moduleName: 'entries',
216
+ indexFileName: 'index.json',
217
+ basePath: path.join(this.entriesMapperPath, cTUid, locale, 'existing'),
218
+ chunkFileSize: this.entriesConfig.chunkFileSize,
219
+ keepMetadata: false,
220
+ omitKeys: this.entriesConfig.invalidKeys,
221
+ });
204
222
  const contentType = (0, lodash_1.find)(this.cTs, { uid: cTUid });
205
223
  const onSuccess = ({ response, apiData: entry, additionalInfo }) => {
206
224
  var _a, _b;
@@ -227,10 +245,33 @@ class EntriesImport extends base_class_1.default {
227
245
  entriesCreateFileHelper.writeIntoFile({ [entry.uid]: entry }, { mapKeyVal: true });
228
246
  }
229
247
  };
230
- const onReject = ({ error, apiData: { uid, title } }) => {
231
- (0, utils_1.log)(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to create`, 'error');
232
- (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
233
- this.failedEntries.push({ content_type: cTUid, locale, entry: { uid, title } });
248
+ const onReject = ({ error, apiData: entry, additionalInfo }) => {
249
+ var _a, _b;
250
+ const { title, uid } = entry;
251
+ //Note: write existing entries into files to handler later
252
+ if (error.errorCode === 119) {
253
+ if (((_a = error === null || error === void 0 ? void 0 : error.errors) === null || _a === void 0 ? void 0 : _a.title) || ((_b = error === null || error === void 0 ? void 0 : error.errors) === null || _b === void 0 ? void 0 : _b.uid)) {
254
+ if (this.importConfig.replaceExisting &&
255
+ (0, lodash_1.indexOf)(this.importConfig.overwriteSupportedModules, 'entries') !== -1) {
256
+ entry.entryOldUid = uid;
257
+ entry.sourceEntryFilePath = path.join(basePath, additionalInfo.entryFileName); // stores source file path temporarily
258
+ existingEntriesFileHelper.writeIntoFile({ [uid]: entry }, { mapKeyVal: true });
259
+ }
260
+ if (!this.importConfig.skipExisting) {
261
+ (0, utils_1.log)(this.importConfig, `Entry '${title}' already exists`, 'info');
262
+ }
263
+ }
264
+ else {
265
+ (0, utils_1.log)(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to create`, 'error');
266
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
267
+ this.failedEntries.push({ content_type: cTUid, locale, entry: { uid, title } });
268
+ }
269
+ }
270
+ else {
271
+ (0, utils_1.log)(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to create`, 'error');
272
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
273
+ this.failedEntries.push({ content_type: cTUid, locale, entry: { uid, title } });
274
+ }
234
275
  };
235
276
  for (const index in indexer) {
236
277
  const chunk = await fs.readChunkFiles.next().catch((error) => {
@@ -254,6 +295,7 @@ class EntriesImport extends base_class_1.default {
254
295
  concurrencyLimit: this.importConcurrency,
255
296
  }).then(() => {
256
297
  entriesCreateFileHelper === null || entriesCreateFileHelper === void 0 ? void 0 : entriesCreateFileHelper.completeFile(true);
298
+ existingEntriesFileHelper === null || existingEntriesFileHelper === void 0 ? void 0 : existingEntriesFileHelper.completeFile(true);
257
299
  (0, utils_1.log)(this.importConfig, `Created entries for content type ${cTUid} in locale ${locale}`, 'success');
258
300
  });
259
301
  }
@@ -287,7 +329,7 @@ class EntriesImport extends base_class_1.default {
287
329
  apiOptions.apiData = entryResponse;
288
330
  apiOptions.additionalInfo[entryResponse.uid] = {
289
331
  isLocalized: true,
290
- entryOldUid: entry.uid
332
+ entryOldUid: entry.uid,
291
333
  };
292
334
  return apiOptions;
293
335
  }
@@ -301,6 +343,118 @@ class EntriesImport extends base_class_1.default {
301
343
  }
302
344
  return apiOptions;
303
345
  }
346
+ async replaceEntries({ cTUid, locale }) {
347
+ const processName = 'Replace existing Entries';
348
+ const indexFileName = 'index.json';
349
+ const basePath = path.join(this.entriesMapperPath, cTUid, locale, 'existing');
350
+ const fs = new cli_utilities_1.FsUtility({ basePath, indexFileName });
351
+ const indexer = fs.indexFileContent;
352
+ const indexerCount = (0, lodash_1.values)(indexer).length;
353
+ if (indexerCount === 0) {
354
+ return Promise.resolve();
355
+ }
356
+ // Write updated entries
357
+ const entriesReplaceFileHelper = new cli_utilities_1.FsUtility({
358
+ moduleName: 'entries',
359
+ indexFileName: 'index.json',
360
+ basePath: path.join(this.entriesMapperPath, cTUid, locale),
361
+ chunkFileSize: this.entriesConfig.chunkFileSize,
362
+ keepMetadata: false,
363
+ useIndexer: true,
364
+ omitKeys: this.entriesConfig.invalidKeys,
365
+ });
366
+ // log(this.importConfig, `Starting to update entries with references for ${cTUid} in locale ${locale}`, 'info');
367
+ const contentType = (0, lodash_1.find)(this.cTs, { uid: cTUid });
368
+ const onSuccess = ({ response, apiData: entry, additionalInfo }) => {
369
+ (0, utils_1.log)(this.importConfig, `Replaced entry: '${entry.title}' of content type ${cTUid} in locale ${locale}`, 'info');
370
+ this.entriesUidMapper[entry.uid] = response.uid;
371
+ entriesReplaceFileHelper.writeIntoFile({ [entry.uid]: entry }, { mapKeyVal: true });
372
+ };
373
+ const onReject = ({ error, apiData: { uid, title } }) => {
374
+ (0, utils_1.log)(this.importConfig, `${title} entry of content type ${cTUid} in locale ${locale} failed to replace`, 'error');
375
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
376
+ this.failedEntries.push({
377
+ content_type: cTUid,
378
+ locale,
379
+ entry: { uid: this.entriesUidMapper[uid], title },
380
+ entryId: uid,
381
+ });
382
+ };
383
+ for (const index in indexer) {
384
+ const chunk = await fs.readChunkFiles.next().catch((error) => {
385
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
386
+ });
387
+ if (chunk) {
388
+ let apiContent = (0, lodash_1.values)(chunk);
389
+ await this.makeConcurrentCall({
390
+ apiContent,
391
+ processName,
392
+ indexerCount,
393
+ currentIndexer: +index,
394
+ apiParams: {
395
+ reject: onReject,
396
+ resolve: onSuccess,
397
+ entity: 'update-entries',
398
+ includeParamOnCompletion: true,
399
+ additionalInfo: { contentType, locale, cTUid },
400
+ },
401
+ concurrencyLimit: this.importConcurrency,
402
+ }, this.replaceEntriesHandler.bind(this)).then(() => {
403
+ entriesReplaceFileHelper === null || entriesReplaceFileHelper === void 0 ? void 0 : entriesReplaceFileHelper.completeFile(true);
404
+ (0, utils_1.log)(this.importConfig, `Replaced entries for content type ${cTUid} in locale ${locale}`, 'success');
405
+ });
406
+ }
407
+ }
408
+ }
409
+ async replaceEntriesHandler({ apiParams, element: entry, isLastRequest, }) {
410
+ const { additionalInfo: { cTUid, locale } = {} } = apiParams;
411
+ return new Promise(async (resolve, reject) => {
412
+ const { items: [entryInStack] = [] } = (await this.stack
413
+ .contentType(cTUid)
414
+ .entry()
415
+ .query({ query: { title: entry.title, locale } })
416
+ .findOne()
417
+ .catch((error) => {
418
+ apiParams.reject({
419
+ error,
420
+ apiData: entry,
421
+ });
422
+ reject(true);
423
+ })) || {};
424
+ if (entryInStack) {
425
+ const entryPayload = this.stack.contentType(cTUid).entry(entryInStack.uid);
426
+ Object.assign(entryPayload, entryInStack, (0, lodash_1.cloneDeep)(entry), {
427
+ uid: entryInStack.uid,
428
+ urlPath: entryInStack.urlPath,
429
+ stackHeaders: entryInStack.stackHeaders,
430
+ _version: entryInStack._version,
431
+ });
432
+ return entryPayload
433
+ .update({ locale })
434
+ .then((response) => {
435
+ apiParams.resolve({
436
+ response,
437
+ apiData: entry,
438
+ });
439
+ resolve(true);
440
+ })
441
+ .catch((error) => {
442
+ apiParams.reject({
443
+ error,
444
+ apiData: entry,
445
+ });
446
+ reject(true);
447
+ });
448
+ }
449
+ else {
450
+ apiParams.reject({
451
+ error: new Error(`Entry with title ${entry.title} not found in the stack`),
452
+ apiData: entry,
453
+ });
454
+ reject(true);
455
+ }
456
+ });
457
+ }
304
458
  populateEntryUpdatePayload() {
305
459
  const requestOptions = [];
306
460
  for (let locale of this.locales) {
@@ -428,7 +582,8 @@ class EntriesImport extends base_class_1.default {
428
582
  * @returns {ApiOptions} ApiOptions
429
583
  */
430
584
  serializeUpdateCTsWithRef(apiOptions) {
431
- const { apiData: contentType } = apiOptions;
585
+ const { apiData } = apiOptions;
586
+ const contentType = (0, lodash_1.cloneDeep)(apiData);
432
587
  if (contentType.field_rules) {
433
588
  delete contentType.field_rules;
434
589
  }
@@ -11,6 +11,7 @@ export default class ImportExtensions extends BaseClass {
11
11
  private extUidMapper;
12
12
  private extSuccess;
13
13
  private extFailed;
14
+ private existingExtensions;
14
15
  constructor({ importConfig, stackAPIClient }: ModuleClassParams);
15
16
  /**
16
17
  * @method start
@@ -18,10 +19,10 @@ export default class ImportExtensions extends BaseClass {
18
19
  */
19
20
  start(): Promise<void>;
20
21
  importExtensions(): Promise<any>;
21
- /**
22
- * @method serializeExtensions
23
- * @param {ApiOptions} apiOptions ApiOptions
24
- * @returns {ApiOptions} ApiOptions
25
- */
26
- serializeExtensions(apiOptions: ApiOptions): ApiOptions;
22
+ replaceExtensions(): Promise<any>;
23
+ replaceExtensionHandler({ apiParams, element: extension, isLastRequest, }: {
24
+ apiParams: ApiOptions;
25
+ element: Record<string, string>;
26
+ isLastRequest: boolean;
27
+ }): Promise<unknown>;
27
28
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
5
5
  const values_1 = tslib_1.__importDefault(require("lodash/values"));
6
+ const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep"));
6
7
  const node_path_1 = require("node:path");
7
8
  const utils_1 = require("../../utils");
8
9
  const base_class_1 = tslib_1.__importDefault(require("./base-class"));
@@ -17,6 +18,7 @@ class ImportExtensions extends base_class_1.default {
17
18
  this.extFailsPath = (0, node_path_1.join)(this.mapperDirPath, 'fails.json');
18
19
  this.extFailed = [];
19
20
  this.extSuccess = [];
21
+ this.existingExtensions = [];
20
22
  this.extUidMapper = {};
21
23
  }
22
24
  /**
@@ -39,6 +41,12 @@ class ImportExtensions extends base_class_1.default {
39
41
  ? utils_1.fsUtil.readFile((0, node_path_1.join)(this.extUidMapperPath), true)
40
42
  : {};
41
43
  await this.importExtensions();
44
+ // Note: if any extensions present, then update it
45
+ if (this.importConfig.replaceExisting && this.existingExtensions.length > 0) {
46
+ await this.replaceExtensions().catch((error) => {
47
+ (0, utils_1.log)(this.importConfig, `Error while replacing extensions ${(0, utils_1.formatError)(error)}`, 'error');
48
+ });
49
+ }
42
50
  if ((_a = this.extSuccess) === null || _a === void 0 ? void 0 : _a.length) {
43
51
  utils_1.fsUtil.writeFile(this.extSuccessPath, this.extSuccess);
44
52
  }
@@ -61,10 +69,14 @@ class ImportExtensions extends base_class_1.default {
61
69
  };
62
70
  const onReject = ({ error, apiData }) => {
63
71
  var _a;
64
- const err = (error === null || error === void 0 ? void 0 : error.message) ? JSON.parse(error.message) : error;
65
72
  const { title } = apiData;
66
- if ((_a = err === null || err === void 0 ? void 0 : err.errors) === null || _a === void 0 ? void 0 : _a.title) {
67
- (0, utils_1.log)(this.importConfig, `Extension '${title}' already exists`, 'info');
73
+ if ((_a = error === null || error === void 0 ? void 0 : error.errors) === null || _a === void 0 ? void 0 : _a.title) {
74
+ if (this.importConfig.replaceExisting) {
75
+ this.existingExtensions.push(apiData);
76
+ }
77
+ if (!this.importConfig.skipExisting) {
78
+ (0, utils_1.log)(this.importConfig, `Extension '${title}' already exists`, 'info');
79
+ }
68
80
  }
69
81
  else {
70
82
  this.extFailed.push(apiData);
@@ -76,7 +88,6 @@ class ImportExtensions extends base_class_1.default {
76
88
  apiContent,
77
89
  processName: 'import extensions',
78
90
  apiParams: {
79
- serializeData: this.serializeExtensions.bind(this),
80
91
  reject: onReject.bind(this),
81
92
  resolve: onSuccess.bind(this),
82
93
  entity: 'create-extensions',
@@ -85,21 +96,76 @@ class ImportExtensions extends base_class_1.default {
85
96
  concurrencyLimit: this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1,
86
97
  }, undefined, false);
87
98
  }
88
- /**
89
- * @method serializeExtensions
90
- * @param {ApiOptions} apiOptions ApiOptions
91
- * @returns {ApiOptions} ApiOptions
92
- */
93
- serializeExtensions(apiOptions) {
94
- const { apiData: extension } = apiOptions;
95
- if (this.extUidMapper.hasOwnProperty(extension.uid)) {
96
- (0, utils_1.log)(this.importConfig, `Extension '${extension.title}' already exists. Skipping it to avoid duplicates!`, 'info');
97
- apiOptions.entity = undefined;
98
- }
99
- else {
100
- apiOptions.apiData = extension;
101
- }
102
- return apiOptions;
99
+ async replaceExtensions() {
100
+ const onSuccess = ({ response, apiData: { uid, title } = { uid: null, title: '' } }) => {
101
+ this.extSuccess.push(response);
102
+ this.extUidMapper[uid] = response.uid;
103
+ (0, utils_1.log)(this.importConfig, `Extension '${title}' replaced successfully`, 'success');
104
+ utils_1.fsUtil.writeFile(this.extUidMapperPath, this.extUidMapper);
105
+ };
106
+ const onReject = ({ error, apiData }) => {
107
+ this.extFailed.push(apiData);
108
+ (0, utils_1.log)(this.importConfig, `Extension '${apiData.title}' failed to replace ${(0, utils_1.formatError)(error)}`, 'error');
109
+ (0, utils_1.log)(this.importConfig, error, 'error');
110
+ };
111
+ await this.makeConcurrentCall({
112
+ apiContent: this.existingExtensions,
113
+ processName: 'Replace extensions',
114
+ apiParams: {
115
+ reject: onReject.bind(this),
116
+ resolve: onSuccess.bind(this),
117
+ entity: 'update-extensions',
118
+ includeParamOnCompletion: true,
119
+ },
120
+ concurrencyLimit: this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1,
121
+ }, this.replaceExtensionHandler.bind(this));
122
+ }
123
+ async replaceExtensionHandler({ apiParams, element: extension, isLastRequest, }) {
124
+ return new Promise(async (resolve, reject) => {
125
+ const { items: [extensionsInStack] = [] } = await this.stack
126
+ .extension()
127
+ .query({ query: { title: extension.title } })
128
+ .findOne()
129
+ .catch((error) => {
130
+ apiParams.reject({
131
+ error,
132
+ apiData: extension,
133
+ });
134
+ reject(true);
135
+ });
136
+ if (extensionsInStack) {
137
+ const extensionPayload = this.stack.extension(extension.uid);
138
+ Object.assign(extensionPayload, extensionsInStack, (0, cloneDeep_1.default)(extension), {
139
+ uid: extensionsInStack.uid,
140
+ urlPath: extensionsInStack.urlPath,
141
+ _version: extensionsInStack._version,
142
+ stackHeaders: extensionsInStack.stackHeaders,
143
+ });
144
+ return extensionPayload
145
+ .update()
146
+ .then((response) => {
147
+ apiParams.resolve({
148
+ response,
149
+ apiData: extension,
150
+ });
151
+ resolve(true);
152
+ })
153
+ .catch((error) => {
154
+ apiParams.reject({
155
+ error,
156
+ apiData: extension,
157
+ });
158
+ reject(true);
159
+ });
160
+ }
161
+ else {
162
+ apiParams.reject({
163
+ error: new Error(`Extension with title ${extension.title} not found in the stack`),
164
+ apiData: extension,
165
+ });
166
+ reject(true);
167
+ }
168
+ });
103
169
  }
104
170
  }
105
171
  exports.default = ImportExtensions;
@@ -22,6 +22,7 @@ export default class ImportGlobalFields extends BaseClass {
22
22
  private marketplaceAppMapperPath;
23
23
  private reqConcurrency;
24
24
  private installedExtensions;
25
+ private existingGFs;
25
26
  private gFsConfig;
26
27
  constructor({ importConfig, stackAPIClient }: ModuleClassParams);
27
28
  start(): Promise<any>;
@@ -31,4 +32,11 @@ export default class ImportGlobalFields extends BaseClass {
31
32
  element: Record<string, string>;
32
33
  isLastRequest: boolean;
33
34
  }): Promise<unknown>;
35
+ replaceGFs(): Promise<any>;
36
+ /**
37
+ * @method serializeUpdateGFs
38
+ * @param {ApiOptions} apiOptions ApiOptions
39
+ * @returns {ApiOptions} ApiOptions
40
+ */
41
+ serializeReplaceGFs(apiOptions: ApiOptions): ApiOptions;
34
42
  }
@@ -21,6 +21,7 @@ class ImportGlobalFields extends base_class_1.default {
21
21
  this.createdGFs = [];
22
22
  this.failedGFs = [];
23
23
  this.pendingGFs = [];
24
+ this.existingGFs = [];
24
25
  this.reqConcurrency = this.gFsConfig.writeConcurrency || this.config.writeConcurrency;
25
26
  this.gFsMapperPath = path.resolve(this.config.data, 'mapper', 'global_fields');
26
27
  this.gFsFolderPath = path.resolve(this.config.data, this.gFsConfig.dirName);
@@ -43,6 +44,11 @@ class ImportGlobalFields extends base_class_1.default {
43
44
  this.installedExtensions = ((await utils_1.fsUtil.readFile(this.marketplaceAppMapperPath)) || { extension_uid: {} }).extension_uid;
44
45
  await this.importGFs();
45
46
  utils_1.fsUtil.writeFile(this.gFsPendingPath, this.pendingGFs);
47
+ if (this.importConfig.replaceExisting && this.existingGFs.length > 0) {
48
+ await this.replaceGFs().catch((error) => {
49
+ (0, utils_1.log)(this.importConfig, `Error while replacing global fields ${(0, utils_1.formatError)(error)}`, 'error');
50
+ });
51
+ }
46
52
  (0, utils_1.log)(this.config, 'Global fields import has been completed!', 'info');
47
53
  }
48
54
  async importGFs() {
@@ -52,10 +58,22 @@ class ImportGlobalFields extends base_class_1.default {
52
58
  utils_1.fsUtil.writeFile(this.gFsUidMapperPath, this.gFsUidMapper);
53
59
  (0, utils_1.log)(this.config, 'Global field ' + uid + ' created successfully', 'success');
54
60
  };
55
- const onReject = ({ error, apiData: { uid } = undefined }) => {
56
- (0, utils_1.log)(this.importConfig, `Global fields '${uid}' failed to import`, 'error');
57
- (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
58
- this.failedGFs.push({ uid });
61
+ const onReject = ({ error, apiData: globalField = undefined }) => {
62
+ var _a;
63
+ const uid = globalField.uid;
64
+ if ((_a = error === null || error === void 0 ? void 0 : error.errors) === null || _a === void 0 ? void 0 : _a.title) {
65
+ if (this.importConfig.replaceExisting) {
66
+ this.existingGFs.push(globalField);
67
+ }
68
+ if (!this.importConfig.skipExisting) {
69
+ (0, utils_1.log)(this.importConfig, `Global fields '${uid}' already exist`, 'info');
70
+ }
71
+ }
72
+ else {
73
+ (0, utils_1.log)(this.importConfig, `Global fields '${uid}' failed to import`, 'error');
74
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
75
+ this.failedGFs.push({ uid });
76
+ }
59
77
  };
60
78
  return await this.makeConcurrentCall({
61
79
  processName: 'Import global fields',
@@ -73,7 +91,7 @@ class ImportGlobalFields extends base_class_1.default {
73
91
  return new Promise(async (resolve, reject) => {
74
92
  (0, utils_1.lookupExtension)(this.config, globalField.schema, this.config.preserveStackVersion, this.installedExtensions);
75
93
  let flag = { supressed: false };
76
- await (0, utils_1.removeReferenceFields)(globalField.schema, flag, this.stackAPIClient);
94
+ await (0, utils_1.removeReferenceFields)(globalField.schema, flag, this.stack);
77
95
  if (flag.supressed) {
78
96
  this.pendingGFs.push(globalField.uid);
79
97
  }
@@ -96,5 +114,44 @@ class ImportGlobalFields extends base_class_1.default {
96
114
  });
97
115
  });
98
116
  }
117
+ async replaceGFs() {
118
+ const onSuccess = ({ response: globalField, apiData: { uid } = { uid: null } }) => {
119
+ this.createdGFs.push(globalField);
120
+ this.gFsUidMapper[uid] = globalField;
121
+ utils_1.fsUtil.writeFile(this.gFsUidMapperPath, this.gFsUidMapper);
122
+ (0, utils_1.log)(this.config, 'Global field ' + uid + ' replaced successfully', 'success');
123
+ };
124
+ const onReject = ({ error, apiData: { uid } }) => {
125
+ (0, utils_1.log)(this.importConfig, `Global fields '${uid}' failed to replace`, 'error');
126
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
127
+ this.failedGFs.push({ uid });
128
+ };
129
+ await this.makeConcurrentCall({
130
+ apiContent: this.existingGFs,
131
+ processName: 'Replace global fields',
132
+ apiParams: {
133
+ serializeData: this.serializeReplaceGFs.bind(this),
134
+ reject: onReject.bind(this),
135
+ resolve: onSuccess.bind(this),
136
+ entity: 'update-gfs',
137
+ includeParamOnCompletion: true,
138
+ },
139
+ concurrencyLimit: this.importConfig.concurrency || this.importConfig.fetchConcurrency || 1,
140
+ }, undefined, false);
141
+ }
142
+ /**
143
+ * @method serializeUpdateGFs
144
+ * @param {ApiOptions} apiOptions ApiOptions
145
+ * @returns {ApiOptions} ApiOptions
146
+ */
147
+ serializeReplaceGFs(apiOptions) {
148
+ const { apiData: globalField } = apiOptions;
149
+ const globalFieldPayload = this.stack.globalField(globalField.uid);
150
+ Object.assign(globalFieldPayload, (0, lodash_1.cloneDeep)(globalField), {
151
+ stackHeaders: globalFieldPayload.stackHeaders,
152
+ });
153
+ apiOptions.apiData = globalFieldPayload;
154
+ return apiOptions;
155
+ }
99
156
  }
100
157
  exports.default = ImportGlobalFields;
@@ -102,8 +102,13 @@ class ImportLocales extends base_class_1.default {
102
102
  utils_1.fsUtil.writeFile(this.langUidMapperPath, this.langUidMapper);
103
103
  };
104
104
  const onReject = ({ error, apiData: { uid, code } = undefined }) => {
105
- (0, utils_1.log)(this.importConfig, `Language '${code}' failed to import`, 'error');
106
- (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
105
+ if ((error === null || error === void 0 ? void 0 : error.errorCode) === 247) {
106
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'info');
107
+ }
108
+ else {
109
+ (0, utils_1.log)(this.importConfig, `Language '${code}' failed to import`, 'error');
110
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
111
+ }
107
112
  this.failedLocales.push({ uid, code });
108
113
  };
109
114
  return await this.makeConcurrentCall({
@@ -14,6 +14,7 @@ const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
14
14
  const toLower_1 = tslib_1.__importDefault(require("lodash/toLower"));
15
15
  const cli_utilities_1 = require("@contentstack/cli-utilities");
16
16
  const base_class_1 = tslib_1.__importDefault(require("./base-class"));
17
+ const log_1 = require("../../utils/log");
17
18
  const interactive_1 = require("../../utils/interactive");
18
19
  const utils_1 = require("../../utils");
19
20
  class ImportMarketplaceApps extends base_class_1.default {
@@ -54,7 +55,7 @@ class ImportMarketplaceApps extends base_class_1.default {
54
55
  this.developerHubBaseUrl = this.importConfig.developerHubBaseUrl || (await (0, utils_1.getDeveloperHubUrl)(this.importConfig));
55
56
  this.sdkClient = await (0, cli_utilities_1.managementSDKClient)({ endpoint: this.developerHubBaseUrl });
56
57
  this.appSdkAxiosInstance = await (0, cli_utilities_1.managementSDKClient)({
57
- host: this.developerHubBaseUrl.split('://').pop()
58
+ host: this.developerHubBaseUrl.split('://').pop(),
58
59
  });
59
60
  this.importConfig.org_uid = await (0, utils_1.getOrgUid)(this.importConfig);
60
61
  await this.setHttpClient();
@@ -223,6 +224,7 @@ class ImportMarketplaceApps extends base_class_1.default {
223
224
  return this.createPrivateApps(updatedApp, true, appSuffix + 1);
224
225
  }
225
226
  else {
227
+ (0, log_1.trace)(response, 'error', true);
226
228
  (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(message), 'error');
227
229
  if (this.importConfig.forceStopMarketplaceAppsPrompt)
228
230
  return Promise.resolve();
@@ -308,13 +310,17 @@ class ImportMarketplaceApps extends base_class_1.default {
308
310
  })
309
311
  .then(({ data }) => {
310
312
  if (data === null || data === void 0 ? void 0 : data.message) {
313
+ (0, log_1.trace)(data, 'error', true);
311
314
  (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(data.message), 'success');
312
315
  }
313
316
  else {
314
317
  (0, utils_1.log)(this.importConfig, `${app.manifest.name} app config updated successfully.!`, 'success');
315
318
  }
316
319
  })
317
- .catch((error) => (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error'));
320
+ .catch((error) => {
321
+ (0, log_1.trace)(error, 'error', true);
322
+ (0, utils_1.log)(this.importConfig, (0, utils_1.formatError)(error), 'error');
323
+ });
318
324
  }
319
325
  }
320
326
  exports.default = ImportMarketplaceApps;
@@ -9,8 +9,9 @@ const path = require('path');
9
9
  const chalk = require('chalk');
10
10
  const mkdirp = require('mkdirp');
11
11
  const { cliux, HttpClient, NodeCrypto, managementSDKClient, isAuthenticated, HttpClientDecorator, OauthDecorator, } = require('@contentstack/cli-utilities');
12
- const { log, fileHelper: { readFileSync, writeFile }, formatError, } = require('../../utils');
13
- let { default: config } = require('../../config');
12
+ const { log, formatError, fileHelper: { readFileSync, writeFile }, } = require('../../utils');
13
+ const { trace } = require('../../utils/log');
14
+ const { default: config } = require('../../config');
14
15
  const { getDeveloperHubUrl, getAllStackSpecificApps } = require('../../utils/marketplace-app-helper');
15
16
  module.exports = class ImportMarketplaceApps {
16
17
  constructor(importConfig, stackAPIClient) {
@@ -306,6 +307,7 @@ module.exports = class ImportMarketplaceApps {
306
307
  updateParam = Object.assign(Object.assign({ manifest: app.manifest }, installation), { configuration, server_configuration });
307
308
  }
308
309
  else if (installation.message) {
310
+ trace(installation, 'error', true);
309
311
  log(this.config, formatError(installation.message), 'success');
310
312
  await this.confirmToCloseProcess(installation);
311
313
  }
@@ -329,6 +331,7 @@ module.exports = class ImportMarketplaceApps {
329
331
  .get(response.redirect_url)
330
332
  .then(async ({ response }) => {
331
333
  if (_.includes([501, 403], response.status)) {
334
+ trace(response, 'error', true); // NOTE Log complete stack and hide on UI
332
335
  log(this.config, `${appName} - ${response.statusText}, OAuth api call failed.!`, 'error');
333
336
  log(this.config, formatError(response), 'error');
334
337
  await this.confirmToCloseProcess({ message: response.data });
@@ -338,6 +341,7 @@ module.exports = class ImportMarketplaceApps {
338
341
  }
339
342
  })
340
343
  .catch((error) => {
344
+ trace(error, 'error', true);
341
345
  if (_.includes([501, 403], error.status)) {
342
346
  log(this.config, formatError(error), 'error');
343
347
  }
@@ -403,13 +407,17 @@ module.exports = class ImportMarketplaceApps {
403
407
  })
404
408
  .then(({ data }) => {
405
409
  if (data.message) {
410
+ trace(data, 'error', true);
406
411
  log(this.config, formatError(data.message), 'success');
407
412
  }
408
413
  else {
409
414
  log(this.config, `${app.manifest.name} app config updated successfully.!`, 'success');
410
415
  }
411
416
  })
412
- .catch((error) => log(this.config, formatError(error), 'error'));
417
+ .catch((error) => {
418
+ trace(data, 'error', true);
419
+ log(this.config, formatError(error), 'error');
420
+ });
413
421
  }
414
422
  validateAppName(name) {
415
423
  if (name.length < 3 || name.length > 20) {
@@ -139,4 +139,5 @@ export default interface DefaultConfig {
139
139
  marketplaceAppEncryptionKey: string;
140
140
  getEncryptionKeyMaxRetry: number;
141
141
  createBackupDir?: string;
142
+ overwriteSupportedModules: string[];
142
143
  }
@@ -42,6 +42,8 @@ export default interface ImportConfig extends DefaultConfig, ExternalConfig {
42
42
  destinationStackName?: string;
43
43
  org_uid?: string;
44
44
  contentVersion: number;
45
+ replaceExisting?: boolean;
46
+ skipExisting?: boolean;
45
47
  }
46
48
  type branch = {
47
49
  uid: string;
@@ -29,6 +29,7 @@ export type ModuleClassParams = {
29
29
  export interface Extensions {
30
30
  dirName: string;
31
31
  fileName: string;
32
+ validKeys: string[];
32
33
  dependencies?: Modules[];
33
34
  }
34
35
  export interface MarketplaceAppsConfig {
@@ -1,4 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- ;
4
- ;
@@ -1,2 +1,2 @@
1
1
  import { ImportConfig } from '../types';
2
- export default function setupBackupDir(importConfig: ImportConfig): Promise<string>;
2
+ export default function backupHandler(importConfig: ImportConfig): Promise<string>;
@@ -5,44 +5,45 @@ const path = tslib_1.__importStar(require("path"));
5
5
  const fs_extra_1 = require("fs-extra");
6
6
  const cli_utilities_1 = require("@contentstack/cli-utilities");
7
7
  const index_1 = require("./index");
8
- function setupBackupDir(importConfig) {
9
- return new Promise(async (resolve, reject) => {
10
- if (importConfig.hasOwnProperty('useBackedupDir')) {
11
- return resolve(importConfig.useBackedupDir);
12
- }
13
- const subDir = isSubDirectory(importConfig);
14
- let backupDirPath;
15
- if (subDir) {
16
- backupDirPath = path.resolve(importConfig.contentDir, '..', '_backup_' + Math.floor(Math.random() * 1000));
17
- if (importConfig.createBackupDir) {
18
- cli_utilities_1.cliux.print(`Warning!!! Provided backup directory path is a sub directory of the content directory, Cannot copy to a sub directory. Hence new backup directory created - ${backupDirPath}`, {
19
- color: 'yellow',
20
- });
21
- }
8
+ async function backupHandler(importConfig) {
9
+ if (importConfig.hasOwnProperty('useBackedupDir')) {
10
+ return importConfig.useBackedupDir;
11
+ }
12
+ let backupDirPath;
13
+ const subDir = isSubDirectory(importConfig);
14
+ if (subDir) {
15
+ backupDirPath = path.resolve(importConfig.contentDir, '..', '_backup_' + Math.floor(Math.random() * 1000));
16
+ if (importConfig.createBackupDir) {
17
+ cli_utilities_1.cliux.print(`Warning!!! Provided backup directory path is a sub directory of the content directory, Cannot copy to a sub directory. Hence new backup directory created - ${backupDirPath}`, {
18
+ color: 'yellow',
19
+ });
22
20
  }
23
- else {
24
- //NOTE: If the backup folder's directory is provided, create it at that location; otherwise, the default path (working directory).
25
- backupDirPath = path.join(process.cwd(), '_backup_' + Math.floor(Math.random() * 1000));
26
- if (importConfig.createBackupDir) {
27
- if (index_1.fileHelper.fileExistsSync(importConfig.createBackupDir)) {
28
- index_1.fileHelper.removeDirSync(importConfig.createBackupDir);
29
- }
30
- index_1.fileHelper.makeDirectory(importConfig.createBackupDir);
31
- backupDirPath = importConfig.createBackupDir;
21
+ }
22
+ else {
23
+ // NOTE: If the backup folder's directory is provided, create it at that location; otherwise, the default path (working directory).
24
+ backupDirPath = path.join(process.cwd(), '_backup_' + Math.floor(Math.random() * 1000));
25
+ if (importConfig.createBackupDir) {
26
+ if (index_1.fileHelper.fileExistsSync(importConfig.createBackupDir)) {
27
+ index_1.fileHelper.removeDirSync(importConfig.createBackupDir);
32
28
  }
29
+ index_1.fileHelper.makeDirectory(importConfig.createBackupDir);
30
+ backupDirPath = importConfig.createBackupDir;
33
31
  }
34
- if (backupDirPath) {
35
- cli_utilities_1.cliux.print('Copying content to the backup directory...');
32
+ }
33
+ if (backupDirPath) {
34
+ cli_utilities_1.cliux.print('Copying content to the backup directory...');
35
+ return new Promise((resolve, reject) => {
36
36
  return (0, fs_extra_1.copy)(importConfig.contentDir, backupDirPath, (error) => {
37
37
  if (error) {
38
+ (0, index_1.trace)(error, 'error', true);
38
39
  return reject(error);
39
40
  }
40
- return resolve(backupDirPath);
41
+ resolve(backupDirPath);
41
42
  });
42
- }
43
- });
43
+ });
44
+ }
44
45
  }
45
- exports.default = setupBackupDir;
46
+ exports.default = backupHandler;
46
47
  /**
47
48
  * Check whether provided backup directory path is sub directory or not
48
49
  * @param importConfig
@@ -53,8 +54,8 @@ function isSubDirectory(importConfig) {
53
54
  const child = importConfig.createBackupDir ? importConfig.createBackupDir : process.cwd();
54
55
  const relative = path.relative(parent, child);
55
56
  if (relative) {
56
- return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
57
+ return !relative.startsWith('..') && !path.isAbsolute(relative);
57
58
  }
58
- //true if both parent and child have same path
59
+ // true if both parent and child have same path
59
60
  return true;
60
61
  }
@@ -75,6 +75,11 @@ const setupConfig = async (importCmdFlags) => {
75
75
  }
76
76
  // Note to support old modules
77
77
  config.target_stack = config.apiKey;
78
+ config.replaceExisting = importCmdFlags['replace-existing'];
79
+ config.skipExisting = importCmdFlags['skip-existing'];
80
+ if (config.replaceExisting && !(0, lodash_1.includes)(config.overwriteSupportedModules, config.moduleName)) {
81
+ throw new Error(`Failed to overwrite ${config.moduleName} module! Currently, with the import command, you can overwrite the following modules: ${config.overwriteSupportedModules.join(',')}`);
82
+ }
78
83
  return config;
79
84
  };
80
85
  exports.default = setupConfig;
@@ -10,3 +10,4 @@ export { schemaTemplate, suppressSchemaReference, removeReferenceFields } from '
10
10
  export { lookupExtension } from './extension-helper';
11
11
  export { lookupEntries, removeUidsFromJsonRteFields, removeEntryRefsFromJSONRTE, restoreJsonRteEntryRefs, } from './entries-helper';
12
12
  export * from './common-helper';
13
+ export * from './log';
@@ -40,3 +40,4 @@ Object.defineProperty(exports, "removeUidsFromJsonRteFields", { enumerable: true
40
40
  Object.defineProperty(exports, "removeEntryRefsFromJSONRTE", { enumerable: true, get: function () { return entries_helper_1.removeEntryRefsFromJSONRTE; } });
41
41
  Object.defineProperty(exports, "restoreJsonRteEntryRefs", { enumerable: true, get: function () { return entries_helper_1.restoreJsonRteEntryRefs; } });
42
42
  tslib_1.__exportStar(require("./common-helper"), exports);
43
+ tslib_1.__exportStar(require("./log"), exports);
@@ -0,0 +1,10 @@
1
+ import { LogEntry } from 'winston/index';
2
+ import { Logger } from '@contentstack/cli-utilities';
3
+ import { LogsType, MessageType } from '@contentstack/cli-utilities/lib/logger';
4
+ import { ImportConfig } from '../types';
5
+ export declare function isImportConfig(config: ImportConfig | MessageType): config is ImportConfig;
6
+ export declare function log(entry: LogEntry): void;
7
+ export declare function log(error: MessageType, logType: LogsType): void;
8
+ export declare function log(error: MessageType, logType: 'error', hidden: boolean): void;
9
+ export declare function initLogger(config?: ImportConfig | undefined): Logger;
10
+ export declare const trace: typeof log;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trace = exports.initLogger = exports.log = exports.isImportConfig = void 0;
4
+ const path_1 = require("path");
5
+ const cli_utilities_1 = require("@contentstack/cli-utilities");
6
+ let logger;
7
+ function isImportConfig(config) {
8
+ return config.data !== undefined && (config === null || config === void 0 ? void 0 : config.contentVersion) !== undefined;
9
+ }
10
+ exports.isImportConfig = isImportConfig;
11
+ function log(entryOrMessage, logType, hidden) {
12
+ logger = initLogger();
13
+ if (logType === 'error') {
14
+ logger.log(entryOrMessage, logType, hidden);
15
+ }
16
+ else {
17
+ logger.log(entryOrMessage, logType);
18
+ }
19
+ }
20
+ exports.log = log;
21
+ function initLogger(config) {
22
+ var _a;
23
+ if (!logger) {
24
+ const basePath = (0, path_1.join)((_a = config === null || config === void 0 ? void 0 : config.data) !== null && _a !== void 0 ? _a : process.cwd(), 'logs', 'import');
25
+ logger = new cli_utilities_1.Logger(Object.assign(config !== null && config !== void 0 ? config : {}, { basePath }));
26
+ }
27
+ return logger;
28
+ }
29
+ exports.initLogger = initLogger;
30
+ exports.trace = log;
@@ -9,7 +9,6 @@ exports.unlinkFileLogger = exports.log = void 0;
9
9
  const tslib_1 = require("tslib");
10
10
  const winston = tslib_1.__importStar(require("winston"));
11
11
  const path = tslib_1.__importStar(require("path"));
12
- const mkdirp_1 = tslib_1.__importDefault(require("mkdirp"));
13
12
  const slice = Array.prototype.slice;
14
13
  const ansiRegexPattern = [
15
14
  '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
@@ -55,9 +54,7 @@ let successTransport;
55
54
  let errorTransport;
56
55
  function init(_logPath) {
57
56
  if (!logger || !errorLogger) {
58
- var logsDir = path.resolve(_logPath, 'logs', 'import');
59
- // Create dir if doesn't already exist
60
- mkdirp_1.default.sync(logsDir);
57
+ const logsDir = path.resolve(_logPath, 'logs', 'import');
61
58
  successTransport = {
62
59
  filename: path.join(logsDir, 'success.log'),
63
60
  maxFiles: 20,
@@ -9,12 +9,13 @@ const includes_1 = tslib_1.__importDefault(require("lodash/includes"));
9
9
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
10
10
  const cli_utilities_1 = require("@contentstack/cli-utilities");
11
11
  const logger_1 = require("./logger");
12
- const interactive_1 = require("./interactive");
12
+ const log_1 = require("../utils/log");
13
13
  const utils_1 = require("../utils");
14
+ const interactive_1 = require("./interactive");
14
15
  const interactive_2 = require("../utils/interactive");
15
16
  const getAllStackSpecificApps = async (developerHubBaseUrl, httpClient, config) => {
16
17
  const appSdkAxiosInstance = await (0, cli_utilities_1.managementSDKClient)({
17
- host: developerHubBaseUrl.split('://').pop()
18
+ host: developerHubBaseUrl.split('://').pop(),
18
19
  });
19
20
  return await appSdkAxiosInstance.axiosInstance
20
21
  .get(`${developerHubBaseUrl}/installations?target_uids=${config.target_stack}`, {
@@ -23,7 +24,10 @@ const getAllStackSpecificApps = async (developerHubBaseUrl, httpClient, config)
23
24
  },
24
25
  })
25
26
  .then(({ data }) => data.data)
26
- .catch((error) => (0, logger_1.log)(config, `Failed to export marketplace-apps ${(0, utils_1.formatError)(error)}`, 'error'));
27
+ .catch((error) => {
28
+ (0, log_1.trace)(error, 'error', true);
29
+ (0, logger_1.log)(config, `Failed to export marketplace-apps ${(0, utils_1.formatError)(error)}`, 'error');
30
+ });
27
31
  };
28
32
  exports.getAllStackSpecificApps = getAllStackSpecificApps;
29
33
  const getDeveloperHubUrl = async (config) => {
@@ -41,6 +45,7 @@ const getOrgUid = async (config) => {
41
45
  .stack({ api_key: config.target_stack })
42
46
  .fetch()
43
47
  .catch((error) => {
48
+ (0, log_1.trace)(error, 'error', true);
44
49
  (0, logger_1.log)(config, (0, utils_1.formatError)(error), 'error');
45
50
  });
46
51
  return (tempStackData === null || tempStackData === void 0 ? void 0 : tempStackData.org_uid) || '';
@@ -92,6 +97,7 @@ const makeRedirectUrlCall = async (response, appName, config) => {
92
97
  .get(response.redirect_url)
93
98
  .then(async ({ response }) => {
94
99
  if ((0, includes_1.default)([501, 403], response.status)) {
100
+ (0, log_1.trace)(response, 'error', true);
95
101
  (0, logger_1.log)(config, `${appName} - ${response.statusText}, OAuth api call failed.!`, 'error');
96
102
  (0, logger_1.log)(config, (0, utils_1.formatError)(response), 'error');
97
103
  await (0, exports.confirmToCloseProcess)(response.data, config);
@@ -101,6 +107,7 @@ const makeRedirectUrlCall = async (response, appName, config) => {
101
107
  }
102
108
  })
103
109
  .catch((error) => {
110
+ (0, log_1.trace)(error, 'error', true);
104
111
  if ((0, includes_1.default)([501, 403], error.status)) {
105
112
  (0, logger_1.log)(config, (0, utils_1.formatError)(error), 'error');
106
113
  }
@@ -145,6 +152,9 @@ const updateAppConfig = async (client, config, app, payload) => {
145
152
  var _a;
146
153
  (0, logger_1.log)(config, `${(_a = app === null || app === void 0 ? void 0 : app.manifest) === null || _a === void 0 ? void 0 : _a.name} app config updated successfully.!`, 'success');
147
154
  })
148
- .catch((error) => (0, logger_1.log)(config, `Failed to update app config.${(0, utils_1.formatError)(error)}`, 'error'));
155
+ .catch((error) => {
156
+ (0, log_1.trace)(error, 'error', true);
157
+ (0, logger_1.log)(config, `Failed to update app config.${(0, utils_1.formatError)(error)}`, 'error');
158
+ });
149
159
  };
150
160
  exports.updateAppConfig = updateAppConfig;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.9.1",
2
+ "version": "1.10.1",
3
3
  "commands": {
4
4
  "cm:stacks:import": {
5
5
  "id": "cm:stacks:import",
@@ -86,6 +86,7 @@
86
86
  "type": "option",
87
87
  "char": "m",
88
88
  "description": "[optional] specific module name",
89
+ "required": false,
89
90
  "multiple": false
90
91
  },
91
92
  "backup-dir": {
@@ -121,6 +122,23 @@
121
122
  "description": "[optional] Override marketplace prompts",
122
123
  "required": false,
123
124
  "allowNo": false
125
+ },
126
+ "replace-existing": {
127
+ "name": "replace-existing",
128
+ "type": "boolean",
129
+ "description": "Replaces the existing module in the target stack.",
130
+ "required": false,
131
+ "allowNo": false,
132
+ "dependsOn": [
133
+ "module"
134
+ ]
135
+ },
136
+ "skip-existing": {
137
+ "name": "skip-existing",
138
+ "type": "boolean",
139
+ "description": "Skips the module exists warning messages.",
140
+ "required": false,
141
+ "allowNo": false
124
142
  }
125
143
  },
126
144
  "args": {}
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-import",
3
3
  "description": "Contentstack CLI plugin to import content into stack",
4
- "version": "1.9.1",
4
+ "version": "1.10.1",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/contentstack/cli/issues",
7
7
  "dependencies": {
8
- "@contentstack/cli-command": "~1.2.13",
9
- "@contentstack/cli-utilities": "~1.5.3",
10
- "@contentstack/management": "~1.10.2",
8
+ "@contentstack/cli-command": "~1.2.15",
9
+ "@contentstack/cli-utilities": "~1.5.5",
10
+ "@contentstack/management": "~1.11.0",
11
11
  "@oclif/core": "^2.9.3",
12
+ "axios": "^1.6.0",
12
13
  "big-json": "^3.2.0",
13
14
  "bluebird": "^3.7.2",
14
15
  "chalk": "^4.1.2",
@@ -92,7 +93,7 @@
92
93
  },
93
94
  "shortCommandName": {
94
95
  "cm:stacks:import": "IMPRT",
95
- "cm:import": "IMPRT"
96
+ "cm:import": "O-IMPRT"
96
97
  }
97
98
  },
98
99
  "repository": "https://github.com/contentstack/cli"