adapt-migrations 1.0.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.
- package/.eslintignore +1 -0
- package/.eslintrc.json +14 -0
- package/README.md +43 -0
- package/api/commands.js +33 -0
- package/api/data.js +26 -0
- package/api/describe.js +12 -0
- package/api/errors.js +28 -0
- package/api/plugins.js +44 -0
- package/api/tests.js +57 -0
- package/api/where.js +46 -0
- package/examples/migrations.js +159 -0
- package/examples/script.js +78 -0
- package/index.js +62 -0
- package/lib/CacheManager.js +95 -0
- package/lib/Journal.js +303 -0
- package/lib/Logger.js +91 -0
- package/lib/Task.js +381 -0
- package/lib/TaskContext.js +27 -0
- package/lib/TaskTest.js +19 -0
- package/lib/lifecycle.js +50 -0
- package/package.json +18 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import globs from 'globs'
|
|
4
|
+
import fs from 'fs-extra'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
import Logger from './Logger.js'
|
|
7
|
+
|
|
8
|
+
export const ONE_MINUTE = 60 * 1000
|
|
9
|
+
export const ONE_HOUR = 60 * ONE_MINUTE
|
|
10
|
+
export const ONE_WEEK = 7 * 24 * ONE_HOUR
|
|
11
|
+
|
|
12
|
+
const logger = Logger.getInstance();
|
|
13
|
+
|
|
14
|
+
export default class CacheManager {
|
|
15
|
+
constructor (maxAge = ONE_WEEK) {
|
|
16
|
+
this.maxAge = maxAge
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static hash (path) {
|
|
20
|
+
return crypto
|
|
21
|
+
.createHash('sha1')
|
|
22
|
+
.update(path, 'utf8')
|
|
23
|
+
.digest('hex')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getTempPath () {
|
|
27
|
+
const osTempPath = await fs.promises.realpath(path.resolve(os.tmpdir()))
|
|
28
|
+
const tempPath = path.posix.join(osTempPath, 'migrations')
|
|
29
|
+
await fs.ensureDir(tempPath)
|
|
30
|
+
return tempPath
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getCachePath ({ basePath = process.cwd(), outputPath, tempPath }) {
|
|
34
|
+
const projectHash = CacheManager.hash(path.join(basePath, outputPath))
|
|
35
|
+
if (tempPath) await fs.ensureDir(tempPath)
|
|
36
|
+
const cachePath = path.join(tempPath ?? await this.getTempPath(), `${projectHash}.cache`)
|
|
37
|
+
return cachePath
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getCheckFilePath () {
|
|
41
|
+
const checkFilePath = path.join(await this.getTempPath(), 'last.touch')
|
|
42
|
+
return checkFilePath
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async isCleaningTime () {
|
|
46
|
+
// By default, clean once a day, or with a floor of one hourly intervals
|
|
47
|
+
const checkInterval = Math.max(this.maxAge / 7, ONE_HOUR)
|
|
48
|
+
const checkFilePath = await this.getCheckFilePath()
|
|
49
|
+
// Check if checkFile is older than the cleaning interval
|
|
50
|
+
return (!fs.existsSync(checkFilePath) || Date.now() - (await fs.stat(checkFilePath)).mtime >= checkInterval)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async clean () {
|
|
54
|
+
if (!await this.isCleaningTime()) return
|
|
55
|
+
logger.debug('CacheManager -- Clean running')
|
|
56
|
+
const checkFilePath = await this.getCheckFilePath()
|
|
57
|
+
// Touch checkFile
|
|
58
|
+
await fs.writeFile(checkFilePath, String(Date.now()))
|
|
59
|
+
logger.debug('CacheManager -- Clearing compilation caches')
|
|
60
|
+
const tempPath = await this.getTempPath()
|
|
61
|
+
// Fetch all cache files except checkFile
|
|
62
|
+
const files = await new Promise((resolve, reject) => globs([
|
|
63
|
+
`${tempPath}/**`,
|
|
64
|
+
`!${checkFilePath}`
|
|
65
|
+
], {
|
|
66
|
+
nodir: true
|
|
67
|
+
}, (err, files) => err ? reject(err) : resolve(files)))
|
|
68
|
+
// Fetch file ages
|
|
69
|
+
const fileAges = []
|
|
70
|
+
const now = Date.now()
|
|
71
|
+
for (const index in files) {
|
|
72
|
+
const file = files[index]
|
|
73
|
+
let age = this.maxAge
|
|
74
|
+
try {
|
|
75
|
+
const stat = await fs.stat(file)
|
|
76
|
+
age = (now - stat.mtime)
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logger.error(`CacheManager -- ${err}`)
|
|
79
|
+
}
|
|
80
|
+
fileAges[index] = { file, age }
|
|
81
|
+
}
|
|
82
|
+
// Sort by oldest
|
|
83
|
+
fileAges.sort((a, b) => b.age - a.age)
|
|
84
|
+
// Filter by expired
|
|
85
|
+
const toRemove = fileAges.filter(fileAge => fileAge.age >= this.maxAge)
|
|
86
|
+
// Delete expired cache files
|
|
87
|
+
for (const fileAge of toRemove) {
|
|
88
|
+
try {
|
|
89
|
+
await fs.unlink(fileAge.file)
|
|
90
|
+
} catch (err) {
|
|
91
|
+
logger.error(`CacheManager -- Could not clear cache file ${fileAge.file}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
package/lib/Journal.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set a property value
|
|
3
|
+
* @param {Object|Array} object
|
|
4
|
+
* @param {[string]} keys
|
|
5
|
+
* @param {*} value
|
|
6
|
+
* @throws {Cannot locate intermediate key: ${keys}}
|
|
7
|
+
*/
|
|
8
|
+
export function setKeys (object, keys, value) {
|
|
9
|
+
const initialKeys = keys.slice(0, -1)
|
|
10
|
+
const lastKey = keys[keys.length - 1]
|
|
11
|
+
const finalObject = initialKeys.reduce((object, key) => {
|
|
12
|
+
if (!(key in object)) throw new Error(`Cannot locate intermediate key: ${keys}`)
|
|
13
|
+
return object[key]
|
|
14
|
+
}, object)
|
|
15
|
+
finalObject[lastKey] = value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get a property value
|
|
20
|
+
* @param {Object|Array} object
|
|
21
|
+
* @param {[string]} keys
|
|
22
|
+
* @returns {*}
|
|
23
|
+
* @throws {Cannot locate intermediate key: ${keys}}
|
|
24
|
+
*/
|
|
25
|
+
export function getKeys (object, keys) {
|
|
26
|
+
return keys.reduce((object, key) => {
|
|
27
|
+
if (!(key in object)) throw new Error(`Cannot locate intermediate key: ${keys}`)
|
|
28
|
+
return object[key]
|
|
29
|
+
}, object)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Delete a property
|
|
34
|
+
* @param {Object|Array} object
|
|
35
|
+
* @param {[string]} keys
|
|
36
|
+
* @throws {Cannot locate intermediate key: ${keys}}
|
|
37
|
+
*/
|
|
38
|
+
export function deleteKeys (object, keys) {
|
|
39
|
+
const initialKeys = keys.slice(0, -1)
|
|
40
|
+
const lastKey = keys[keys.length - 1]
|
|
41
|
+
const finalObject = initialKeys.reduce((object, key) => {
|
|
42
|
+
if (!(key in object)) throw new Error(`Cannot locate intermediate key: ${keys}`)
|
|
43
|
+
return object[key]
|
|
44
|
+
}, object)
|
|
45
|
+
delete finalObject[lastKey]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clones some JSON
|
|
50
|
+
* @param {Object|Array|string|number|null} object
|
|
51
|
+
* @returns {Object|Array|string|number|null}
|
|
52
|
+
*/
|
|
53
|
+
export function clone (object) {
|
|
54
|
+
return JSON.parse(JSON.stringify(object))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Wraps a JSON hierarchy in transparent, dynamically generated
|
|
59
|
+
* Proxy instances to trap JSON change entries, keeping them in
|
|
60
|
+
* a journal to undo, patch or capture.
|
|
61
|
+
*/
|
|
62
|
+
export default class Journal {
|
|
63
|
+
/**
|
|
64
|
+
* @param {Object} options
|
|
65
|
+
* @param {Object|Array} options.data JSON to wrap
|
|
66
|
+
* @param {Function} [options.supplementEntry] Entry supplementation function
|
|
67
|
+
*/
|
|
68
|
+
constructor ({
|
|
69
|
+
logger,
|
|
70
|
+
data
|
|
71
|
+
}) {
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add supplemental information to the journal
|
|
75
|
+
* for both adapt content and plugins
|
|
76
|
+
*/
|
|
77
|
+
const supplementEntry = (entry, data) => {
|
|
78
|
+
switch (entry.keys[0]) {
|
|
79
|
+
case 'fromPlugins':
|
|
80
|
+
// plugin name
|
|
81
|
+
entry._name = data[entry.keys[0]][entry.keys[1]]?.name ?? '';
|
|
82
|
+
break;
|
|
83
|
+
case 'content':
|
|
84
|
+
// object _id, _type and _component or _extension if available
|
|
85
|
+
entry._id = data[entry.keys[0]][entry.keys[1]]?._id ?? '';
|
|
86
|
+
entry._type = data[entry.keys[0]][entry.keys[1]]?._type ?? '';
|
|
87
|
+
if (entry._type && data[entry.keys[0]][entry.keys[1]]?.[`_${entry._type}`]) {
|
|
88
|
+
entry._name = entry[`_${entry._type}`] = data[entry.keys[0]][entry.keys[1]]?.[`_${entry._type}`] ?? '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
* @param {string} method add/set/delete to signify how the JSON was modified
|
|
96
|
+
* @param {[string]} keys An array of key names to a location in the JSON hierarchy
|
|
97
|
+
* @param {Object|Array|string|number|null} value Value after change
|
|
98
|
+
* @param {Object|Array|string|number|null} previous Value before change
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
const addEntry = (method, keys, value = null, previous = null) => {
|
|
102
|
+
// Prevent entries from being added whilst undoing.
|
|
103
|
+
if (this.isUndoing) return
|
|
104
|
+
// Add entries to the entries list with any necessary supplemental information.
|
|
105
|
+
this.entries.push(supplementEntry({
|
|
106
|
+
method,
|
|
107
|
+
keys,
|
|
108
|
+
// Clone the value and previous to preserve the values of objects by reference.
|
|
109
|
+
value: clone(value),
|
|
110
|
+
previous: clone(previous)
|
|
111
|
+
}, this.data))
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap an Object or Array in a proxy to trap changes
|
|
115
|
+
* @param {Object|Array} data JSON object or array to wrap in a proxy
|
|
116
|
+
* @param {[string]} [keys] Use the closure to store the key location of the generated proxy instance
|
|
117
|
+
* @returns {Proxy<Object|Array>}
|
|
118
|
+
*/
|
|
119
|
+
const wrap = (data, keys = []) => {
|
|
120
|
+
const journal = this
|
|
121
|
+
return new Proxy(data, {
|
|
122
|
+
/**
|
|
123
|
+
* Getter trap. If the property value is an Array or Object, return wrapped at the next key location
|
|
124
|
+
* @param {Object|Array} target
|
|
125
|
+
* @param {string} prop Array index or object property name
|
|
126
|
+
* @returns {Proxy<Object|Array>|string|number|null}
|
|
127
|
+
*/
|
|
128
|
+
get (target, prop) {
|
|
129
|
+
const value = target[prop]
|
|
130
|
+
if (prop === 'valueOf' && typeof value === 'function') {
|
|
131
|
+
// Special case for turning a proxy into its JSON, which is otherwise not possible
|
|
132
|
+
// This prevents the data side from being infected by proxies.
|
|
133
|
+
return function () {
|
|
134
|
+
return target
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(value) || typeof value === 'object') {
|
|
138
|
+
// Wrap an array or object in a new proxy, passing the appropriate key into the closure.
|
|
139
|
+
return wrap(value, keys.concat([prop]))
|
|
140
|
+
}
|
|
141
|
+
return Reflect.get(...arguments)
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Setter trap. Adds entries to the journal at the current key location for sets or adds to the Object or Array.
|
|
145
|
+
* Has special cases for array length modifications.
|
|
146
|
+
* @param {Object|Array} target
|
|
147
|
+
* @param {string} prop
|
|
148
|
+
* @param {Object|Array|string|number|null} value
|
|
149
|
+
* @param {Proxy} proxy
|
|
150
|
+
* @returns {boolean}
|
|
151
|
+
* @throws {Data is frozen, cannot modify}
|
|
152
|
+
*/
|
|
153
|
+
set (target, prop, value, proxy) {
|
|
154
|
+
if (journal.isFrozen) throw new Error('Data is frozen, cannot modify')
|
|
155
|
+
// Special case for turning a proxy into its JSON, which is otherwise not possible.
|
|
156
|
+
// This prevents the data side from being infected by proxies.
|
|
157
|
+
if (value?.valueOf) value = value.valueOf()
|
|
158
|
+
const previous = target[prop]
|
|
159
|
+
const isArrayLength = (Array.isArray(target) && prop === 'length')
|
|
160
|
+
const isObjectPropertyAdd = !(prop in target)
|
|
161
|
+
if (isArrayLength && target.length === value) {
|
|
162
|
+
// Ignore identically set lengths for array (splice can do this)
|
|
163
|
+
// We don't need to add an entry for this.
|
|
164
|
+
return true
|
|
165
|
+
} else if (isObjectPropertyAdd) {
|
|
166
|
+
if (Array.isArray(target) && !isNaN(prop)) {
|
|
167
|
+
// An array index value is being set.
|
|
168
|
+
const intProp = parseInt(prop)
|
|
169
|
+
if (intProp >= target.length) {
|
|
170
|
+
// Make sure to keep the previous length if the array is growing.
|
|
171
|
+
addEntry('set', keys.concat(['length']), intProp + 1, target.length)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Add an entry for an indexed/named property addition, with its new value.
|
|
175
|
+
addEntry('add', keys.concat([prop]), value, undefined)
|
|
176
|
+
} else {
|
|
177
|
+
// Add an entry for a changed indexed/named property value.
|
|
178
|
+
addEntry('set', keys.concat([prop]), value, previous)
|
|
179
|
+
}
|
|
180
|
+
return Reflect.set(target, prop, value, proxy)
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* Delete trap. Adds a journal entry if an indexed/named property is deleted.
|
|
184
|
+
* Splice on an array or delete on an object.
|
|
185
|
+
* @param {Object|Array} target
|
|
186
|
+
* @param {string} prop
|
|
187
|
+
* @returns {boolean}
|
|
188
|
+
* @throws {Data is frozen, cannot modify}
|
|
189
|
+
*/
|
|
190
|
+
deleteProperty (target, prop) {
|
|
191
|
+
if (journal.isFrozen) throw new Error('Data is frozen, cannot modify')
|
|
192
|
+
const previous = target[prop]
|
|
193
|
+
addEntry('delete', keys.concat([prop]), undefined, previous)
|
|
194
|
+
return Reflect.deleteProperty(...arguments)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Original JSON.
|
|
200
|
+
* @type {Object|Array}
|
|
201
|
+
* */
|
|
202
|
+
this.data = data
|
|
203
|
+
/**
|
|
204
|
+
* Proxied version of data.
|
|
205
|
+
* @type {Proxy<Object|Array>}
|
|
206
|
+
* */
|
|
207
|
+
this.subject = wrap(data)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Signifies if the proxy is frozen for sets.
|
|
212
|
+
* @type {boolean}
|
|
213
|
+
*/
|
|
214
|
+
get isFrozen () {
|
|
215
|
+
return this._isFrozen ?? false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Freeze the subject for sets.
|
|
220
|
+
*/
|
|
221
|
+
freeze () {
|
|
222
|
+
this._isFrozen = true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Open the subject to sets.
|
|
227
|
+
*/
|
|
228
|
+
unfreeze () {
|
|
229
|
+
this._isFrozen = false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Undo and return entries.
|
|
234
|
+
* @param {number} [count] Number of entries to undo. Defaults to all.
|
|
235
|
+
* @returns {Array<Object>} Returns undone journal entries.
|
|
236
|
+
* @throws {Cannot locate intermediate key: ${keys}}
|
|
237
|
+
*/
|
|
238
|
+
undo (count = this.entries.length) {
|
|
239
|
+
const entries = []
|
|
240
|
+
this.isUndoing = true
|
|
241
|
+
for (let i = 0, l = count; i < l; i++) {
|
|
242
|
+
const entry = this.entries.pop()
|
|
243
|
+
entries.unshift(entry)
|
|
244
|
+
switch (entry.method) {
|
|
245
|
+
case 'delete':
|
|
246
|
+
case 'set':
|
|
247
|
+
setKeys(this.subject, entry.keys, entry.previous)
|
|
248
|
+
break
|
|
249
|
+
case 'add':
|
|
250
|
+
deleteKeys(this.subject, entry.keys)
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.isUndoing = false
|
|
255
|
+
return entries
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Undo entries up until a specified index.
|
|
260
|
+
* @param {Number} [index=-1] Index up to which to undo. Used in conjunction with lastEntryIndex.
|
|
261
|
+
* @returns {Array<Object>} Returns undone journal entries.
|
|
262
|
+
* @throws {Cannot locate intermediate key: ${keys}}
|
|
263
|
+
*/
|
|
264
|
+
undoToIndex (index = -1) {
|
|
265
|
+
const lastIndex = this.entries.length - 1
|
|
266
|
+
const count = lastIndex - index
|
|
267
|
+
return this.undo(count)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Apply specified entries to the data JSON
|
|
272
|
+
* @param {Array<Object>} entries Entries to apply to the data JSON.
|
|
273
|
+
* @throws Cannot locate intermediate key: ${keys}
|
|
274
|
+
*/
|
|
275
|
+
patch (entries) {
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
switch (entry.method) {
|
|
278
|
+
case 'delete':
|
|
279
|
+
deleteKeys(this.subject, entry.keys)
|
|
280
|
+
break
|
|
281
|
+
case 'set':
|
|
282
|
+
case 'add':
|
|
283
|
+
setKeys(this.subject, entry.keys, entry.value)
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** @type {number} */
|
|
290
|
+
get lastEntryIndex () {
|
|
291
|
+
return this.entries.length - 1
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** @type {Array<Object>} */
|
|
295
|
+
get entries () {
|
|
296
|
+
return (this._entries = this._entries || [])
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** @type {Object} */
|
|
300
|
+
get lastEntry () {
|
|
301
|
+
return this.entries[this.entries.length - 1]
|
|
302
|
+
}
|
|
303
|
+
}
|
package/lib/Logger.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
|
|
5
|
+
function padNum2(value) {
|
|
6
|
+
return String(value).padStart(2, '0');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default class Logger {
|
|
10
|
+
constructor () {
|
|
11
|
+
this.logArr = []
|
|
12
|
+
this.config = {
|
|
13
|
+
levels: {
|
|
14
|
+
error: {
|
|
15
|
+
colour: 'red'
|
|
16
|
+
},
|
|
17
|
+
warn: {
|
|
18
|
+
colour: 'yellow'
|
|
19
|
+
},
|
|
20
|
+
info: {
|
|
21
|
+
colour: 'cyan'
|
|
22
|
+
},
|
|
23
|
+
debug: {
|
|
24
|
+
colour: 'grey'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// getInstance used to allow import
|
|
31
|
+
static getInstance () {
|
|
32
|
+
if (Logger.instance === null) {
|
|
33
|
+
Logger.instance = new Logger()
|
|
34
|
+
}
|
|
35
|
+
return Logger.instance
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Colour level string, easier to differentiate between info/error/warn/debug
|
|
39
|
+
static colourise (str, colour) {
|
|
40
|
+
const chalkFunc = chalk[colour]
|
|
41
|
+
return chalkFunc ? chalkFunc(str) : str
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Use getDateStamp to return readable date/time string.
|
|
45
|
+
static getDateStamp () {
|
|
46
|
+
const d = new Date()
|
|
47
|
+
const date = `${d.getFullYear().toString()}/${padNum2(d.getMonth() + 1)}/${padNum2(d.getDate())}`
|
|
48
|
+
const time = `${padNum2(d.getHours())}:${padNum2(d.getMinutes())}:${padNum2(d.getSeconds())}.${padNum2(d.getMilliseconds())}`
|
|
49
|
+
const str = `${date} ${time}`
|
|
50
|
+
return str
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add Success/Fail ?
|
|
54
|
+
info (...args) {
|
|
55
|
+
this.log('info', args)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
error (...args) {
|
|
59
|
+
this.log('error', args)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
warn (...args) {
|
|
63
|
+
this.log('warn', args)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
debug (...args) {
|
|
67
|
+
this.log('debug', args)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Colour level for easy read, store log in this.logArr as string
|
|
71
|
+
log (level, args) {
|
|
72
|
+
const colour = this?.config?.levels[level]?.colour || 'grey'
|
|
73
|
+
const logFunc = console[level] ?? console.log
|
|
74
|
+
const dateStamp = Logger.getDateStamp()
|
|
75
|
+
const argsStr = args.join(', ')
|
|
76
|
+
logFunc(`(${dateStamp}) -- ${Logger.colourise(level, colour)} -- `, ...args)
|
|
77
|
+
this.logArr.push(`[${dateStamp}] -- ${level} -- ${argsStr}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Output this.logArr, 2 log outputs, capture & migrate
|
|
81
|
+
output (dir, type) {
|
|
82
|
+
const logPath = path.join(dir, './logs/')
|
|
83
|
+
if (!fs.existsSync(logPath)) fs.mkdirSync(logPath)
|
|
84
|
+
|
|
85
|
+
const outputName = `${type}_log`
|
|
86
|
+
const outputFile = path.join(logPath, `${outputName}.json`)
|
|
87
|
+
fs.writeJSONSync(outputFile, this.logArr, { replacer: null, spaces: 2 })
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Logger.instance = null
|