@contentstack/cli-cm-import 1.0.1 → 1.2.0

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.
@@ -9,130 +9,170 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const Promise = require('bluebird');
11
11
  const chalk = require('chalk');
12
- const {isEmpty} = require('lodash');
12
+ const { isEmpty, merge } = require('lodash');
13
13
 
14
14
  const helper = require('../util/fs');
15
+ const { formatError } = require('../util');
15
16
  const { addlogs } = require('../util/log');
16
17
  let config = require('../../config/default');
17
18
  let stack = require('../util/contentstack-management-sdk');
18
19
 
19
- let reqConcurrency = config.concurrency;
20
- let workflowConfig = config.modules.workflows;
21
- let workflowFolderPath;
22
- let workflowMapperPath;
23
- let workflowUidMapperPath;
24
- let workflowSuccessPath;
25
- let workflowFailsPath;
26
- let client;
27
-
28
- function importWorkflows() {
29
- this.fails = [];
30
- this.success = [];
31
- this.workflowUidMapper = {};
32
- this.labelUids = [];
33
- if (fs.existsSync(workflowUidMapperPath)) {
34
- this.workflowUidMapper = helper.readFile(workflowUidMapperPath);
35
- this.workflowUidMapper = this.workflowUidMapper || {};
20
+ module.exports = class importWorkflows {
21
+ client;
22
+ fails = [];
23
+ success = [];
24
+ workflowUidMapper = {};
25
+ workflowConfig = config.modules.workflows;
26
+ reqConcurrency = config.concurrency || config.fetchConcurrency || 1;
27
+
28
+ constructor(credentialConfig) {
29
+ this.config = merge(config, credentialConfig);
30
+ this.client = stack.Client(this.config);
36
31
  }
37
- }
38
32
 
39
- importWorkflows.prototype = {
40
- start: function (credentialConfig) {
33
+ start() {
34
+ addlogs(this.config, chalk.white('Migrating workflows'), 'success');
35
+
41
36
  let self = this;
42
- config = credentialConfig;
43
- client = stack.Client(config);
44
- addlogs(config, chalk.white('Migrating workflows'), 'success');
45
- workflowFolderPath = path.resolve(config.data, workflowConfig.dirName);
46
- self.workflows = helper.readFile(path.resolve(workflowFolderPath, workflowConfig.fileName));
47
- workflowMapperPath = path.resolve(config.data, 'mapper', 'workflows');
48
- workflowUidMapperPath = path.resolve(config.data, 'mapper', 'workflows', 'uid-mapping.json');
49
- workflowSuccessPath = path.resolve(config.data, 'workflows', 'success.json');
50
- workflowFailsPath = path.resolve(config.data, 'workflows', 'fails.json');
37
+ let workflowMapperPath = path.resolve(this.config.data, 'mapper', 'workflows');
38
+ let workflowFailsPath = path.resolve(this.config.data, 'workflows', 'fails.json');
39
+ let workflowSuccessPath = path.resolve(this.config.data, 'workflows', 'success.json');
40
+ let workflowUidMapperPath = path.resolve(this.config.data, 'mapper', 'workflows', 'uid-mapping.json');
41
+ let workflowFolderPath = path.resolve(this.config.data, this.workflowConfig.dirName);
42
+
43
+ self.workflows = helper.readFileSync(path.resolve(workflowFolderPath, this.workflowConfig.fileName));
44
+
45
+ if (fs.existsSync(workflowUidMapperPath)) {
46
+ this.workflowUidMapper = helper.readFileSync(workflowUidMapperPath);
47
+ this.workflowUidMapper = this.workflowUidMapper || {};
48
+ }
49
+
51
50
  mkdirp.sync(workflowMapperPath);
51
+
52
52
  return new Promise(function (resolve, reject) {
53
53
  if (self.workflows == undefined || isEmpty(self.workflows)) {
54
- addlogs(config, chalk.white('No workflow Found'), 'success');
54
+ addlogs(self.config, chalk.white('No workflow Found'), 'success');
55
55
  return resolve({ empty: true });
56
56
  }
57
57
  self.workflowsUids = Object.keys(self.workflows);
58
58
  return Promise.map(
59
59
  self.workflowsUids,
60
- function (workflowUid) {
60
+ async function (workflowUid) {
61
61
  let workflow = self.workflows[workflowUid];
62
62
 
63
63
  if (!self.workflowUidMapper.hasOwnProperty(workflowUid)) {
64
- const workflowStages = workflow.workflow_stages
64
+ const roleNameMap = {};
65
+ const workflowStages = workflow.workflow_stages;
66
+ const roles = await self.client
67
+ .stack({ api_key: self.config.target_stack, management_token: self.config.management_token })
68
+ .role()
69
+ .fetchAll();
70
+
71
+ for (const role of roles.items) {
72
+ roleNameMap[role.name] = role.uid;
73
+ }
74
+
65
75
  for (const stage of workflowStages) {
66
- if (
67
- stage.SYS_ACL.users.uids.length > 0 &&
68
- stage.SYS_ACL.users.uids[0] !== '$all'
69
- ) {
76
+ if (stage.SYS_ACL.users.uids.length && stage.SYS_ACL.users.uids[0] !== '$all') {
70
77
  stage.SYS_ACL.users.uids = ['$all'];
71
78
  }
72
79
 
73
- if (stage.SYS_ACL.roles.uids.length > 0) {
74
- stage.SYS_ACL.roles.uids = [];
80
+ if (stage.SYS_ACL.roles.uids.length) {
81
+ try {
82
+ for (let i = 0; i < stage.SYS_ACL.roles.uids.length; i++) {
83
+ const roleData = stage.SYS_ACL.roles.uids[i];
84
+ if (!roleNameMap[roleData.name]) {
85
+ // rules.branch is required to create custom roles.
86
+ const branchRuleExists = roleData.rules.find((rule) => rule.module === 'branch');
87
+ if (!branchRuleExists) {
88
+ roleData.rules.push({
89
+ module: 'branch',
90
+ branches: ['main'],
91
+ acl: { read: true },
92
+ });
93
+ }
94
+
95
+ const role = await self.client
96
+ .stack({ api_key: self.config.target_stack, management_token: self.config.management_token })
97
+ .role()
98
+ .create({ role: roleData });
99
+ stage.SYS_ACL.roles.uids[i] = role.uid;
100
+ roleNameMap[roleData.name] = role.uid;
101
+ } else {
102
+ stage.SYS_ACL.roles.uids[i] = roleNameMap[roleData.name];
103
+ }
104
+ }
105
+ } catch (error) {
106
+ addlogs(
107
+ self.config,
108
+ chalk.red('Error while importing workflows roles. ' + formatError(error)),
109
+ 'error',
110
+ );
111
+ reject({ message: 'Error while importing workflows roles' });
112
+ }
75
113
  }
76
114
  }
77
115
 
78
116
  if (workflow.admin_users !== undefined) {
79
- addlogs(config, chalk.yellow('We are skipping import of `Workflow superuser(s)` from workflow'), 'info');
117
+ addlogs(
118
+ self.config,
119
+ chalk.yellow('We are skipping import of `Workflow superuser(s)` from workflow'),
120
+ 'info',
121
+ );
80
122
  delete workflow.admin_users;
81
123
  }
124
+ // One branch is required to create workflow.
125
+ if (!workflow.branches) {
126
+ workflow.branches = ['main'];
127
+ }
82
128
 
83
- let requestOption = {
84
- workflow,
85
- };
86
-
87
- return client
88
- .stack({ api_key: config.target_stack, management_token: config.management_token })
129
+ return self.client
130
+ .stack({ api_key: self.config.target_stack, management_token: self.config.management_token })
89
131
  .workflow()
90
- .create(requestOption)
132
+ .create({ workflow })
91
133
  .then(function (response) {
92
134
  self.workflowUidMapper[workflowUid] = response;
93
- helper.writeFile(workflowUidMapperPath, self.workflowUidMapper);
135
+ helper.writeFileSync(workflowUidMapperPath, self.workflowUidMapper);
94
136
  })
95
137
  .catch(function (error) {
96
138
  self.fails.push(workflow);
97
139
  if (error.errors.name) {
98
- addlogs(config, chalk.red("workflow: '" + workflow.name + "' already exist"), 'error');
140
+ addlogs(self.config, chalk.red("workflow: '" + workflow.name + "' already exist"), 'error');
99
141
  } else if (error.errors['workflow_stages.0.users']) {
100
142
  addlogs(
101
- config,
143
+ self.config,
102
144
  chalk.red(
103
145
  "Failed to import Workflows as you've specified certain roles in the Stage transition and access rules section. We currently don't import roles to the stack.",
104
146
  ),
105
147
  'error',
106
148
  );
107
149
  } else {
108
- addlogs(config, chalk.red("workflow: '" + workflow.name + "' failed"), 'error');
150
+ addlogs(self.config, chalk.red("workflow: '" + workflow.name + "' failed"), 'error');
109
151
  }
110
152
  });
111
153
  } else {
112
154
  // the workflow has already been created
113
155
  addlogs(
114
- config,
156
+ self.config,
115
157
  chalk.white("The Workflows: '" + workflow.name + "' already exists. Skipping it to avoid duplicates!"),
116
158
  'success',
117
159
  );
118
160
  }
119
161
  // import 1 workflows at a time
120
162
  },
121
- {
122
- concurrency: reqConcurrency,
123
- },
163
+ { concurrency: self.reqConcurrency },
124
164
  )
125
165
  .then(function () {
126
- helper.writeFile(workflowSuccessPath, self.success);
127
- addlogs(config, chalk.green('Workflows have been imported successfully!'), 'success');
128
- return resolve();
166
+ helper.writeFileSync(workflowSuccessPath, self.success);
167
+ addlogs(self.config, chalk.green('Workflows have been imported successfully!'), 'success');
168
+ resolve();
129
169
  })
130
170
  .catch(function (error) {
131
- helper.writeFile(workflowFailsPath, self.fails);
132
- addlogs(config, chalk.red('Workflows import failed'), 'error');
171
+ helper.writeFileSync(workflowFailsPath, self.fails);
172
+ addlogs(self.config, chalk.red('Workflows import failed'), 'error');
173
+ addlogs(self.config, formatError(error), 'error');
133
174
  return reject(error);
134
175
  });
135
176
  });
136
- },
177
+ }
137
178
  };
138
- module.exports = new importWorkflows();
@@ -6,23 +6,25 @@
6
6
 
7
7
  // eslint-disable-next-line unicorn/filename-case
8
8
  let path = require('path');
9
+ const _ = require('lodash');
9
10
  let helper = require('./fs');
10
11
  let util = require('../util');
11
12
  let config = util.getConfig();
12
13
  let extensionPath = path.resolve(config.data, 'mapper/extensions', 'uid-mapping.json');
13
14
  let globalfieldsPath = path.resolve(config.data, 'mapper/globalfields', 'uid-mapping.json');
15
+ const marketplaceAppPath = path.resolve(config.data, 'marketplace_apps', 'marketplace_apps.json');
14
16
 
15
17
  // eslint-disable-next-line camelcase
16
- let extension_uid_Replace = (module.exports = function (schema, preserveStackVersion) {
18
+ let extension_uid_Replace = (module.exports = function (schema, preserveStackVersion, installedExtensions) {
17
19
  for (let i in schema) {
18
20
  if (schema[i].data_type === 'group') {
19
- extension_uid_Replace(schema[i].schema, preserveStackVersion);
21
+ extension_uid_Replace(schema[i].schema, preserveStackVersion, installedExtensions);
20
22
  } else if (schema[i].data_type === 'blocks') {
21
23
  for (let block in schema[i].blocks) {
22
24
  if (schema[i].blocks[block].hasOwnProperty('reference_to')) {
23
25
  delete schema[i].blocks[block].schema;
24
26
  } else {
25
- extension_uid_Replace(schema[i].blocks[block].schema, preserveStackVersion);
27
+ extension_uid_Replace(schema[i].blocks[block].schema, preserveStackVersion, installedExtensions);
26
28
  }
27
29
  }
28
30
  } else if (
@@ -37,20 +39,33 @@ let extension_uid_Replace = (module.exports = function (schema, preserveStackVer
37
39
  }
38
40
  } else if (schema[i].data_type === 'global_field') {
39
41
  let global_fields_key_value = schema[i].reference_to;
40
- let global_fields_data = helper.readFile(path.join(globalfieldsPath));
42
+ let global_fields_data = helper.readFileSync(path.join(globalfieldsPath));
41
43
  if (global_fields_data && global_fields_data.hasOwnProperty(global_fields_key_value)) {
42
44
  schema[i].reference_to = global_fields_data[global_fields_key_value];
43
45
  }
44
46
  } else if (schema[i].hasOwnProperty('extension_uid')) {
45
47
  const extension_key_value = schema[i].extension_uid;
46
- const data = helper.readFile(path.join(extensionPath));
48
+ const data = helper.readFileSync(path.join(extensionPath));
47
49
  if (data && data.hasOwnProperty(extension_key_value)) {
48
50
  // eslint-disable-next-line camelcase
49
51
  schema[i].extension_uid = data[extension_key_value];
52
+ } else if (schema[i].field_metadata && schema[i].field_metadata.extension) {
53
+ if (installedExtensions) {
54
+ const marketplaceApps = helper.readFileSync(marketplaceAppPath);
55
+ const oldExt = _.find(marketplaceApps, { uid: schema[i].extension_uid });
56
+
57
+ if (oldExt) {
58
+ const ext = _.find(installedExtensions, { type: 'field', title: oldExt.title, app_uid: oldExt.app_uid });
59
+
60
+ if (ext) {
61
+ schema[i].extension_uid = ext.uid;
62
+ }
63
+ }
64
+ }
50
65
  }
51
66
  } else if (schema[i].data_type === 'json' && schema[i].hasOwnProperty('plugins') && schema[i].plugins.length > 0) {
52
67
  const newPluginUidsArray = [];
53
- const data = helper.readFile(path.join(extensionPath));
68
+ const data = helper.readFileSync(path.join(extensionPath));
54
69
  schema[i].plugins.forEach((extension_key_value) => {
55
70
  if (data && data.hasOwnProperty(extension_key_value)) {
56
71
  newPluginUidsArray.push(data[extension_key_value]);
@@ -59,4 +74,4 @@ let extension_uid_Replace = (module.exports = function (schema, preserveStackVer
59
74
  schema[i].plugins = newPluginUidsArray;
60
75
  }
61
76
  }
62
- })
77
+ });
@@ -7,33 +7,112 @@
7
7
  var fs = require('fs');
8
8
  var path = require('path');
9
9
  var mkdirp = require('mkdirp');
10
+ const bigJSON = require('big-json');
10
11
 
11
- exports.readFile = function (filePath, parse) {
12
- var data;
12
+ exports.readFileSync = function (filePath, parse) {
13
+ let data;
13
14
  parse = typeof parse === 'undefined' ? true : parse;
14
15
  filePath = path.resolve(filePath);
15
16
  if (fs.existsSync(filePath)) {
16
- data = parse ? JSON.parse(fs.readFileSync(filePath, 'utf-8')) : data;
17
+ try {
18
+ data = parse ? JSON.parse(fs.readFileSync(filePath, 'utf-8')) : data;
19
+ } catch (error) {
20
+ return data;
21
+ }
17
22
  }
18
23
  return data;
19
24
  };
20
25
 
21
- exports.writeFile = function (filePath, data) {
26
+ // by default file type is json
27
+ exports.readFile = async (filePath, options = { type: 'json' }) => {
28
+ return new Promise((resolve, reject) => {
29
+ filePath = path.resolve(filePath);
30
+ fs.readFile(filePath, 'utf-8', (error, data) => {
31
+ if (error) {
32
+ if (error.code === 'ENOENT') {
33
+ return resolve();
34
+ }
35
+ reject(error);
36
+ } else {
37
+ if (options.type !== 'json') {
38
+ return resolve(data);
39
+ }
40
+ resolve(JSON.parse(data));
41
+ }
42
+ });
43
+ });
44
+ };
45
+
46
+ exports.writeFileSync = function (filePath, data) {
22
47
  data = typeof data === 'object' ? JSON.stringify(data) : data || '{}';
23
48
  fs.writeFileSync(filePath, data);
24
49
  };
25
50
 
26
- exports.makeDirectory = function () {
27
- for (var key in arguments) {
28
- var dirname = path.resolve(arguments[key]);
29
- if (!fs.existsSync(dirname)) {
30
- mkdirp.sync(dirname);
31
- }
51
+ exports.writeFile = function (filePath, data) {
52
+ return new Promise((resolve, reject) => {
53
+ data = typeof data === 'object' ? JSON.stringify(data) : data || '{}';
54
+ fs.writeFile(filePath, data, (error) => {
55
+ if (error) {
56
+ return reject(error);
57
+ }
58
+ resolve('done');
59
+ });
60
+ });
61
+ };
62
+
63
+ exports.makeDirectory = async function (path) {
64
+ if (!path) {
65
+ throw new Error('Invalid path to create directory');
66
+ }
67
+ return mkdirp(path);
68
+ };
69
+
70
+ exports.readLargeFile = function (filePath, opts = {}) {
71
+ if (typeof filePath !== 'string') {
72
+ return;
73
+ }
74
+ filePath = path.resolve(filePath);
75
+ if (fs.existsSync(filePath)) {
76
+ return new Promise((resolve, reject) => {
77
+ const readStream = fs.createReadStream(filePath, { encoding: 'utf-8' });
78
+ const parseStream = bigJSON.createParseStream();
79
+ parseStream.on('data', function (data) {
80
+ if (opts.type === 'array') {
81
+ return resolve(Object.values(data));
82
+ }
83
+ resolve(data);
84
+ });
85
+ parseStream.on('error', function (error) {
86
+ console.log('error', error);
87
+ reject(error);
88
+ });
89
+ readStream.pipe(parseStream);
90
+ });
32
91
  }
33
92
  };
34
93
 
35
- exports.readdir = function (dirPath) {
36
- if (fs.existsSync(path)) {
94
+ exports.writeLargeFile = function (filePath, data) {
95
+ if (typeof filePath !== 'string' || typeof data !== 'object') {
96
+ return;
97
+ }
98
+ filePath = path.resolve(filePath);
99
+ return new Promise((resolve, reject) => {
100
+ const stringifyStream = bigJSON.createStringifyStream({
101
+ body: data,
102
+ });
103
+ var writeStream = fs.createWriteStream(filePath, 'utf-8');
104
+ stringifyStream.pipe(writeStream);
105
+ writeStream.on('finish', () => {
106
+ resolve();
107
+ });
108
+ writeStream.on('error', (error) => {
109
+ reject(error);
110
+ });
111
+ });
112
+ };
113
+
114
+ exports.readdirSync = function (dirPath) {
115
+ if (fs.existsSync(dirPath)) {
37
116
  return fs.readdirSync(dirPath);
38
117
  } else {
39
118
  return [];
@@ -13,6 +13,7 @@ var chalk = require('chalk');
13
13
  var { addlogs } = require('./log');
14
14
  var defaultConfig = require('../../config/default');
15
15
  const stack = require('./contentstack-management-sdk');
16
+ const promiseLimit = require('promise-limit');
16
17
  var config;
17
18
 
18
19
  exports.initialization = function (configData) {
@@ -69,7 +70,7 @@ exports.sanitizeStack = function (importConfig) {
69
70
  importConfig.modules.stack.fileName,
70
71
  );
71
72
 
72
- const oldStackDetails = fs.readFile(stackFilePath);
73
+ const oldStackDetails = fs.readFileSync(stackFilePath);
73
74
  if (!oldStackDetails || !oldStackDetails.settings || !oldStackDetails.settings.hasOwnProperty('version')) {
74
75
  throw new Error(`${JSON.stringify(oldStackDetails)} is invalid!`);
75
76
  }
@@ -129,7 +130,7 @@ exports.field_rules_update = function (importConfig, ctPath) {
129
130
  return new Promise(function (resolve, reject) {
130
131
  let client = stack.Client(importConfig);
131
132
 
132
- fs.readFile(path.join(ctPath + '/field_rules_uid.json'), async (err, data) => {
133
+ fs.readFileSync(path.join(ctPath + '/field_rules_uid.json'), async (err, data) => {
133
134
  if (err) {
134
135
  throw err;
135
136
  }
@@ -152,7 +153,7 @@ exports.field_rules_update = function (importConfig, ctPath) {
152
153
  let updatedValue = [];
153
154
  for (let j = 0; j < fieldRulesArray.length; j++) {
154
155
  let splitedFieldRulesValue = fieldRulesArray[j];
155
- let oldUid = helper.readFile(path.join(entryUidMapperPath));
156
+ let oldUid = helper.readFileSync(path.join(entryUidMapperPath));
156
157
  if (oldUid.hasOwnProperty(splitedFieldRulesValue)) {
157
158
  updatedValue.push(oldUid[splitedFieldRulesValue]);
158
159
  } else {
@@ -185,3 +186,38 @@ exports.field_rules_update = function (importConfig, ctPath) {
185
186
  exports.getConfig = function () {
186
187
  return config;
187
188
  };
189
+
190
+ exports.formatError = function (error) {
191
+ try {
192
+ if (typeof error === 'string') {
193
+ error = JSON.parse(error);
194
+ } else {
195
+ error = JSON.parse(error.message);
196
+ }
197
+ } catch (e) {}
198
+ let message = error.errorMessage || error.error_message || error.message || error;
199
+ if (error.errors && Object.keys(error.errors).length > 0) {
200
+ Object.keys(error.errors).forEach((e) => {
201
+ let entity = e;
202
+ if (e === 'authorization') entity = 'Management Token';
203
+ if (e === 'api_key') entity = 'Stack API key';
204
+ if (e === 'uid') entity = 'Content Type';
205
+ if (e === 'access_token') entity = 'Delivery Token';
206
+ message += ' ' + [entity, error.errors[e]].join(' ');
207
+ });
208
+ }
209
+ return message;
210
+ };
211
+
212
+ exports.executeTask = function (tasks = [], handler, options) {
213
+ if (typeof handler !== 'function') {
214
+ throw new Error('Invalid handler');
215
+ }
216
+ const { concurrency = 1 } = options;
217
+ const limit = promiseLimit(concurrency);
218
+ return Promise.all(
219
+ tasks.map((task) => {
220
+ return limit(() => handler(task));
221
+ }),
222
+ );
223
+ };
@@ -9,11 +9,9 @@ var path = require('path');
9
9
  var mkdirp = require('mkdirp');
10
10
  var slice = Array.prototype.slice;
11
11
 
12
-
13
-
14
12
  const ansiRegexPattern = [
15
13
  '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
16
- '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
14
+ '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
17
15
  ].join('|');
18
16
 
19
17
  function returnString(args) {
@@ -22,14 +20,18 @@ function returnString(args) {
22
20
  returnStr = args
23
21
  .map(function (item) {
24
22
  if (item && typeof item === 'object') {
25
- return JSON.stringify(item).replace(/authtoken\":\"blt................/g, 'authtoken":"blt....');
23
+ try {
24
+ return JSON.stringify(item).replace(/authtoken\":\"blt................/g, 'authtoken":"blt....');
25
+ } catch (error) {
26
+ return item.message;
27
+ }
26
28
  }
27
29
  return item;
28
30
  })
29
31
  .join(' ')
30
32
  .trim();
31
33
  }
32
- returnStr = returnStr.replace(new RegExp(ansiRegexPattern, 'g'), "").trim();
34
+ returnStr = returnStr.replace(new RegExp(ansiRegexPattern, 'g'), '').trim();
33
35
  return returnStr;
34
36
  }
35
37
 
@@ -38,7 +38,8 @@ module.exports = function (config) {
38
38
  client
39
39
  .stack({ api_key: config.target_stack, management_token: config.management_token })
40
40
  .fetch()
41
- .then(function () {
41
+ .then(function (stack) {
42
+ config.destinationStackName = stack.name
42
43
  return resolve();
43
44
  })
44
45
  .catch((error) => {