@contentstack/cli-cm-export 1.0.0 → 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.
@@ -1,116 +1,87 @@
1
- /*!
2
- * Contentstack Export
3
- * Copyright (c) 2019 Contentstack LLC
4
- * MIT Licensed
5
- */
6
-
7
- const mkdirp = require('mkdirp');
8
1
  const path = require('path');
2
+ const fileHelper = require('../util/helper');
9
3
  const chalk = require('chalk');
10
- const Promise = require('bluebird');
11
-
12
- const helper = require('../util/helper');
13
- const stack = require('../util/contentstack-management-sdk');
4
+ const { executeTask, formatError } = require('../util');
14
5
  const { addlogs } = require('../util/log');
15
6
 
16
- let config = require('../../config/default');
17
- const contentTypeConfig = config.modules.content_types;
18
- const validKeys = contentTypeConfig.validKeys;
19
- let client;
20
- let contentTypesFolderPath;
21
-
22
- function ExportContentTypes() {
23
- this.content_types = [];
24
-
25
- this.requestOptions = {
26
- qs: {
7
+ class ContentTypesExport {
8
+ constructor(exportConfig, stackAPIClient) {
9
+ this.stackAPIClient = stackAPIClient;
10
+ this.exportConfig = exportConfig;
11
+ this.contentTypesConfig = exportConfig.modules.content_types;
12
+ this.qs = {
27
13
  include_count: true,
28
14
  asc: 'updated_at',
29
- limit: config.modules.content_types.limit,
15
+ limit: this.contentTypesConfig.limit,
30
16
  include_global_field_schema: true,
31
- },
32
- };
33
- }
17
+ };
18
+ // If content type id is provided then use it as part of query
19
+ if (Array.isArray(this.exportConfig.contentTypes) && this.exportConfig.length > 0) {
20
+ this.qs.uid = { $in: this.exportConfig.contentTypes };
21
+ }
22
+ this.contentTypesPath = path.resolve(
23
+ exportConfig.data,
24
+ exportConfig.branchName || '',
25
+ this.contentTypesConfig.dirName,
26
+ );
27
+ this.contentTypes = [];
28
+ this.fetchConcurrency = this.contentTypesConfig.fetchConcurrency || this.exportConfig.fetchConcurrency;
29
+ this.writeConcurrency = this.contentTypesConfig.writeConcurrency || this.exportConfig.writeConcurrency;
30
+ }
34
31
 
35
- ExportContentTypes.prototype = {
36
- start: function (credentialConfig) {
37
- this.content_types = [];
38
- let self = this;
39
- config = credentialConfig;
40
- contentTypesFolderPath = path.resolve(config.data, config.branchName || '', contentTypeConfig.dirName);
32
+ async start() {
33
+ try {
34
+ addlogs(this.exportConfig, 'Starting content type export', 'success');
35
+ await fileHelper.makeDirectory(this.contentTypesPath);
36
+ await this.getContentTypes();
37
+ await this.writeContentTypes(this.contentTypes);
38
+ addlogs(this.exportConfig, chalk.green('Content type(s) exported successfully'), 'success');
39
+ } catch (error) {
40
+ addlogs(this.exportConfig, chalk.red(`Failed to export content types ${formatError(error)}`), 'error');
41
+ throw new Error('Failed to export content types');
42
+ }
43
+ }
41
44
 
42
- client = stack.Client(config);
43
- // If content type id is provided then use it as part of query
44
- if (Array.isArray(config.contentTypes) && config.contentTypes.length > 0) {
45
- self.requestOptions.qs.uid = { $in: config.contentTypes };
45
+ async getContentTypes(skip = 0) {
46
+ if (skip) {
47
+ this.qs.skip = skip;
46
48
  }
47
- // Create folder for content types
48
- mkdirp.sync(contentTypesFolderPath);
49
- addlogs(config, 'Starting content type export', 'success');
50
- return new Promise(function (resolve, reject) {
51
- return self
52
- .getContentTypes()
53
- .then(function () {
54
- return self
55
- .writeContentTypes()
56
- .then(() => {
57
- return resolve();
58
- })
59
- .catch((error) => {
60
- return reject(error);
61
- });
62
- })
63
- .catch(reject);
64
- });
65
- },
66
- getContentTypes: function (skip) {
67
- let self = this;
68
- if (typeof skip !== 'number') {
69
- skip = 0;
70
- self.requestOptions.qs.skip = skip;
49
+
50
+ const contentTypeSearchResponse = await this.stackAPIClient.contentType().query(this.qs).find();
51
+ if (Array.isArray(contentTypeSearchResponse.items) && contentTypeSearchResponse.items.length > 0) {
52
+ let updatedContentTypes = this.sanitizeAttribs(contentTypeSearchResponse.items);
53
+ this.contentTypes.push(...updatedContentTypes);
54
+
55
+ skip += this.contentTypesConfig.limit;
56
+ if (skip > contentTypeSearchResponse.count) {
57
+ return;
58
+ }
59
+ return await this.getContentTypes(skip);
71
60
  } else {
72
- self.requestOptions.qs.skip = skip;
61
+ console.log('No content types returned for the given query');
73
62
  }
63
+ }
74
64
 
75
- return new Promise(function (resolve, reject) {
76
- client
77
- .stack({ api_key: config.source_stack, management_token: config.management_token })
78
- .contentType()
79
- .query(self.requestOptions.qs)
80
- .find()
81
- .then((contenttypeResponse) => {
82
- if (contenttypeResponse.items.length === 0) {
83
- addlogs(config, 'No content types were found in the Stack', 'success');
84
- return resolve();
85
- }
86
- contenttypeResponse.items.forEach(function (content_type) {
87
- for (let key in content_type) {
88
- if (validKeys.indexOf(key) === -1) {
89
- delete content_type[key];
90
- }
91
- }
92
- self.content_types.push(content_type);
93
- });
94
-
95
- skip += config.modules.content_types.limit;
96
- if (skip > contenttypeResponse.count) {
97
- return resolve();
98
- }
99
- return self.getContentTypes(skip).then(resolve).catch(reject);
100
- });
65
+ sanitizeAttribs(contentTypes) {
66
+ let updatedContentTypes = [];
67
+ contentTypes.forEach((contentType) => {
68
+ for (let key in contentType) {
69
+ if (this.contentTypesConfig.validKeys.indexOf(key) === -1) {
70
+ delete contentType[key];
71
+ }
72
+ }
73
+ updatedContentTypes.push(contentType);
101
74
  });
102
- },
103
- writeContentTypes: function () {
104
- let self = this;
105
- return new Promise(function (resolve) {
106
- helper.writeFile(path.join(contentTypesFolderPath, 'schema.json'), self.content_types);
107
- self.content_types.forEach(function (content_type) {
108
- helper.writeFile(path.join(contentTypesFolderPath, content_type.uid + '.json'), content_type);
109
- });
110
- addlogs(config, chalk.green('Content type(s) exported successfully'), 'success');
111
- return resolve();
112
- });
113
- },
114
- };
75
+ return updatedContentTypes;
76
+ }
77
+
78
+ async writeContentTypes(contentTypes) {
79
+ function write(contentType) {
80
+ return fileHelper.writeFile(path.join(this.contentTypesPath, contentType.uid + '.json'), contentType);
81
+ }
82
+ await executeTask(contentTypes, write.bind(this), { concurrency: this.writeConcurrency });
83
+ return fileHelper.writeFile(path.join(this.contentTypesPath, 'schema.json'), contentTypes);
84
+ }
85
+ }
115
86
 
116
- module.exports = new ExportContentTypes();
87
+ module.exports = ContentTypesExport;
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const chalk = require('chalk');
5
+ const mkdirp = require('mkdirp');
6
+ const { merge } = require('lodash');
7
+
8
+ const helper = require('../util/helper');
9
+ const { addlogs } = require('../util/log');
10
+ const { formatError } = require('../util');
11
+ const config = require('../../config/default');
12
+ const stack = require('../util/contentstack-management-sdk');
13
+
14
+ module.exports = class ExportCustomRoles {
15
+ roles = {};
16
+ client = null;
17
+ customRoles = {};
18
+ EXISTING_ROLES = {
19
+ Admin: 1,
20
+ Developer: 1,
21
+ 'Content Manager': 1,
22
+ };
23
+ rolesConfig = config.modules.customRoles;
24
+
25
+ constructor(credentialConfig) {
26
+ this.config = merge(config, credentialConfig);
27
+ this.client = stack.Client(this.config);
28
+ }
29
+
30
+ async start() {
31
+ try {
32
+ const self = this;
33
+ addlogs(this.config, 'Starting roles export', 'success');
34
+
35
+ const rolesFolderPath = path.resolve(this.config.data, this.config.branchName || '', this.rolesConfig.dirName);
36
+ mkdirp.sync(rolesFolderPath);
37
+
38
+ const roles = await this.client
39
+ .stack({ api_key: self.config.source_stack, management_token: self.config.management_token })
40
+ .role()
41
+ .fetchAll({ include_rules: true, include_permissions: true });
42
+
43
+ const customRoles = roles.items.filter((role) => !self.EXISTING_ROLES[role.name]);
44
+
45
+ if (!customRoles.length) {
46
+ addlogs(self.config, 'No custom roles were found in the Stack', 'success');
47
+ return;
48
+ }
49
+
50
+ await getCustomRolesLocales(
51
+ customRoles,
52
+ path.join(rolesFolderPath, self.rolesConfig.customRolesLocalesFileName),
53
+ this.client,
54
+ self.config,
55
+ );
56
+ self.customRoles = {};
57
+ customRoles.forEach((role) => {
58
+ addlogs(self.config, role.name + ' role was exported successfully', 'success');
59
+ self.customRoles[role.uid] = role;
60
+ });
61
+ helper.writeFileSync(path.join(rolesFolderPath, self.rolesConfig.fileName), self.customRoles);
62
+ addlogs(self.config, chalk.green('All the custom roles have been exported successfully'), 'success');
63
+ } catch (error) {
64
+ if (error.statusCode === 401) {
65
+ addlogs(
66
+ self.config,
67
+ chalk.red('You are not allowed to export roles, Unless you provide email and password in config'),
68
+ 'error',
69
+ );
70
+ return;
71
+ }
72
+ addlogs(self.config, 'Error occurred in exporting roles. ' + error && error.message, 'error');
73
+ addlogs(self.config, formatError(error), 'error');
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ async getCustomRolesLocales(customRoles, customRolesLocalesFilepath, client, config) {
79
+ const localesMap = {};
80
+ for (const role of customRoles) {
81
+ const rulesLocales = role.rules.find((rule) => rule.module === 'locale');
82
+ if (rulesLocales.locales && rulesLocales.locales.length) {
83
+ rulesLocales.locales.forEach((locale) => {
84
+ localesMap[locale] = 1;
85
+ });
86
+ }
87
+ }
88
+
89
+ if (Object.keys(localesMap).length) {
90
+ const locales = await client
91
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
92
+ .locale()
93
+ .query({})
94
+ .find();
95
+ const sourceLocalesMap = {};
96
+ for (const locale of locales.items) {
97
+ sourceLocalesMap[locale.uid] = locale;
98
+ }
99
+ for (const locale in localesMap) {
100
+ delete sourceLocalesMap[locale]['stackHeaders'];
101
+ localesMap[locale] = sourceLocalesMap[locale];
102
+ }
103
+ helper.writeFileSync(customRolesLocalesFilepath, localesMap);
104
+ }
105
+ }
106
+ };