@contentstack/cli-migration 0.1.1-beta.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.
Files changed (47) hide show
  1. package/README.md +69 -0
  2. package/oclif.manifest.json +1 -0
  3. package/package.json +68 -0
  4. package/src/actions/action-list.js +32 -0
  5. package/src/actions/index.js +218 -0
  6. package/src/commands/cm/migration.js +182 -0
  7. package/src/config/api-config.js +19 -0
  8. package/src/config/default-options.js +7 -0
  9. package/src/config/index.js +7 -0
  10. package/src/config/master-locale.js +10 -0
  11. package/src/modules/base.js +95 -0
  12. package/src/modules/content-types.js +208 -0
  13. package/src/modules/fields.js +304 -0
  14. package/src/modules/index.js +8 -0
  15. package/src/modules/locale.js +33 -0
  16. package/src/modules/migration.js +106 -0
  17. package/src/modules/parser.js +84 -0
  18. package/src/services/content-types.js +323 -0
  19. package/src/services/index.js +6 -0
  20. package/src/services/locales.js +74 -0
  21. package/src/utils/auto-retry.js +30 -0
  22. package/src/utils/callsite.js +22 -0
  23. package/src/utils/constants.js +219 -0
  24. package/src/utils/contentstack-sdk.js +71 -0
  25. package/src/utils/error-handler.js +21 -0
  26. package/src/utils/error-helper.js +58 -0
  27. package/src/utils/fs-helper.js +14 -0
  28. package/src/utils/get-batches.js +7 -0
  29. package/src/utils/get-config.js +13 -0
  30. package/src/utils/group-by.js +38 -0
  31. package/src/utils/index.js +21 -0
  32. package/src/utils/logger.js +84 -0
  33. package/src/utils/map.js +40 -0
  34. package/src/utils/object-helper.js +9 -0
  35. package/src/utils/request.js +95 -0
  36. package/src/utils/safe-promise.js +3 -0
  37. package/src/utils/schema-helper.js +35 -0
  38. package/src/utils/success-handler.js +12 -0
  39. package/src/validators/api-error.js +18 -0
  40. package/src/validators/base-validator.js +39 -0
  41. package/src/validators/create-content-type-validator.js +58 -0
  42. package/src/validators/edit-content-type-validator.js +56 -0
  43. package/src/validators/field-validator.js +19 -0
  44. package/src/validators/index.js +11 -0
  45. package/src/validators/migration-error.js +18 -0
  46. package/src/validators/schema-validator.js +21 -0
  47. package/src/validators/type-error.js +21 -0
@@ -0,0 +1,95 @@
1
+ 'use strict'
2
+
3
+ // Utils
4
+ const {map: _map, constants} = require('../utils')
5
+ // Actions
6
+ const {actionCreators} = require('../actions')
7
+ // Utils properties
8
+ const {getMapInstance, get} = _map
9
+ const {actionMapper} = constants
10
+
11
+ /**
12
+ * Base class for module classes
13
+ * @class Base
14
+ * @ignore
15
+ */
16
+ class Base {
17
+ constructor(id, action) {
18
+ this.id = id
19
+ this.action = action
20
+ this.actions = []
21
+ }
22
+
23
+ /**
24
+ * Chained function which takes value for title
25
+ * @param {string} value Title
26
+ * @returns {Base} current instance of inherited class
27
+ */
28
+ title(value) {
29
+ const mapInstance = getMapInstance()
30
+ const {id, action} = this
31
+
32
+ const contentType = get(id, mapInstance)
33
+
34
+ contentType[action].content_type.title = value
35
+
36
+ return this
37
+ }
38
+
39
+ /**
40
+ * Chained function which takes value for description
41
+ * @param {string} value Description
42
+ * @returns {Base} current instance of inherited class
43
+ */
44
+ description(value) {
45
+ const mapInstance = getMapInstance()
46
+ const {id, action} = this
47
+ const contentType = get(id, mapInstance)
48
+ contentType[action].content_type.description = value
49
+ return this
50
+ }
51
+
52
+ /**
53
+ * Chained function takes boolean value for force while deleting content type
54
+ * @param {boolean} value Force delete
55
+ * @returns {Base} current instance of inherited class
56
+ */
57
+ force(value) {
58
+ const mapInstance = getMapInstance()
59
+ const {id, action} = this
60
+
61
+ const contentType = get(id, mapInstance)
62
+
63
+ contentType[action].content_type.force = value
64
+
65
+ return this
66
+ }
67
+
68
+ /**
69
+ * Accumulates actions for validating user provided inputs
70
+ * @ignore
71
+ * @param {Object} callsite Gets the file location and file number of caller
72
+ * @param {string} id unique id of action type
73
+ * @param {Object} opts holds payload to be validated
74
+ * @param {string} method type of action
75
+ */
76
+ dispatch(callsite, id, opts, method) {
77
+ if (!id && !opts) {
78
+ let mapInstance = getMapInstance()
79
+ let actions = get(actionMapper, mapInstance) // Returns an array if empty
80
+ let action = actionCreators.customTasks(callsite, opts)
81
+ actions.push(action)
82
+ } else {
83
+ let mapInstance = getMapInstance()
84
+ let actions = get(actionMapper, mapInstance) // Returns an array if empty
85
+ let action = actionCreators.contentType[method](callsite, id, {...opts, id})
86
+ actions.push(action)
87
+ }
88
+ }
89
+
90
+ getActions() {
91
+ return this.actions
92
+ }
93
+ }
94
+
95
+ module.exports = Base
@@ -0,0 +1,208 @@
1
+ /* eslint-disable camelcase */
2
+ 'use strict'
3
+
4
+ const Field = require('./fields')
5
+
6
+ // Services
7
+ const {ContentTypeService} = require('../services')
8
+
9
+ // Config
10
+ const {defaultOptions} = require('../config')
11
+
12
+ // Utils
13
+ const {map: _map, schemaHelper, constants, getCallsite} = require('../utils')
14
+
15
+ // Base class
16
+ const Base = require('./base')
17
+
18
+ // Properties
19
+ const {getMapInstance, set, get} = _map
20
+ const {actions, validationAction} = constants
21
+ const {getUid} = schemaHelper
22
+ const {create, edit} = validationAction
23
+
24
+ /**
25
+ * ContentType class
26
+ * @class ContentType
27
+ * @augments Base
28
+ */
29
+ class ContentType extends Base {
30
+ constructor() {
31
+ super()
32
+ this.contentTypeService = new ContentTypeService()
33
+ }
34
+
35
+ /**
36
+ * Creates content type by passing content type name and options
37
+ * @param {string} id Content type UID
38
+ * @param {Object} opts Optional: Content type fields definition
39
+ * @returns {Field} instance of Field
40
+ * @example
41
+ * module.exports = {migrations} => {
42
+ * const blog = migrations.createContentType('blog', {
43
+ * title: 'blog'
44
+ * })
45
+ * }
46
+ */
47
+ createContentType(id, opts = {}) {
48
+ const callsite = getCallsite()
49
+ // base class method
50
+ let options = {...defaultOptions, ...opts}
51
+ delete options.title
52
+ delete options.description
53
+ this.dispatch(callsite, id, opts, create)
54
+ const {title, description} = opts
55
+ const mapInstance = getMapInstance()
56
+
57
+ const {CREATE_CT} = actions
58
+ const uid = getUid(id)
59
+
60
+ const ctObj = {content_type: {title, uid, description, options}}
61
+
62
+ const ctActionObj = {[CREATE_CT]: ctObj}
63
+
64
+ const {contentTypeService} = this
65
+ // Sets data to post in map object
66
+ set(id, mapInstance, ctActionObj)
67
+ // Sets action and id in content type service
68
+ contentTypeService.setIdAndAction(id, CREATE_CT)
69
+ const tasks = [contentTypeService.postContentTypes.bind(contentTypeService, callsite, id, CREATE_CT)]
70
+ const req = {
71
+ title: `Adding content type: ${id}`,
72
+ failMessage: `Failed to create content type: ${id}`,
73
+ successMessage: `Successfully added content type: ${id}`,
74
+ tasks,
75
+ }
76
+ let field = new Field(id, CREATE_CT, contentTypeService, req)
77
+ // TODO: should find better way to attach content type level methods
78
+ field.singleton = this.singleton
79
+ field.isPage = this.isPage
80
+ return field
81
+ }
82
+
83
+ /**
84
+ * Set content type to singleton or multiple
85
+ * @param {boolean} value set value true to set content type as singleton default it is multiple
86
+ * @returns {ContentType} instance of ContentType for chaining
87
+ */
88
+ singleton(value) {
89
+ const mapInstance = getMapInstance()
90
+ const {id, action} = this
91
+ const contentType = get(id, mapInstance)
92
+
93
+ contentType[action].content_type.options.singleton = value
94
+ return this
95
+ }
96
+
97
+ /**
98
+ * Set content type to singleton or multiple
99
+ * @param {boolean} value set value false to set content type as content as block default true
100
+ * @returns {ContentType} instance of ContentType for chaining
101
+ */
102
+ isPage(value) {
103
+ const mapInstance = getMapInstance()
104
+ const {id, action} = this
105
+ const contentType = get(id, mapInstance)
106
+
107
+ contentType[action].content_type.options.is_page = value
108
+ return this
109
+ }
110
+
111
+ /**
112
+ * Edits content type by passing content type name and options
113
+ * @param {string} id Content type UID
114
+ * @param {Object} opts Optional: Content type fields definition
115
+ * @returns {Field} instance of Field
116
+ * @example
117
+ * module.exports = {migrations} => {
118
+ * const blog = migrations.editContentType('blog', {
119
+ * title: 'blog'
120
+ * });
121
+ * blog.description('Changed description');
122
+ * }
123
+ */
124
+ editContentType(id, opts = {}) {
125
+ let options = {...defaultOptions, ...opts}
126
+ delete options.title
127
+ delete options.description
128
+
129
+ const callsite = getCallsite()
130
+ // base class method
131
+ this.dispatch(callsite, id, {}, edit)
132
+ const {title, description} = opts
133
+ const mapInstance = getMapInstance()
134
+
135
+ const {EDIT_CT} = actions
136
+
137
+ const uid = id
138
+
139
+ const ctObj = {content_type: {title, uid, description, options}}
140
+ const ctActionObj = {[EDIT_CT]: ctObj}
141
+
142
+ const {contentTypeService} = this
143
+
144
+ // Sets data to update in map object
145
+ let ctAction = get(id, mapInstance)
146
+
147
+ set(id, mapInstance, {...ctActionObj, ...ctAction})
148
+ // Sets action and id in content type service
149
+ contentTypeService.setIdAndAction(id, EDIT_CT)
150
+ const tasks = [
151
+ contentTypeService.fetchContentType.bind(contentTypeService, callsite, id),
152
+ contentTypeService.applyActionsOnFields.bind(contentTypeService, callsite),
153
+ contentTypeService.editContentType.bind(contentTypeService, callsite),
154
+ ]
155
+
156
+ const req = {
157
+ title: `Editing content type: ${id}`,
158
+ failMessage: `Failed to edit content type: ${id}`,
159
+ successMessage: `Successfully updated content type: ${id}`,
160
+ tasks,
161
+ }
162
+
163
+ // Keeping the same instance of contentTypeService in Field class
164
+ let fieldI = new Field(id, EDIT_CT, contentTypeService, req)
165
+ // TODO: should find better way to attach content type level methods
166
+ fieldI.singleton = this.singleton
167
+ fieldI.isPage = this.isPage
168
+ return fieldI
169
+ }
170
+
171
+ /**
172
+ * Deletes content type by passing content type name
173
+ * @param {string} id Content type UID
174
+ * @returns {Field} instance of Field
175
+ * @example
176
+ * module.exports = {migrations} => {
177
+ * const blog = migrations.deleteContentType('blog');
178
+ * }
179
+ */
180
+ deleteContentType(id) {
181
+ const callsite = getCallsite()
182
+
183
+ const mapInstance = getMapInstance()
184
+
185
+ const {DELETE_CT} = actions
186
+
187
+ const uid = getUid(id)
188
+
189
+ const ctObj = {content_type: {uid, force: false}} // keep by default false
190
+
191
+ const ctActionObj = {[DELETE_CT]: ctObj}
192
+
193
+ const {contentTypeService} = this
194
+
195
+ // Sets data to delete in map object
196
+ set(id, mapInstance, ctActionObj)
197
+ // Sets action and id in content type service
198
+ contentTypeService.setIdAndAction(id, DELETE_CT)
199
+
200
+ const tasks = [contentTypeService.deleteContentType.bind(contentTypeService, callsite)]
201
+
202
+ const req = {title: 'Deleting content type', tasks}
203
+
204
+ return new Field(id, DELETE_CT, contentTypeService, req)
205
+ }
206
+ }
207
+
208
+ module.exports = ContentType
@@ -0,0 +1,304 @@
1
+ 'use strict'
2
+
3
+ const {keys} = Object
4
+ // Utils
5
+ const {map: _map, schemaHelper, constants} = require('../utils')
6
+
7
+ // Utils Properties
8
+ const {getMapInstance, get} = _map
9
+ const {getSchema} = schemaHelper
10
+ const {
11
+ data_type,
12
+ mandatory,
13
+ _default,
14
+ unique,
15
+ display_name,
16
+ field_metadata,
17
+ reference_to,
18
+ actions: _actions,
19
+ } = constants
20
+
21
+ // Base class
22
+ const Base = require('./base')
23
+
24
+ /**
25
+ * Field class
26
+ * @class Field
27
+ */
28
+ class Field extends Base {
29
+ // prop, value
30
+ constructor(uid, action, contentTypeService, request) {
31
+ super(uid)
32
+ this.uid = uid
33
+ this.action = action
34
+ this.contentTypeService = contentTypeService
35
+ this.request = request
36
+ }
37
+
38
+ /**
39
+ * @typedef {Object} Task
40
+ * @param {string} title - Title for custom task
41
+ * @param {function[]} task - array of async function to be executed
42
+ * @param {string} failMessage message to be printed when task fails
43
+ * @param {string} successMessage - message to be printed when task succeeds
44
+ */
45
+
46
+ /**
47
+ * Creates a field with provided uid.
48
+ * @param {string} field Field name to be created
49
+ * @param {Object} opts Options to be passed
50
+ * @returns {Field} current instance of field object to chain further methods.
51
+ * @example
52
+ * module.exports =({ migration })=> {
53
+ * const blog = migration.editContentType('blog');
54
+ *
55
+ * blog.createField('author');
56
+ * .display_name('Author')
57
+ * .data_type('text')
58
+ * .mandatory(false);
59
+ * };
60
+ */
61
+ createField(field, opts) {
62
+ this.updateContentTypeSchema(field)
63
+
64
+ // Build schema from options provided
65
+ if (opts && keys(opts).length) return this.getSchemaFromOptions(opts, field)
66
+ return this
67
+ }
68
+
69
+ /**
70
+ * Edits the field with provided uid.
71
+ * @param {string} field Field name to be edited
72
+ * @param {Object} opts Options to be passed
73
+ * @returns {Field} current instance of field object to chain further methods.
74
+ * @example
75
+ * module.exports =({ migration })=> {
76
+ * const blog = migration.editContentType('blog');
77
+ *
78
+ * blog.editField('uniqueid')
79
+ * .display_name('Unique ID')
80
+ * .mandatory(false);
81
+ * };
82
+ */
83
+ editField(field, opts) {
84
+ const {EDIT_FIELD} = _actions
85
+ this.updateContentTypeSchema(field, EDIT_FIELD)
86
+
87
+ // Build schema from options provided
88
+ if (opts && keys(opts).length) return this.getSchemaFromOptions(opts, field)
89
+ return this
90
+ }
91
+
92
+ /**
93
+ * Delete a field from the content type
94
+ * @param {string} field Field uid to be deleted
95
+ * @returns {Field} current instance of field object to chain further methods.
96
+ * @example
97
+ * module.exports =({ migration })=> {
98
+ * const blog = migration.editContentType('blog');
99
+ *
100
+ * blog.deleteField('uniqueid');
101
+ * };
102
+ */
103
+ deleteField(field) {
104
+ const {DELETE_FIELD} = _actions
105
+ this.updateContentTypeSchema(field, DELETE_FIELD)
106
+
107
+ return this
108
+ }
109
+
110
+ /**
111
+ * Move the field (position of the field in the editor)
112
+ * @param {string} field Field uid to be moved
113
+ * @returns {Field} current instance of field object to chain further methods.
114
+ * @example
115
+ * module.exports = ({migration}) => {
116
+ * const blog = migration.editContentType('blog');
117
+ *
118
+ * blog.createField('credits')
119
+ * .display_name('Credits')
120
+ * .data_type('text')
121
+ * .mandatory(false);
122
+ *
123
+ * blog.createField('references')
124
+ * .display_name('References')
125
+ * .data_type('text')
126
+ * .mandatory(false);
127
+ *
128
+ * blog.moveField('uniqueid').toTheBottom();
129
+ * blog.moveField('references').beforeField('credits');
130
+ * blog.moveField('author').toTheTop();
131
+ * blog.moveField('url').afterField('author');
132
+ * };
133
+ */
134
+ moveField(field) {
135
+ this.fieldToMove = field
136
+ return this
137
+ }
138
+
139
+ updateContentTypeSchema(field, subAction) {
140
+ const mapInstance = getMapInstance()
141
+
142
+ const {uid, action} = this
143
+
144
+ const contentType = get(uid, mapInstance)
145
+
146
+ let contentTypeSchema = contentType[action].content_type.schema
147
+ contentTypeSchema = contentTypeSchema || []
148
+
149
+ const schemaObj = getSchema(field, subAction)
150
+ contentTypeSchema.push(schemaObj)
151
+
152
+ contentType[action].content_type.schema = contentTypeSchema
153
+
154
+ this.field = schemaObj.uid
155
+ }
156
+
157
+ // changeFieldId(currentId, newId) { }
158
+
159
+ /**
160
+ *
161
+ * @param {string} value set display name for the field
162
+ * @returns {Field} current instance of field object to chain further methods.
163
+ */
164
+ display_name(value) {
165
+ this.buildSchema(display_name, this.field, value)
166
+ return this
167
+ }
168
+
169
+ /**
170
+ *
171
+ * @param {string} value Set data type of the field e.g. text, json, boolean
172
+ * @returns {Field} current instance of field object to chain further methods.
173
+ */
174
+ data_type(value) {
175
+ this.buildSchema(data_type, this.field, value)
176
+ return this
177
+ }
178
+
179
+ /**
180
+ *
181
+ * @param {boolean} value set true when field is mandatory
182
+ * @returns {Field} current instance of field object to chain further methods.
183
+ */
184
+ mandatory(value) {
185
+ this.buildSchema(mandatory, this.field, value)
186
+ return this
187
+ }
188
+
189
+ /**
190
+ *
191
+ * @param {string|boolean|number} value set true when field is mandatory
192
+ * @returns {Field} current instance of field object to chain further methods.
193
+ */
194
+ default(value) {
195
+ this.buildSchema(_default, this.field, value)
196
+ return this
197
+ }
198
+
199
+ /**
200
+ *
201
+ * @param {boolean} value set true if field is unique
202
+ * @returns {Field} current instance of field object to chain further methods.
203
+ */
204
+ unique(value) {
205
+ this.buildSchema(unique, this.field, value)
206
+ return this
207
+ }
208
+
209
+ /**
210
+ *
211
+ * @param {string | string[]} value uid of reference content type set array if ref_multipleContentType true
212
+ * @see {@link ref_multipleContentType}
213
+ * @returns {Field} current instance of field object to chain further methods.
214
+ */
215
+ reference_to(value) {
216
+ this.buildSchema(reference_to, this.field, value)
217
+ return this
218
+ }
219
+
220
+ /**
221
+ *
222
+ * @param {string} value set true if accepts multiple entries as reference
223
+ * @returns {Field} current instance of field object to chain further methods.
224
+ */
225
+ ref_multiple(value) {
226
+ this.buildSchema(field_metadata, this.field, {ref_multiple: value, ref_multiple_content_types: true})
227
+ return this
228
+ }
229
+
230
+ /**
231
+ *
232
+ * @param {boolean} value set true if refer to multiple content types
233
+ * @returns {Field} current instance of field object to chain further methods.
234
+ */
235
+ ref_multipleContentType(value) {
236
+ this.buildSchema(field_metadata, this.field, {ref_multiple_content_types: value})
237
+ return this
238
+ }
239
+
240
+ toTheBottom() {
241
+ const {fieldToMove, contentTypeService} = this
242
+
243
+ if (!fieldToMove) throw new Error('Cannot access this method directly.')
244
+
245
+ contentTypeService.getActions({action: 'toTheBottom', fieldToMove})
246
+ }
247
+
248
+ toTheTop() {
249
+ const {fieldToMove, contentTypeService} = this
250
+ if (!fieldToMove) throw new Error('Cannot access this method directly.')
251
+
252
+ contentTypeService.getActions({action: 'toTheTop', fieldToMove})
253
+ }
254
+
255
+ afterField(field) {
256
+ const {fieldToMove, contentTypeService} = this
257
+
258
+ if (!fieldToMove) throw new Error('Cannot access this method directly.')
259
+
260
+ contentTypeService.getActions({action: 'afterField', fieldToMove, against: field})
261
+ }
262
+
263
+ beforeField(field) {
264
+ const {fieldToMove, contentTypeService} = this
265
+
266
+ if (!fieldToMove) throw new Error('Cannot access this method directly.')
267
+
268
+ contentTypeService.getActions({action: 'beforeField', fieldToMove, against: field})
269
+ }
270
+
271
+ buildSchema(prop, field, value) {
272
+ const mapInstance = getMapInstance()
273
+
274
+ const {uid, action} = this
275
+
276
+ const contentType = get(uid, mapInstance)
277
+
278
+ for (const _schema of contentType[action].content_type.schema) {
279
+ if (_schema.uid === field) {
280
+ _schema[prop] = value
281
+ break
282
+ }
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Once you add the fields to content type you can call this method to get the task definition
288
+ * @returns {Task} This task definition is to pass to migration.addTask()
289
+ * @example
290
+ * migration.addTask(foo.getTaskDefinition())
291
+ */
292
+ getTaskDefinition() {
293
+ return this.request
294
+ }
295
+
296
+ getSchemaFromOptions(opts, field) {
297
+ const allKeys = keys(opts)
298
+ allKeys.forEach(_key => {
299
+ this.buildSchema(_key, field, opts[_key])
300
+ })
301
+ }
302
+ }
303
+
304
+ module.exports = Field
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ ContentType: require('./content-types'),
5
+ Field: require('./fields'),
6
+ Migration: require('./migration'),
7
+ Parser: require('./parser'),
8
+ }
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ // Service
4
+ const {LocaleService} = require('../services')
5
+
6
+ // Config
7
+ const {masterLocale} = require('../config')
8
+
9
+ // Utils
10
+ const {safePromise} = require('../utils')
11
+
12
+ class Locale {
13
+ constructor() {
14
+ this.localeService = new LocaleService()
15
+ }
16
+
17
+ async fetchLocales(callback) {
18
+ let {master_locale} = masterLocale
19
+
20
+ let {localeService} = this
21
+ let [err, result] = await safePromise(localeService.getLocale())
22
+
23
+ if (err) throw new Error(err)
24
+
25
+ // Use default code, if no result is found
26
+ result = result.length ? result : [master_locale]
27
+
28
+ if (callback) return callback(null, result)
29
+ return result
30
+ }
31
+ }
32
+
33
+ module.exports = Locale