@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.
@@ -1,251 +1,187 @@
1
- /* eslint-disable no-console */
2
- /*!
3
- * Contentstack Import
4
- * Copyright (c) 2019 Contentstack LLC
5
- * MIT Licensed
6
- */
7
- let mkdirp = require('mkdirp');
8
- let fs = require('fs');
9
- let fsPromises = require('fs').promises;
10
- let path = require('path');
11
- let _ = require('lodash');
12
- let Promise = require('bluebird');
13
- let chalk = require('chalk');
14
-
15
- let helper = require('../util/fs');
16
- let { addlogs } = require('../util/log');
17
- let supress = require('../util/extensionsUidReplace');
18
- let sdkInstance = require('../util/contentstack-management-sdk');
19
-
20
- let config = require('../../config/default');
21
- let reqConcurrency = config.concurrency;
22
- let requestLimit = config.rateLimit;
23
- let contentTypeConfig = config.modules.content_types;
24
- let globalFieldConfig = config.modules.globalfields;
25
- let globalFieldsFolderPath;
26
- let contentTypesFolderPath;
27
- let mapperFolderPath;
28
- let globalFieldMapperFolderPath;
29
- let globalFieldUpdateFile;
30
- let globalFieldPendingPath;
31
- let skipFiles = ['__master.json', '__priority.json', 'schema.json', '.DS_Store'];
32
- let fileNames;
33
- let field_rules_ct = [];
34
- let client;
35
- let stack = {};
36
-
37
- function importContentTypes() {
38
- this.contentTypes = [];
39
- this.schemaTemplate = require('../util/schemaTemplate');
40
- this.requestOptions = {
41
- json: {},
42
- };
43
- }
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
+ }
44
46
 
45
- importContentTypes.prototype = {
46
- start: function (credentialConfig) {
47
- addlogs(config, 'Migrating contenttypes', 'success');
48
- let self = this;
49
- config = credentialConfig;
50
- client = sdkInstance.Client(config);
51
- stack = client.stack({ api_key: config.target_stack, management_token: config.management_token });
52
- globalFieldsFolderPath = path.resolve(config.data, globalFieldConfig.dirName);
53
- contentTypesFolderPath = path.resolve(config.data, contentTypeConfig.dirName);
54
- mapperFolderPath = path.join(config.data, 'mapper', 'content_types');
55
- globalFieldMapperFolderPath = helper.readFile(path.join(config.data, 'mapper', 'global_fields', 'success.json'));
56
- globalFieldPendingPath = helper.readFile(
57
- path.join(config.data, 'mapper', 'global_fields', 'pending_global_fields.js'),
58
- );
59
- globalFieldUpdateFile = path.join(config.data, 'mapper', 'global_fields', 'success.json');
60
- fileNames = fs.readdirSync(path.join(contentTypesFolderPath));
61
- self.globalfields = helper.readFile(path.resolve(globalFieldsFolderPath, globalFieldConfig.fileName));
62
- for (let index in fileNames) {
63
- if (skipFiles.indexOf(fileNames[index]) === -1) {
64
- self.contentTypes.push(helper.readFile(path.join(contentTypesFolderPath, fileNames[index])));
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']));
65
54
  }
66
- }
67
55
 
68
- self.contentTypeUids = _.map(self.contentTypes, 'uid').filter(val => val);
69
- self.createdContentTypeUids = [];
70
- if (!fs.existsSync(mapperFolderPath)) {
71
- mkdirp.sync(mapperFolderPath);
72
- }
73
- // avoid re-creating content types that already exists in the stack
74
- if (fs.existsSync(path.join(mapperFolderPath, 'success.json'))) {
75
- self.createdContentTypeUids = helper.readFile(path.join(mapperFolderPath, 'success.json')) || [];
76
- }
77
- self.contentTypeUids = _.difference(self.contentTypeUids, self.createdContentTypeUids);
78
- self.uidToTitleMap = self.mapUidToTitle(self.contentTypes);
79
- // remove content types, already created
80
- _.remove(this.contentTypes, function (contentType) {
81
- return self.contentTypeUids.indexOf(contentType.uid) === -1;
82
- });
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');
83
72
 
84
- return new Promise(function (resolve, reject) {
85
- if (self.contentTypes === undefined || _.isEmpty(self.contentTypes)) {
86
- addlogs(config, chalk.yellow('No Content types found'), 'success');
87
- return resolve({ empty: true })
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);
88
78
  }
89
- return Promise.map(
90
- self.contentTypeUids,
91
- function (contentTypeUid) {
92
- return self
93
- .seedContentTypes(contentTypeUid, self.uidToTitleMap[contentTypeUid])
94
- .catch(reject);
95
- },
96
- {
97
- // seed 3 content types at a time
98
- concurrency: reqConcurrency,
99
- },
100
- )
101
- .then(function () {
102
- let batches = [];
103
- let lenObj = self.contentTypes;
104
- for (let i = 0; i < lenObj.length; i += Math.round(requestLimit / 3)) {
105
- batches.push(lenObj.slice(i, i + Math.round(requestLimit / 3)));
106
- }
107
79
 
108
- return Promise.map(
109
- batches,
110
- async function (batch) {
111
- return Promise.map(
112
- batch,
113
- async function (contentType) {
114
- await self.updateContentTypes(contentType)
115
- addlogs(config, contentType.uid + ' was updated successfully!', 'success');
116
- },
117
- {
118
- concurrency: reqConcurrency,
119
- },
120
- ).catch((e) => {
121
- console.log('Something went wrong while migrating content type batch', e);
122
- });
123
- },
124
- {
125
- concurrency: reqConcurrency
126
- }
127
- ).then(async function () {
128
- // eslint-disable-next-line quotes
129
- if (field_rules_ct.length > 0) {
130
- await fsPromises.writeFile(
131
- contentTypesFolderPath + '/field_rules_uid.json',
132
- JSON.stringify(field_rules_ct),
133
- );
134
- }
135
-
136
- if (globalFieldPendingPath && globalFieldPendingPath.length !== 0) {
137
- return self
138
- .updateGlobalfields()
139
- .then(function () {
140
- addlogs(config, chalk.green('Content types have been imported successfully!'), 'success');
141
- return resolve();
142
- })
143
- .catch((_error) => {
144
- addlogs(config, chalk.green('Error in GlobalFields'), 'success');
145
- return reject();
146
- });
147
- }
148
- addlogs(config, chalk.green('Content types have been imported successfully!'), 'success');
149
- return resolve();
150
- }).catch((error) => {
151
- return reject(error);
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,
152
95
  });
153
- })
154
- .catch((error) => {
155
- return reject(error);
156
- });
157
- });
158
- },
159
- seedContentTypes: function (uid, title) {
160
- let self = this;
161
- return new Promise(function (resolve, reject) {
162
- let body = _.cloneDeep(self.schemaTemplate);
163
- body.content_type.uid = uid;
164
- body.content_type.title = title;
165
- let requestObject = _.cloneDeep(self.requestOptions);
166
- requestObject.json = body;
167
-
168
- return stack
169
- .contentType()
170
- .create(requestObject.json)
171
- .then(resolve)
172
- .catch(function (err) {
173
- let error = JSON.parse(err.message);
174
- if (error.error_code === 115 && (error.errors.uid || error.errors.title)) {
175
- // content type uid already exists
176
- return resolve();
177
- }
178
- return reject(error);
179
- });
180
- });
181
- },
182
- updateContentTypes: function (contentType) {
183
- let self = this;
184
- return new Promise(function (resolve, reject) {
185
- setTimeout(function () {
186
- let requestObject = _.cloneDeep(self.requestOptions);
187
- if (contentType.field_rules) {
188
- field_rules_ct.push(contentType.uid);
189
- delete contentType.field_rules;
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
+ );
190
103
  }
191
- supress(contentType.schema);
192
- requestObject.json.content_type = contentType;
193
- let contentTypeResponse = stack.contentType(contentType.uid);
194
- Object.assign(contentTypeResponse, _.cloneDeep(contentType));
195
- contentTypeResponse
196
- .update()
197
- .then((_updatedcontentType) => {
198
- return resolve();
199
- })
200
- .catch((err) => {
201
- addlogs(config, err, 'error');
202
- return reject(err);
203
- });
204
- }, 1000);
205
- });
206
- },
207
-
208
- updateGlobalfields: function () {
209
- let self = this;
210
- return new Promise(function (resolve, reject) {
211
- // eslint-disable-next-line no-undef
212
- return Promise.map(globalFieldPendingPath, function (globalfield) {
213
- let Obj = _.find(self.globalfields, { uid: globalfield });
214
- supress(Obj.schema);
215
- let globalFieldObj = stack.globalField(globalfield);
216
- Object.assign(globalFieldObj, _.cloneDeep(Obj));
217
- return globalFieldObj
218
- .update()
219
- .then((globalFieldResponse) => {
220
- let updateObjpos = _.findIndex(globalFieldMapperFolderPath, function (successobj) {
221
- let global_field_uid = globalFieldResponse.uid;
222
- return global_field_uid === successobj;
223
- });
224
- globalFieldMapperFolderPath.splice(updateObjpos, 1, Obj);
225
- helper.writeFile(globalFieldUpdateFile, globalFieldMapperFolderPath);
226
-
227
- resolve(globalFieldResponse)
228
- })
229
- .catch(function (err) {
230
- let error = JSON.parse(err.message);
231
- // eslint-disable-next-line no-console
232
- addlogs(config, chalk.red('Global Field failed to update ' + JSON.stringify(error.errors)), 'error');
233
- })
234
- .catch(function (error) {
235
- // failed to update modified schemas back to their original form
236
- return reject(error);
237
- });
104
+ }
105
+
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
+ }
113
+ }
114
+
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');
119
+ }
120
+ }
121
+
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;
138
+ }
139
+ }
140
+
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;
147
+ }
148
+
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
+ }
156
+
157
+ async updateGlobalFields({ uid }) {
158
+ const globalField = find(this.globalFields, { uid });
159
+ supress(globalField.schema, this.importConfig.preserveStackVersion, this.installedExtensions);
160
+ let globalFieldObj = this.stackAPIClient.globalField(globalField);
161
+ Object.assign(globalFieldObj, cloneDeep(globalField));
162
+ try {
163
+ const globalFieldResponse = await globalFieldObj.update();
164
+ const existingGlobalField = findIndex(this.existingGlobalFields, (existingGlobalFieldUId) => {
165
+ return globalFieldResponse.uid === existingGlobalFieldUId;
238
166
  });
239
- });
240
- },
241
167
 
242
- mapUidToTitle: function (contentTypes) {
243
- let result = {};
244
- contentTypes.forEach((ct) => {
245
- result[ct.uid] = ct.title;
168
+ // Improve write the updated global fields once all updates are completed
169
+ this.existingGlobalFields.splice(existingGlobalField, 1, globalField);
170
+ await fileHelper.writeFile(this.globalFieldMapperFolderPath, this.existingGlobalFields).catch((error) => {
171
+ addlogs(this.importConfig, `failed to write updated the global field ${uid} ${formatError(error)}`);
172
+ });
173
+ addlogs(this.importConfig, `Updated the global field ${uid} with content type references `);
174
+ return true;
175
+ } catch (error) {
176
+ addlogs(this.importConfig, `failed to update the global field ${uid} ${formatError(error)}`);
177
+ }
178
+ }
179
+
180
+ async mapUidToTitle() {
181
+ this.contentTypes.forEach((ct) => {
182
+ this.titleToUIdMap.set(ct.uid, ct.title);
246
183
  });
247
- return result;
248
- },
249
- };
184
+ }
185
+ }
250
186
 
251
- module.exports = new importContentTypes();
187
+ module.exports = ContentTypesImport;
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ const mkdirp = require('mkdirp');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const chalk = require('chalk');
7
+ const { merge } = require('lodash');
8
+
9
+ const helper = require('../util/fs');
10
+ const { addlogs } = require('../util/log');
11
+ const { formatError } = require('../util');
12
+ let config = require('../../config/default');
13
+ const stack = require('../util/contentstack-management-sdk');
14
+
15
+ module.exports = class ImportCustomRoles {
16
+ fails = [];
17
+ client = null;
18
+ labelUids = [];
19
+ customRolesUidMapper = {};
20
+ customRolesConfig = config.modules.customRoles;
21
+
22
+ constructor(credentialConfig) {
23
+ this.config = merge(config, credentialConfig);
24
+ this.client = stack.Client(this.config);
25
+ }
26
+
27
+ async start() {
28
+ const self = this;
29
+ addlogs(this.config, chalk.white('Migrating custom-roles'), 'success');
30
+
31
+ let customRolesFolderPath = path.resolve(this.config.data, this.customRolesConfig.dirName);
32
+ let customRolesMapperPath = path.resolve(this.config.data, 'mapper', 'custom-roles');
33
+ let entriesUidMapperFolderPath = path.resolve(this.config.data, 'mapper', 'entries');
34
+ let customRolesFailsPath = path.resolve(this.config.data, 'custom-roles', 'fails.json');
35
+ let environmentsUidMapperFolderPath = path.resolve(this.config.data, 'mapper', 'environments');
36
+ let customRolesUidMapperPath = path.resolve(this.config.data, 'mapper', 'custom-roles', 'uid-mapping.json');
37
+ let customRolesLocalesFilePath = path.resolve(
38
+ customRolesFolderPath,
39
+ this.customRolesConfig.customRolesLocalesFileName,
40
+ );
41
+
42
+ try {
43
+ self.customRoles = helper.readFileSync(path.resolve(customRolesFolderPath, this.customRolesConfig.fileName));
44
+ self.customRolesLocales = helper.readFileSync(customRolesLocalesFilePath);
45
+ // Mapper file paths.
46
+
47
+ if (fs.existsSync(customRolesMapperPath)) {
48
+ this.customRolesUidMapper = helper.readFileSync(customRolesUidMapperPath) || {};
49
+ }
50
+
51
+ mkdirp.sync(customRolesMapperPath);
52
+
53
+ if (!self.customRoles) {
54
+ addlogs(self.config, chalk.white('No custom-roles found'), 'error');
55
+ return;
56
+ }
57
+ self.customRolesUids = Object.keys(self.customRoles);
58
+
59
+ self.localesUidMap = await getLocalesUidMap(self.client, self.config, self.customRolesLocales);
60
+
61
+ if (fs.existsSync(environmentsUidMapperFolderPath)) {
62
+ self.environmentsUidMap = helper.readFileSync(
63
+ path.resolve(environmentsUidMapperFolderPath, 'uid-mapping.json'),
64
+ );
65
+ }
66
+ if (fs.existsSync(entriesUidMapperFolderPath)) {
67
+ self.entriesUidMap = helper.readFileSync(path.resolve(entriesUidMapperFolderPath, 'uid-mapping.json'));
68
+ }
69
+
70
+ for (const uid of self.customRolesUids) {
71
+ const customRole = self.customRoles[uid];
72
+
73
+ if (uid in self.customRolesUidMapper) {
74
+ addlogs(
75
+ self.config,
76
+ chalk.white(`The custom-role ${customRole.name} already exists. Skipping it to avoid duplicates!`),
77
+ 'success',
78
+ );
79
+ continue;
80
+ }
81
+
82
+ try {
83
+ customRole.rules.forEach((rule) => {
84
+ const transformUids = getTransformUidsFactory(rule);
85
+ rule = transformUids(rule, self.environmentsUidMap, self.localesUidMap, self.entriesUidMap);
86
+ });
87
+ // rules.branch is required to create custom roles.
88
+ const branchRuleExists = customRole.rules.find((rule) => rule.module === 'branch');
89
+ if (!branchRuleExists) {
90
+ customRole.rules.push({
91
+ module: 'branch',
92
+ branches: ['main'],
93
+ acl: { read: true },
94
+ });
95
+ }
96
+ const role = await self.client
97
+ .stack({ api_key: self.config.target_stack, management_token: self.config.management_token })
98
+ .role()
99
+ .create({ role: customRole });
100
+
101
+ self.customRolesUidMapper[uid] = role;
102
+ helper.writeFileSync(customRolesUidMapperPath, self.customRolesUidMapper);
103
+ } catch (error) {
104
+ self.fails.push(customRole);
105
+
106
+ if (error && error.errors && error.errors.name) {
107
+ addlogs(self.config, chalk.red(`custom-role: ${customRole.name} already exists`), 'error');
108
+ } else {
109
+ addlogs(self.config, chalk.red(`custom-role: ${customRole.name} failed`), 'error');
110
+ }
111
+
112
+ addlogs(self.self.config, formatError(error), 'error');
113
+ }
114
+ }
115
+ addlogs(self.config, chalk.green('Custom-roles have been imported successfully!'), 'success');
116
+ } catch (error) {
117
+ helper.writeFileSync(customRolesFailsPath, self.fails);
118
+ addlogs(self.config, chalk.red('Custom-roles import failed'), 'error');
119
+ addlogs(self.config, formatError(error), 'error');
120
+
121
+ throw error;
122
+ }
123
+ }
124
+ };
125
+
126
+ const getTransformUidsFactory = (rule) => {
127
+ if (rule.module === 'environment') {
128
+ return environmentUidTransformer;
129
+ } else if (rule.module === 'locale') {
130
+ return localeUidTransformer;
131
+ } else if (rule.module === 'entry') {
132
+ return entryUidTransformer;
133
+ } else {
134
+ return noopTransformer;
135
+ }
136
+ };
137
+
138
+ const environmentUidTransformer = (rule, environmentsUidMap) => {
139
+ rule.environments = rule.environments.map((env) => environmentsUidMap[env]);
140
+ return rule;
141
+ };
142
+
143
+ const localeUidTransformer = (rule, environmentsUidMap, localesUidMap) => {
144
+ rule.locales = rule.locales.map((locale) => localesUidMap[locale]);
145
+ return rule;
146
+ };
147
+
148
+ const entryUidTransformer = (rule, environmentsUidMap, localesUidMap, entriesUidMap) => {
149
+ rule.entries = rule.entries.map((entry) => entriesUidMap[entry]);
150
+ return rule;
151
+ };
152
+
153
+ const noopTransformer = (rule) => {
154
+ return rule;
155
+ };
156
+
157
+ const getLocalesUidMap = async (client, config, sourceLocales) => {
158
+ const { items } = await client
159
+ .stack({ api_key: config.target_stack, management_token: config.management_token })
160
+ .locale()
161
+ .query()
162
+ .find();
163
+ const [targetLocalesMap, sourceLocalesMap] = [{}, {}];
164
+
165
+ items.forEach((locale) => {
166
+ targetLocalesMap[locale.code] = locale.uid;
167
+ });
168
+ for (const key in sourceLocales) {
169
+ sourceLocalesMap[sourceLocales[key].code] = key;
170
+ }
171
+ const localesUidMap = {};
172
+ for (const key in sourceLocalesMap) {
173
+ localesUidMap[sourceLocalesMap[key]] = targetLocalesMap[key];
174
+ }
175
+ return localesUidMap;
176
+ };