@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 +6 -2
- package/lib/commands/cm/stacks/import.js +13 -0
- package/lib/config/index.js +1 -0
- package/lib/import/module-importer.js +4 -2
- package/lib/import/modules/base-class.d.ts +1 -1
- package/lib/import/modules/entries.d.ts +9 -0
- package/lib/import/modules/entries.js +162 -7
- package/lib/import/modules/extensions.d.ts +7 -6
- package/lib/import/modules/extensions.js +85 -19
- package/lib/import/modules/global-fields.d.ts +8 -0
- package/lib/import/modules/global-fields.js +62 -5
- package/lib/import/modules/locales.js +7 -2
- package/lib/import/modules/marketplace-apps.js +8 -2
- package/lib/import/modules-js/marketplace-apps.js +11 -3
- package/lib/types/default-config.d.ts +1 -0
- package/lib/types/import-config.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +0 -2
- package/lib/utils/backup-handler.d.ts +1 -1
- package/lib/utils/backup-handler.js +32 -31
- package/lib/utils/import-config-handler.js +5 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/log.d.ts +10 -0
- package/lib/utils/log.js +30 -0
- package/lib/utils/logger.js +1 -4
- package/lib/utils/marketplace-app-helper.js +14 -4
- package/oclif.manifest.json +19 -1
- package/package.json +6 -5
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
|
-
[](https://github.com/contentstack/cli/blob/main/LICENSE)
|
|
5
|
+
[](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.
|
|
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]';
|
package/lib/config/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
67
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
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
|
-
|
|
106
|
-
|
|
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) =>
|
|
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 },
|
|
13
|
-
|
|
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) =>
|
|
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) {
|
package/lib/types/index.d.ts
CHANGED
package/lib/types/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ImportConfig } from '../types';
|
|
2
|
-
export default function
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
+
resolve(backupDirPath);
|
|
41
42
|
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
|
-
exports.default =
|
|
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
|
|
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;
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -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';
|
package/lib/utils/index.js
CHANGED
|
@@ -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;
|
package/lib/utils/log.js
ADDED
|
@@ -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;
|
package/lib/utils/logger.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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;
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "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.
|
|
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.
|
|
9
|
-
"@contentstack/cli-utilities": "~1.5.
|
|
10
|
-
"@contentstack/management": "~1.
|
|
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"
|