@contentstack/cli-cm-import 1.1.0 → 1.2.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 +4 -3
- package/oclif.manifest.json +1 -1
- package/package.json +8 -7
- package/src/app.js +90 -106
- package/src/commands/cm/stacks/import.js +8 -1
- package/src/config/default.js +9 -4
- package/src/lib/import/assets.js +291 -296
- package/src/lib/import/content-types.js +171 -246
- package/src/lib/import/custom-roles.js +110 -93
- package/src/lib/import/entries.js +216 -174
- package/src/lib/import/environments.js +40 -50
- package/src/lib/import/extensions.js +35 -41
- package/src/lib/import/global-fields.js +56 -68
- package/src/lib/import/labels.js +62 -61
- package/src/lib/import/locales.js +61 -64
- package/src/lib/import/marketplace-apps.js +293 -290
- package/src/lib/import/webhooks.js +45 -51
- package/src/lib/import/workflows.js +72 -62
- package/src/lib/util/extensionsUidReplace.js +9 -9
- package/src/lib/util/fs.js +91 -12
- package/src/lib/util/index.js +39 -3
- package/src/lib/util/log.js +7 -5
- package/src/lib/util/login.js +2 -1
- package/src/lib/util/lookupReplaceAssets.js +22 -10
- package/src/lib/util/lookupReplaceEntries.js +60 -60
- package/src/lib/util/marketplace-app-helper.js +25 -6
|
@@ -1,266 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const fileHelper = require('../util/fs');
|
|
5
|
+
const config = require('../../config/default');
|
|
6
|
+
const supress = require('../util/extensionsUidReplace');
|
|
7
|
+
const { getInstalledExtensions } = require('../util/marketplace-app-helper');
|
|
8
|
+
const { executeTask, formatError } = require('../util');
|
|
9
|
+
const { addlogs } = require('../util/log');
|
|
10
|
+
const { cloneDeep, remove, isEmpty, find, findIndex } = require('lodash');
|
|
11
|
+
const schemaTemplate = require('../util/schemaTemplate');
|
|
12
|
+
|
|
13
|
+
class ContentTypesImport {
|
|
14
|
+
constructor(importConfig, stackAPIClient) {
|
|
15
|
+
this.stackAPIClient = stackAPIClient;
|
|
16
|
+
this.importConfig = importConfig;
|
|
17
|
+
this.contentTypeConfig = importConfig.modules.content_types;
|
|
18
|
+
this.globalFieldConfig = importConfig.modules.globalfields;
|
|
19
|
+
this.importConcurrency = this.contentTypeConfig.importConcurrency || this.importConfig.importConcurrency;
|
|
20
|
+
this.writeConcurrency = this.contentTypeConfig.writeConcurrency || this.importConfig.writeConcurrency;
|
|
21
|
+
this.contentTypesFolderPath = path.join(this.importConfig.data, this.contentTypeConfig.dirName);
|
|
22
|
+
this.mapperFolderPath = path.join(this.importConfig.data, 'mapper', 'content_types');
|
|
23
|
+
this.existingContentTypesPath = path.join(this.mapperFolderPath, 'success.json');
|
|
24
|
+
this.globalFieldsFolderPath = path.resolve(this.importConfig.data, this.globalFieldConfig.dirName);
|
|
25
|
+
this.globalFieldMapperFolderPath = path.join(importConfig.data, 'mapper', 'global_fields', 'success.json');
|
|
26
|
+
this.globalFieldPendingPath = path.join(importConfig.data, 'mapper', 'global_fields', 'pending_global_fields.js');
|
|
27
|
+
this.appMapperFolderPath = path.join(importConfig.data, 'mapper', 'marketplace_apps');
|
|
28
|
+
this.ignoredFilesInContentTypesFolder = new Map([
|
|
29
|
+
['__master.json', 'true'],
|
|
30
|
+
['__priority.json', 'true'],
|
|
31
|
+
['schema.json', 'true'],
|
|
32
|
+
['.DS_Store', 'true'],
|
|
33
|
+
]);
|
|
34
|
+
this.contentTypes = [];
|
|
35
|
+
this.existingContentTypesUIds;
|
|
36
|
+
this.titleToUIdMap = new Map();
|
|
37
|
+
this.requestOptions = {
|
|
38
|
+
json: {},
|
|
39
|
+
};
|
|
40
|
+
this.fieldRules = [];
|
|
41
|
+
this.installedExtensions = [];
|
|
42
|
+
this.globalFields = [];
|
|
43
|
+
this.existingGlobalFields = [];
|
|
44
|
+
this.pendingGlobalFields = [];
|
|
45
|
+
}
|
|
21
46
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let globalFieldUpdateFile;
|
|
31
|
-
let globalFieldPendingPath;
|
|
32
|
-
let skipFiles = ['__master.json', '__priority.json', 'schema.json', '.DS_Store'];
|
|
33
|
-
let fileNames;
|
|
34
|
-
let field_rules_ct = [];
|
|
35
|
-
let client;
|
|
36
|
-
let stack = {};
|
|
47
|
+
async start() {
|
|
48
|
+
try {
|
|
49
|
+
// read content types
|
|
50
|
+
// remove content types already existing
|
|
51
|
+
if (fs.existsSync(this.existingContentTypesPath)) {
|
|
52
|
+
this.existingContentTypesUIds = fileHelper.readFileSync(this.existingContentTypesPath) || [];
|
|
53
|
+
this.existingContentTypesUIds = new Map(this.existingContentTypesUIds.map((id) => [id, 'true']));
|
|
54
|
+
}
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
56
|
+
const contentTypeFiles = fileHelper.readdirSync(this.contentTypesFolderPath);
|
|
57
|
+
for (let contentTypeName of contentTypeFiles) {
|
|
58
|
+
if (!this.ignoredFilesInContentTypesFolder.has(contentTypeName)) {
|
|
59
|
+
const contentTypePath = path.join(this.contentTypesFolderPath, contentTypeName);
|
|
60
|
+
const contentType = await fileHelper.readFile(contentTypePath);
|
|
61
|
+
if (!this.existingContentTypesUIds?.has(contentType.uid)) {
|
|
62
|
+
this.contentTypes.push(await fileHelper.readFile(contentTypePath));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// this.mapUidToTitle(this.csontentTypes);
|
|
67
|
+
|
|
68
|
+
// seed content type
|
|
69
|
+
addlogs(this.importConfig, 'Started to seed content types', 'info');
|
|
70
|
+
await executeTask(this.contentTypes, this.seedContentType.bind(this), { concurrency: this.importConcurrency });
|
|
71
|
+
addlogs(this.importConfig, 'Created content types', 'success');
|
|
72
|
+
|
|
73
|
+
// update content type
|
|
74
|
+
this.installedExtensions =
|
|
75
|
+
fileHelper.readFileSync(path.join(this.appMapperFolderPath, 'marketplace-apps.json')) || {};
|
|
76
|
+
if (isEmpty(this.installedExtensions)) {
|
|
77
|
+
this.installedExtensions = await getInstalledExtensions(this.importConfig);
|
|
78
|
+
}
|
|
46
79
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
addlogs(this.importConfig, 'Started to update content types with references', 'info');
|
|
81
|
+
await executeTask(this.contentTypes, this.updateContentType.bind(this), { concurrency: this.importConcurrency });
|
|
82
|
+
addlogs(this.importConfig, 'Updated content types with references', 'success');
|
|
83
|
+
|
|
84
|
+
// global field update
|
|
85
|
+
this.pendingGlobalFields = fileHelper.readFileSync(this.globalFieldPendingPath);
|
|
86
|
+
if (Array.isArray(this.pendingGlobalFields) && this.pendingGlobalFields.length > 0) {
|
|
87
|
+
this.globalFields = fileHelper.readFileSync(
|
|
88
|
+
path.resolve(this.globalFieldsFolderPath, this.globalFieldConfig.fileName),
|
|
89
|
+
);
|
|
90
|
+
this.existingGlobalFields = fileHelper.readFileSync(this.globalFieldMapperFolderPath);
|
|
91
|
+
try {
|
|
92
|
+
addlogs(this.importConfig, 'Started to update pending global field with content type references', 'info');
|
|
93
|
+
await executeTask(this.pendingGlobalFields, this.updateGlobalFields.bind(this), {
|
|
94
|
+
concurrency: this.importConcurrency,
|
|
95
|
+
});
|
|
96
|
+
addlogs(this.importConfig, 'Updated pending global fields with content type with references', 'success');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
addlogs(
|
|
99
|
+
this.importConfig,
|
|
100
|
+
`Failed to updates global fields with content type reference ${formatError(error)}`,
|
|
101
|
+
'error',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
106
|
+
// write field rules
|
|
107
|
+
if (this.fieldRules.length > 0) {
|
|
108
|
+
try {
|
|
109
|
+
await fileHelper.writeFile(path.join(this.contentTypesFolderPath, 'field_rules_uid.json'), this.fieldRules);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
addlogs(this.importConfig, `Failed to write field rules ${formatError(error)}`, 'success');
|
|
112
|
+
}
|
|
69
113
|
}
|
|
70
|
-
}
|
|
71
114
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
// avoid re-creating content types that already exists in the stack
|
|
78
|
-
if (fs.existsSync(path.join(mapperFolderPath, 'success.json'))) {
|
|
79
|
-
self.createdContentTypeUids = helper.readFile(path.join(mapperFolderPath, 'success.json')) || [];
|
|
115
|
+
addlogs(this.importConfig, chalk.green('Content types imported successfully'), 'success');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
addlogs(this.importConfig, formatError(error), 'error');
|
|
118
|
+
throw new Error('Failed to import content types');
|
|
80
119
|
}
|
|
120
|
+
}
|
|
81
121
|
|
|
82
|
-
|
|
83
|
-
|
|
122
|
+
async seedContentType(contentType) {
|
|
123
|
+
const body = cloneDeep(schemaTemplate);
|
|
124
|
+
body.content_type.uid = contentType.uid;
|
|
125
|
+
body.content_type.title = contentType.title;
|
|
126
|
+
const requestObject = cloneDeep(this.requestOptions);
|
|
127
|
+
requestObject.json = body;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await this.stackAPIClient.contentType().create(requestObject.json);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error.error_code === 115 && (error.errors.uid || error.errors.title)) {
|
|
133
|
+
// content type uid already exists
|
|
134
|
+
// _.remove(self.contentTypes, { uid: uid });
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
84
138
|
}
|
|
139
|
+
}
|
|
85
140
|
|
|
86
|
-
|
|
87
|
-
|
|
141
|
+
async updateContentType(contentType) {
|
|
142
|
+
if (typeof contentType !== 'object') return;
|
|
143
|
+
const requestObject = cloneDeep(this.requestOptions);
|
|
144
|
+
if (contentType.field_rules) {
|
|
145
|
+
this.fieldRules.push(contentType.uid);
|
|
146
|
+
delete contentType.field_rules;
|
|
88
147
|
}
|
|
89
148
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return new Promise(function (resolve, reject) {
|
|
98
|
-
if (self.contentTypes === undefined || _.isEmpty(self.contentTypes)) {
|
|
99
|
-
addlogs(config, chalk.yellow('No Content types found'), 'success');
|
|
100
|
-
return resolve({ empty: true })
|
|
101
|
-
}
|
|
102
|
-
return Promise.map(
|
|
103
|
-
self.contentTypeUids,
|
|
104
|
-
function (contentTypeUid) {
|
|
105
|
-
return self
|
|
106
|
-
.seedContentTypes(contentTypeUid, self.uidToTitleMap[contentTypeUid])
|
|
107
|
-
.catch(reject);
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
// seed 3 content types at a time
|
|
111
|
-
concurrency: reqConcurrency,
|
|
112
|
-
},
|
|
113
|
-
)
|
|
114
|
-
.then(function () {
|
|
115
|
-
let batches = [];
|
|
116
|
-
let lenObj = self.contentTypes;
|
|
117
|
-
for (let i = 0; i < lenObj.length; i += Math.round(requestLimit / 3)) {
|
|
118
|
-
batches.push(lenObj.slice(i, i + Math.round(requestLimit / 3)));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return Promise.map(
|
|
122
|
-
batches,
|
|
123
|
-
async function (batch) {
|
|
124
|
-
return Promise.map(
|
|
125
|
-
batch,
|
|
126
|
-
async function (contentType) {
|
|
127
|
-
await self.updateContentTypes(contentType)
|
|
128
|
-
addlogs(config, contentType.uid + ' was updated successfully!', 'success');
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
concurrency: reqConcurrency,
|
|
132
|
-
},
|
|
133
|
-
).catch((e) => {
|
|
134
|
-
console.log('Something went wrong while migrating content type batch', e);
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
concurrency: reqConcurrency
|
|
139
|
-
}
|
|
140
|
-
).then(async function () {
|
|
141
|
-
// eslint-disable-next-line quotes
|
|
142
|
-
if (field_rules_ct.length > 0) {
|
|
143
|
-
await fsPromises.writeFile(
|
|
144
|
-
contentTypesFolderPath + '/field_rules_uid.json',
|
|
145
|
-
JSON.stringify(field_rules_ct),
|
|
146
|
-
);
|
|
147
|
-
}
|
|
149
|
+
supress(contentType.schema, this.importConfig.preserveStackVersion, this.installedExtensions);
|
|
150
|
+
requestObject.json.content_type = contentType;
|
|
151
|
+
const contentTypeResponse = this.stackAPIClient.contentType(contentType.uid);
|
|
152
|
+
Object.assign(contentTypeResponse, cloneDeep(contentType));
|
|
153
|
+
await contentTypeResponse.update();
|
|
154
|
+
addlogs(this.importConfig, contentType.uid + ' updated with references', 'success');
|
|
155
|
+
}
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
addlogs(config, chalk.green('Content types have been imported successfully!'), 'success');
|
|
162
|
-
return resolve();
|
|
163
|
-
}).catch((error) => {
|
|
164
|
-
return reject(error);
|
|
165
|
-
});
|
|
166
|
-
})
|
|
167
|
-
.catch((error) => {
|
|
168
|
-
return reject(error);
|
|
157
|
+
async updateGlobalFields({ uid }) {
|
|
158
|
+
const globalField = find(this.globalFields, { uid });
|
|
159
|
+
if (globalField) {
|
|
160
|
+
supress(globalField.schema, this.importConfig.preserveStackVersion, this.installedExtensions);
|
|
161
|
+
let globalFieldObj = this.stackAPIClient.globalField(globalField);
|
|
162
|
+
Object.assign(globalFieldObj, cloneDeep(globalField));
|
|
163
|
+
try {
|
|
164
|
+
const globalFieldResponse = await globalFieldObj.update();
|
|
165
|
+
const existingGlobalField = findIndex(this.existingGlobalFields, (existingGlobalFieldUId) => {
|
|
166
|
+
return globalFieldResponse.uid === existingGlobalFieldUId;
|
|
169
167
|
});
|
|
170
|
-
});
|
|
171
|
-
},
|
|
172
|
-
seedContentTypes: function (uid, title) {
|
|
173
|
-
let self = this;
|
|
174
|
-
return new Promise(function (resolve, reject) {
|
|
175
|
-
let body = _.cloneDeep(self.schemaTemplate);
|
|
176
|
-
body.content_type.uid = uid;
|
|
177
|
-
body.content_type.title = title;
|
|
178
|
-
let requestObject = _.cloneDeep(self.requestOptions);
|
|
179
|
-
requestObject.json = body;
|
|
180
168
|
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
.
|
|
184
|
-
|
|
185
|
-
.catch(function (err) {
|
|
186
|
-
let error = JSON.parse(err.message);
|
|
187
|
-
if (error.error_code === 115 && (error.errors.uid || error.errors.title)) {
|
|
188
|
-
// content type uid already exists
|
|
189
|
-
return resolve();
|
|
190
|
-
}
|
|
191
|
-
return reject(error);
|
|
169
|
+
// Improve write the updated global fields once all updates are completed
|
|
170
|
+
this.existingGlobalFields.splice(existingGlobalField, 1, globalField);
|
|
171
|
+
await fileHelper.writeFile(this.globalFieldMapperFolderPath, this.existingGlobalFields).catch((error) => {
|
|
172
|
+
addlogs(this.importConfig, `failed to write updated the global field ${uid} ${formatError(error)}`);
|
|
192
173
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
delete contentType.field_rules;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
supress(contentType.schema, config.preserveStackVersion, self.installedExtensions);
|
|
206
|
-
requestObject.json.content_type = contentType;
|
|
207
|
-
let contentTypeResponse = stack.contentType(contentType.uid);
|
|
208
|
-
Object.assign(contentTypeResponse, _.cloneDeep(contentType));
|
|
209
|
-
contentTypeResponse
|
|
210
|
-
.update()
|
|
211
|
-
.then((_updatedcontentType) => {
|
|
212
|
-
return resolve();
|
|
213
|
-
})
|
|
214
|
-
.catch((err) => {
|
|
215
|
-
addlogs(config, err, 'error');
|
|
216
|
-
return reject(err);
|
|
217
|
-
});
|
|
218
|
-
}, 1000);
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
updateGlobalfields: function () {
|
|
223
|
-
let self = this;
|
|
224
|
-
return new Promise(function (resolve, reject) {
|
|
225
|
-
// eslint-disable-next-line no-undef
|
|
226
|
-
return Promise.map(globalFieldPendingPath, async function (globalfield) {
|
|
227
|
-
let Obj = _.find(self.globalfields, { uid: globalfield });
|
|
228
|
-
|
|
229
|
-
supress(Obj.schema, config.preserveStackVersion, self.installedExtensions);
|
|
230
|
-
let globalFieldObj = stack.globalField(globalfield);
|
|
231
|
-
Object.assign(globalFieldObj, _.cloneDeep(Obj));
|
|
232
|
-
return globalFieldObj
|
|
233
|
-
.update()
|
|
234
|
-
.then((globalFieldResponse) => {
|
|
235
|
-
let updateObjpos = _.findIndex(globalFieldMapperFolderPath, function (successobj) {
|
|
236
|
-
let global_field_uid = globalFieldResponse.uid;
|
|
237
|
-
return global_field_uid === successobj;
|
|
238
|
-
});
|
|
239
|
-
globalFieldMapperFolderPath.splice(updateObjpos, 1, Obj);
|
|
240
|
-
helper.writeFile(globalFieldUpdateFile, globalFieldMapperFolderPath);
|
|
241
|
-
|
|
242
|
-
resolve(globalFieldResponse)
|
|
243
|
-
})
|
|
244
|
-
.catch(function (err) {
|
|
245
|
-
let error = JSON.parse(err.message);
|
|
246
|
-
// eslint-disable-next-line no-console
|
|
247
|
-
addlogs(config, chalk.red('Global Field failed to update ' + JSON.stringify(error.errors)), 'error');
|
|
248
|
-
})
|
|
249
|
-
.catch(function (error) {
|
|
250
|
-
// failed to update modified schemas back to their original form
|
|
251
|
-
return reject(error);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
},
|
|
174
|
+
addlogs(this.importConfig, `Updated the global field ${uid} with content type references `);
|
|
175
|
+
return true;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
addlogs(this.importConfig, `failed to update the global field ${uid} ${formatError(error)}`);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
addlogs(this.importConfig, `Global field ${uid} does not exist, and hence failed to update.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
256
183
|
|
|
257
|
-
mapUidToTitle
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
result[ct.uid] = ct.title;
|
|
184
|
+
async mapUidToTitle() {
|
|
185
|
+
this.contentTypes.forEach((ct) => {
|
|
186
|
+
this.titleToUIdMap.set(ct.uid, ct.title);
|
|
261
187
|
});
|
|
262
|
-
return result;
|
|
263
188
|
}
|
|
264
|
-
}
|
|
189
|
+
}
|
|
265
190
|
|
|
266
|
-
module.exports =
|
|
191
|
+
module.exports = ContentTypesImport;
|