@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,106 @@
1
+ 'use strict'
2
+
3
+ const {map: _map, getCallsite, constants, safePromise} = require('../utils')
4
+ const Listr = require('listr')
5
+ const {waterfall} = require('async')
6
+ const {requests} = constants
7
+
8
+ // Properties
9
+ const {getMapInstance, set, get} = _map
10
+
11
+ const ContentType = require('./content-types')
12
+
13
+ // Merge all classes containing migration methods into a single class
14
+ const _Migration = _Class => class extends _Class { }
15
+
16
+ /**
17
+ * Migration class
18
+ * @class Migration
19
+ */
20
+ class Migration extends _Migration(ContentType) {
21
+ /**
22
+ * Adds custom task in migration to execute.
23
+ * @param {Object} taskDescription Task title and task function to execute
24
+ * @param {string} taskDescription.title Title for custom task
25
+ * @param {array} taskDescription.task async function to be executed
26
+ * @param {string} taskDescription.failMessage message to be printed when task fails
27
+ * @param {string} taskDescription.successMessage message to be printed when task succeeds
28
+ * @example
29
+ *
30
+ * let first = 'binding glue'
31
+ * let second = 'second glue'
32
+ * let tasks = {
33
+ * title:'My First custom task',
34
+ * successMessage: 'Custom success message',
35
+ * failMessage: 'Custom fail message'
36
+ * task: async (params)=>{
37
+ * const {first, second} = params
38
+ * const a = await stackSDKInstance.fetch();
39
+ * },
40
+ * }
41
+ * migration.addTask(task)
42
+ */
43
+ addTask(taskDescription) {
44
+ const {title, failMessage, successMessage} = taskDescription
45
+ let {tasks, task} = taskDescription
46
+ const callsite = getCallsite()
47
+ const mapInstance = getMapInstance()
48
+ // eslint-disable-next-line no-warning-comments
49
+ // TODO: Make it better to accept only single task
50
+ if (tasks && !Array.isArray(tasks))
51
+ tasks = [tasks]
52
+ if (task && !Array.isArray(task)) {
53
+ tasks = [task]
54
+ }
55
+ this.contentTypeService.base.dispatch(callsite, null, null, tasks)
56
+ let _requests = get(requests, mapInstance)
57
+ const req = {
58
+ title: title,
59
+ failedTitle: failMessage || `Failed to execute task: ${title}`,
60
+ successTitle: successMessage || `Successfully executed task: ${title}`,
61
+ tasks,
62
+ }
63
+ _requests.push(req)
64
+ set(requests, mapInstance, _requests)
65
+ }
66
+
67
+ async run() {
68
+ const mapInstance = getMapInstance()
69
+ let _requests = get(requests, mapInstance)
70
+ // Make calls from here
71
+ const tasks = await this.getTasks(_requests)
72
+ const listr = new Listr(tasks)
73
+ await listr.run().catch(error => {
74
+ this.handleErrors(error)
75
+ // When the process is child, send error message to parent
76
+ if (process.send) process.send({errorOccurred: true})
77
+ })
78
+ }
79
+
80
+ async getTasks(requests) {
81
+ const _tasks = []
82
+ const results = []
83
+ for (let i = 0; i < requests.length; i++) {
84
+ let reqObj = requests[i]
85
+ const {title, failedTitle, successTitle, tasks} = reqObj
86
+ const task = {
87
+ title: title,
88
+ task: async (ctx, task) => {
89
+ const [err, result] = await safePromise(waterfall(tasks))
90
+ if (err) {
91
+ ctx.error = true
92
+ task.title = failedTitle
93
+ throw err
94
+ }
95
+ result && results.push(result)
96
+ task.title = successTitle
97
+ return result
98
+ },
99
+ }
100
+ _tasks.push(task)
101
+ }
102
+ return _tasks
103
+ }
104
+ }
105
+
106
+ module.exports = Migration
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ const Migration = require('./migration')
4
+
5
+ const {
6
+ CreateContentTypeValidator,
7
+ EditContentTypeValidator,
8
+ _TypeError,
9
+ FieldValidator,
10
+ } = require('../validators')
11
+ // eslint-disable-next-line no-warning-comments
12
+ // TODO: Need a better way to combine classes
13
+ const Base = require('./base')
14
+
15
+ const {ActionList} = require('../actions')
16
+ // Utils
17
+ const {map: _map, constants} = require('../utils')
18
+ // map properties
19
+ const {getMapInstance, get} = _map
20
+ // Constants
21
+ const {actionMapper, MANAGEMENT_SDK, MANAGEMENT_TOKEN, AUTH_TOKEN, API_KEY, BRANCH} = constants
22
+
23
+ class Parser {
24
+ async getMigrationParser(migrationFunc) {
25
+ const migration = new Migration()
26
+ const mapInstance = getMapInstance()
27
+ const parseResult = {}
28
+ const typeErrors = []
29
+ // migrations
30
+ try {
31
+ const stackSDKInstance = get(MANAGEMENT_SDK, mapInstance)
32
+ const managementToken = get(MANAGEMENT_TOKEN, mapInstance)
33
+ const authToken = get(AUTH_TOKEN, mapInstance)
34
+ const apiKey = get(API_KEY, mapInstance)
35
+ const branch = get(BRANCH, mapInstance)
36
+ await migrationFunc({migration, stackSDKInstance, managementToken, authToken, apiKey, branch})
37
+ } catch (error) {
38
+ if (error instanceof TypeError) {
39
+ if (error.message.includes('is not a function')) {
40
+ const base = new Base()
41
+ // eslint-disable-next-line
42
+ const [, filename, line] = error.stack.match(/\/([\/\w-_\.]+\.js):(\d*):(\d*)/);
43
+ const callsite = {
44
+ getFileName: () => `/${filename}`,
45
+ getLineNumber: () => line,
46
+ }
47
+ const errMsgString = error.message.split(' ')
48
+ const typeErrorFirstStr = errMsgString[0].split('.')
49
+ const typeErrorFunction = typeErrorFirstStr[typeErrorFirstStr.length - 1]
50
+ typeErrors.push(typeErrorFunction)
51
+ base.dispatch(callsite, null, {typeErrors}, 'typeError')
52
+ }
53
+ } else {
54
+ console.log(error)
55
+ // eslint-disable-next-line
56
+ const [, filename, line] = error.stack.match(/\/([\/\w-_\.]+\.js):(\d*):(\d*)/)
57
+ const callsite = {
58
+ getFileName: () => `/${filename}`,
59
+ getLineNumber: () => line,
60
+ }
61
+ const base = new Base()
62
+ let typeErrors = [error]
63
+ base.dispatch(callsite, null, {typeErrors}, 'typeError')
64
+ }
65
+ }
66
+ const actions = get(actionMapper, mapInstance)
67
+ const actionList = new ActionList(actions)
68
+
69
+ actionList.addValidators(new CreateContentTypeValidator())
70
+ actionList.addValidators(new FieldValidator())
71
+ actionList.addValidators(new _TypeError())
72
+ actionList.addValidators(new EditContentTypeValidator())
73
+
74
+ const hasErrors = actionList.validate()
75
+
76
+ if (hasErrors.length > 0) {
77
+ parseResult.hasErrors = hasErrors
78
+ return parseResult
79
+ }
80
+ return parseResult
81
+ }
82
+ }
83
+
84
+ module.exports = Parser
@@ -0,0 +1,323 @@
1
+ /* eslint-disable unicorn/explicit-length-check */
2
+ /* eslint-disable no-unused-expressions */
3
+ 'use strict'
4
+
5
+ const Base = require('../modules/base')
6
+ // Utils
7
+ const {map: _map, safePromise, successHandler, errorHandler, constants} = require('../utils')
8
+ // Map methods
9
+ const {get, getMapInstance, getDataWithAction} = _map
10
+ const mapInstance = getMapInstance()
11
+ const {ContentType, MANAGEMENT_SDK, actions: _actions} = constants
12
+
13
+ class ContentTypeService {
14
+ constructor() {
15
+ // Stores actions required for moveField function
16
+ this.moveFieldActions = []
17
+ this.base = new Base()
18
+ this.stackSDKInstance = get(MANAGEMENT_SDK, mapInstance)
19
+ }
20
+
21
+ async fetchContentType(callsite, id) {
22
+ const method = 'GET'
23
+
24
+ const [err, result] = await safePromise(this.stackSDKInstance.contentType(id).fetch())
25
+ if (err) {
26
+ errorHandler(id, ContentType, method, err)
27
+ this.base.dispatch(callsite, id, err, 'apiError')
28
+ throw err
29
+ }
30
+ successHandler(id, ContentType, method)
31
+
32
+ return result || {}
33
+ }
34
+
35
+ async postContentTypes(callsite, id, action) {
36
+ const data = getDataWithAction(id, mapInstance, action)
37
+ const [err, result] = await safePromise(this.stackSDKInstance.contentType().create(data))
38
+ if (err) {
39
+ errorHandler(id, ContentType, 'POST', err)
40
+ this.base.dispatch(callsite, id, err, 'apiError')
41
+ throw err
42
+ }
43
+
44
+ successHandler(id, ContentType, 'POST')
45
+ return result.content_type || {}
46
+ }
47
+
48
+ async editContentType(callsite, data) {
49
+ const d = getDataWithAction(data.uid, mapInstance, _actions.EDIT_CT)
50
+ data = {...data, ...d.content_type}
51
+ const method = 'PUT'
52
+ const [err, result] = await safePromise(data.update())
53
+ if (err) {
54
+ errorHandler(data.uid, ContentType, method, err)
55
+ this.base.dispatch(callsite, data.uid, err, 'apiError')
56
+ throw err
57
+ }
58
+
59
+ successHandler(data.uid, ContentType, method)
60
+ return result.content_type || {}
61
+ }
62
+
63
+ async deleteContentType(callsite) {
64
+ const {id} = this
65
+ const method = 'DELETE'
66
+ const [err, result] = await safePromise(this.stackSDKInstance.contentType(id).delete())
67
+
68
+ if (err) {
69
+ errorHandler(id, ContentType, method, err)
70
+ this.base.dispatch(callsite, id, err, 'apiError')
71
+ throw err
72
+ }
73
+ successHandler(id, ContentType, method)
74
+ return result.content_type || {}
75
+ }
76
+
77
+ applyActionsOnFields(callsite, data, cb) {
78
+ const {schema} = data
79
+ const {moveFieldActions, mergeEditSchema} = this
80
+ let i = 0
81
+ let finalSchema
82
+ try {
83
+ finalSchema = mergeEditSchema.call(this, schema)
84
+ } catch (error) {
85
+ this.base.dispatch(callsite, null, error, 'field')
86
+ // Call the callback with error
87
+ if (typeof cb === 'function') return cb(error)
88
+ }
89
+ data.schema = finalSchema
90
+ // Handle for no move field action required
91
+ if (!moveFieldActions.length) return cb(null, data)
92
+ // eslint-disable-next-line
93
+ while (true) {
94
+ /** VALIDATIONS */
95
+ const validResult = this.getValidated(finalSchema, moveFieldActions[i])
96
+ if (!validResult.isValid) {
97
+ const error = {message: `${validResult.missingField} does not exist in schema.`}
98
+ this.base.dispatch(callsite, null, error, 'field')
99
+ // Call the callback with error
100
+ if (typeof cb === 'function') return cb(error)
101
+ }
102
+
103
+ finalSchema = this[moveFieldActions[i].action](finalSchema, moveFieldActions[i])
104
+ i++
105
+ if (!moveFieldActions[i]) break
106
+ }
107
+ data.schema = finalSchema
108
+ if (cb) return cb(null, data)
109
+ return data
110
+ }
111
+
112
+ getActions(action) {
113
+ this.moveFieldActions.push(action)
114
+ }
115
+
116
+ // Sets id and action for this instance
117
+ setIdAndAction(id, action) {
118
+ this.id = id
119
+ this.action = action
120
+ }
121
+
122
+ // Merges the user specified with new fields with existing schema
123
+ mergeEditSchema(schema = []) {
124
+ const mapInstance = getMapInstance()
125
+
126
+ const {id, action} = this
127
+
128
+ const contentType = get(id, mapInstance)
129
+
130
+ let contentTypeSchema = contentType[action].content_type.schema
131
+ contentTypeSchema = contentTypeSchema || []
132
+
133
+ const indicesToRemoveFromNewSchema = []
134
+ const indicesToRemoveFromOldSchema = []
135
+
136
+ let isEditFieldValid = false
137
+ let isEditFieldPresent = false
138
+ let isDeleteFieldValid = false
139
+ let isDeleteFieldPresent = false
140
+ let fieldToRaiseExceptionAgainst
141
+
142
+ if (contentTypeSchema.length > 0 && schema.length > 0) {
143
+ // If found a updated field replace the new field with the existing field
144
+ contentTypeSchema.forEach((newSchema, i) => {
145
+ schema.every((oldSchema, j) => {
146
+ /** VALIDATIONS */
147
+ if (newSchema.isEdit) {
148
+ isEditFieldPresent = true
149
+ fieldToRaiseExceptionAgainst = newSchema.uid
150
+ newSchema.uid === oldSchema.uid && (isEditFieldValid = true)
151
+ }
152
+
153
+ if (newSchema.isDelete) {
154
+ isDeleteFieldPresent = true
155
+ fieldToRaiseExceptionAgainst = newSchema.uid
156
+
157
+ newSchema.uid === oldSchema.uid && (isDeleteFieldValid = true)
158
+ }
159
+ /** VALIDATIONS ENDS */
160
+
161
+ if (newSchema.uid === oldSchema.uid) {
162
+ let tempObj = newSchema
163
+ indicesToRemoveFromNewSchema.push(i)
164
+ // Handle delete action here
165
+ if (newSchema.isDelete) {
166
+ indicesToRemoveFromOldSchema.push(j)
167
+ } else {
168
+ schema.splice(j, 1, tempObj) // Replace the new schema with old schema
169
+ }
170
+ // break
171
+ return false
172
+ }
173
+ // continue
174
+ return true
175
+ })
176
+ })
177
+ }
178
+
179
+ // Raise exception if any of the following conditions are true
180
+ if ((isEditFieldPresent && !isEditFieldValid) || (isDeleteFieldPresent && !isDeleteFieldValid)) {
181
+ throw {message: `${fieldToRaiseExceptionAgainst} does not exist in the schema. Please check again`}
182
+ }
183
+
184
+ contentTypeSchema = contentTypeSchema.filter((_, i) =>
185
+ !indicesToRemoveFromNewSchema.includes(i))
186
+
187
+ schema = schema.filter((_, i) =>
188
+ !indicesToRemoveFromOldSchema.includes(i))
189
+
190
+ schema = schema.concat(contentTypeSchema)
191
+ contentType[action].content_type.schema = schema
192
+ return schema
193
+ }
194
+
195
+ toTheTop(schema, actionObj) {
196
+ const {fieldToMove} = actionObj
197
+ let i = 0
198
+ // eslint-disable-next-line
199
+ while (true) {
200
+ if (schema[i].uid === fieldToMove) {
201
+ let tempObj = schema[i]
202
+ schema.splice(i, 1)
203
+ schema.unshift(tempObj)
204
+ break
205
+ }
206
+ i++
207
+ if (!schema[i]) break // Error handling required
208
+ }
209
+ return schema
210
+ }
211
+
212
+ toTheBottom(schema, actionObj) {
213
+ const {fieldToMove} = actionObj
214
+
215
+ let i = 0
216
+ // eslint-disable-next-line
217
+ while (true) {
218
+ if (schema[i].uid === fieldToMove) {
219
+ let tempObj = schema[i]
220
+ schema.splice(i, 1)
221
+ schema.push(tempObj)
222
+ break
223
+ }
224
+ i++
225
+ if (!schema[i]) break
226
+ }
227
+ return schema
228
+ }
229
+
230
+ afterField(schema, actionObj) {
231
+ const {fieldToMove, against} = actionObj
232
+ let i = 0
233
+ let indexToMove = 0
234
+ let tempObj
235
+ let found = 0
236
+ // eslint-disable-next-line
237
+ while (true) {
238
+ if (schema[i].uid === against) {
239
+ indexToMove = i
240
+ found++
241
+ }
242
+ if (schema[i].uid === fieldToMove) {
243
+ tempObj = schema[i]
244
+ schema.splice(i, 1)
245
+ found++
246
+ }
247
+ i++
248
+ if (found === 2) break
249
+ if (!schema[i]) break
250
+ }
251
+ // TODO: Handle error
252
+ found === 2 && schema.splice(indexToMove + 1, null, tempObj)
253
+ return schema
254
+ }
255
+
256
+ beforeField(schema, actionObj) {
257
+ const {fieldToMove, against} = actionObj
258
+
259
+ let i = 0
260
+ let indexToMove = 0
261
+ let tempObj = 0
262
+ let found = 0
263
+ // eslint-disable-next-line
264
+ while (true) {
265
+ if (schema[i].uid === against) {
266
+ indexToMove = i
267
+ found++
268
+ }
269
+ if (schema[i].uid === fieldToMove) {
270
+ tempObj = schema[i]
271
+ schema.splice(i, 1)
272
+ found++
273
+ }
274
+ i++
275
+ if (found === 2) break
276
+ if (!schema[i]) break
277
+ }
278
+ found === 2 && schema.splice(indexToMove, null, tempObj)
279
+ return schema
280
+ }
281
+
282
+ getValidated(schema, actionObj) {
283
+ let isValid = true
284
+ let found = 0
285
+ let missingField = ''
286
+ let i = 0
287
+
288
+ const {fieldToMove, against} = actionObj
289
+ const uids = []
290
+ // eslint-disable-next-line
291
+ while (true) {
292
+
293
+ uids.push(schema[i].uid)
294
+
295
+ if (schema[i].uid === fieldToMove) {
296
+ found++
297
+ }
298
+ if (against === schema[i].uid) {
299
+ found++
300
+ }
301
+ i++
302
+ if (!schema[i]) break
303
+ }
304
+ // TODO: Need a better way to handle this
305
+ missingField = uids.includes(fieldToMove) ? null : fieldToMove
306
+
307
+ // against && (missingField = !uids.includes(against) ? against : null);
308
+ if (!missingField && against) {
309
+ missingField = uids.includes(against) ? null : against
310
+ }
311
+
312
+ // Handling both the scenarios
313
+ if (found === 0) {
314
+ isValid = false
315
+ } else if (against && found === 1) {
316
+ isValid = false
317
+ }
318
+
319
+ return {isValid, missingField}
320
+ }
321
+ }
322
+
323
+ module.exports = ContentTypeService
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ ContentTypeService: require('./content-types'),
5
+ LocaleService: require('./locales'),
6
+ }
@@ -0,0 +1,74 @@
1
+ 'use strict'
2
+
3
+ // Utils
4
+ const {safePromise, constants, map: _map} = require('../utils')
5
+ const {MANAGEMENT_SDK} = constants
6
+ const {get, getMapInstance} = _map
7
+ const mapInstance = getMapInstance()
8
+ this.stackSDKInstance = get(MANAGEMENT_SDK, mapInstance)
9
+
10
+ class LocaleService {
11
+ constructor() {
12
+ this.stackSDKInstance = get(MANAGEMENT_SDK, mapInstance)
13
+ }
14
+
15
+ async getLocale() {
16
+ const [err, result] = await safePromise(
17
+ this.stackSDKInstance.locale().query().find()
18
+ )
19
+ if (err) throw err
20
+ let orderedResult = this.getOrderedResult(result)
21
+ return orderedResult
22
+ }
23
+
24
+ getOrderedResult(result) {
25
+ if (result && result.items) {
26
+ const locales = result.items
27
+
28
+ let i = 0
29
+ let noEventTookPlace = 0 // counter which tracks if the list is sorted by fallback language
30
+ let len = locales.length
31
+
32
+ // Circular loop (Time complexity => Order of n^2, complexity of splice op is ignored)
33
+ do {
34
+ i = i % len + 1
35
+ noEventTookPlace++
36
+
37
+ let correctedI = i - 1
38
+
39
+ let a = locales[correctedI]
40
+
41
+ if (a.fallback_locale) {
42
+ let fallbackLangIndex = 0
43
+ let currentLangIndex = 0
44
+
45
+ for (let x = 0; x < len; x++) {
46
+ if (locales[x].code === a.code) {
47
+ currentLangIndex = x
48
+ }
49
+ if (locales[x].code === a.fallback_locale) {
50
+ fallbackLangIndex = x
51
+ }
52
+ }
53
+
54
+ // if index of fallback langauge is smaller no operation is required, it might be sorted
55
+ if (currentLangIndex > fallbackLangIndex) {
56
+ continue
57
+ }
58
+ let temp = a
59
+ // remove the object
60
+ locales.splice(correctedI, 1)
61
+ // add the object at fallbackLangIndex cus size of locales is decremented
62
+ locales.splice(fallbackLangIndex, 0, temp)
63
+ i--
64
+ noEventTookPlace--
65
+ }
66
+ } while (noEventTookPlace < len)
67
+
68
+ return locales
69
+ }
70
+ throw {message: 'Something went wrong.'}
71
+ }
72
+ }
73
+
74
+ module.exports = LocaleService
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ const {MAX_RETRY} = require('./constants')
4
+
5
+ const __safePromise = (promise, data) => {
6
+ return promise(data).then(res => [null, res]).catch(err => [err])
7
+ }
8
+
9
+ async function autoRetry(promise, retryCount = 0) {
10
+ /**
11
+ * Entries functions needs to pass params directly to http object,
12
+ * whereas for content types it fetches request params from global map object,
13
+ * thus the handling
14
+ */
15
+ let data
16
+ this && (data = this.data)
17
+
18
+ const [error, result] = await __safePromise(promise, data)
19
+
20
+ if (error) {
21
+ retryCount++
22
+ if (retryCount === MAX_RETRY) {
23
+ throw error
24
+ }
25
+ return await autoRetry(promise, retryCount)
26
+ }
27
+ return result
28
+ }
29
+
30
+ module.exports = autoRetry
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const getCallsites = require('callsites')
4
+ const {parse, resolve} = require('path')
5
+
6
+ function getFileDirectory(path) {
7
+ const parentPath = resolve(path, '../') // Assuming that will be 2 folders up
8
+ return parse(parentPath).dir
9
+ }
10
+
11
+ module.exports = () => {
12
+ const thisDir = getFileDirectory(__filename)
13
+ const callsites = getCallsites()
14
+
15
+ const externalFile = callsites.find(callsite => {
16
+ const currentDir = getFileDirectory(callsite.getFileName())
17
+ const isNotThisDir = thisDir !== currentDir
18
+ return isNotThisDir
19
+ })
20
+
21
+ return externalFile
22
+ }