mikser-io 6.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/src/render.js ADDED
@@ -0,0 +1,71 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import _ from 'lodash'
4
+
5
+ export default async ({ entity, options, config, context, state, logger, port }) => {
6
+ logger = logger || {
7
+ info(...args) {
8
+ port.postMessage(JSON.stringify({ command: 'logger', data: { log: 'info', args } }))
9
+ },
10
+ warn(...args) {
11
+ port.postMessage(JSON.stringify({ command: 'logger', data: { log: 'warn', args } }))
12
+ },
13
+ error(...args) {
14
+ port.postMessage(JSON.stringify({ command: 'logger', data: { log: 'error', args } }))
15
+ },
16
+ trace(...args) {
17
+ port.postMessage(JSON.stringify({ command: 'logger', data: { log: 'trace', args } }))
18
+ },
19
+ notice(...args) {
20
+ port.postMessage(JSON.stringify({ command: 'logger', data: { log: 'notice', args } }))
21
+ }
22
+ }
23
+
24
+ async function loadPlugin(pluginName) {
25
+ const resolveLocations = [
26
+ path.join(options.workingFolder, 'node_modules', `mikser-core-${pluginName}/index.js`),
27
+ path.join(options.workingFolder, 'plugins', `${pluginName}.js`),
28
+ path.join(path.dirname(import.meta.url), 'plugins', 'render', `${pluginName.replace('render-', '')}.js`)
29
+ ]
30
+ for (let resolveLocation of resolveLocations) {
31
+ try {
32
+ return await import(resolveLocation)
33
+ } catch (err) {
34
+ if (err.code != 'ERR_MODULE_NOT_FOUND') {
35
+ logger.error('Redner plugin error:', resolveLocation, err)
36
+ throw err
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ const { renderer } = options
43
+ const plugins = {}
44
+ let pluginsToLoad = [...context.plugins || []]
45
+ pluginsToLoad.push(`render-${renderer}`)
46
+ if (entity.meta?.plugins) {
47
+ pluginsToLoad.push(...entity.meta.plugins)
48
+ }
49
+ pluginsToLoad.push(...options.plugins)
50
+ pluginsToLoad = _.uniq(pluginsToLoad.filter(pluginName => pluginName && pluginName.indexOf('render-') == 0))
51
+
52
+ const runtime = {
53
+ [entity.type]: entity,
54
+ entity,
55
+ plugins,
56
+ config: config[`render-${renderer}`],
57
+ data: context.data,
58
+ content() {
59
+ return readFileSync(entity.source, { encoding: 'utf8' })
60
+ }
61
+ }
62
+
63
+ for (let pluginName of pluginsToLoad) {
64
+ const plugin = await loadPlugin(pluginName)
65
+ plugins[pluginName] = plugin
66
+ if (plugin?.load) await plugin.load({ entity, options, config: config[pluginName], context, runtime, state, logger })
67
+ }
68
+
69
+ const rendererPlugin = plugins[`render-${renderer}`]
70
+ return await rendererPlugin?.render({ entity, options, config, context, plugins, runtime, state, logger })
71
+ }
package/src/runtime.js ADDED
@@ -0,0 +1,153 @@
1
+ import { Mutex } from 'await-semaphore'
2
+ import { AbortError } from './utils.js'
3
+
4
+ const runtime = {
5
+ stamp: Date.now(),
6
+ processTime: undefined,
7
+ engine: {},
8
+ options: {
9
+ plugins: []
10
+ },
11
+ config: {},
12
+ journal: [],
13
+ validators: [],
14
+ started: false,
15
+ mutex: new Mutex(),
16
+ abortController: undefined,
17
+ hooks: {
18
+ initialize: [],
19
+ initialized: [],
20
+ load: [],
21
+ loaded: [],
22
+ import: [],
23
+ validate: [],
24
+ imported: [],
25
+ process: [],
26
+ processed: [],
27
+ persist: [],
28
+ persisted: [],
29
+ beforeRender: [],
30
+ render: [],
31
+ afterRender: [],
32
+ beforePostprocess: [],
33
+ postprocess: [],
34
+ afterPostprocess: [],
35
+ cancel: [],
36
+ cancelled: [],
37
+ finalize: [],
38
+ finalized: [],
39
+ sync: [],
40
+ completed: [],
41
+ },
42
+
43
+ async callHooks(hooks, signal) {
44
+ for (let hook of hooks) {
45
+ if (signal?.aborted) throw new AbortError()
46
+ await hook(signal)
47
+ }
48
+ },
49
+
50
+ addHook(name, hook) {
51
+ if (!this.hooks[name]) throw new Error(`Unknown hook: ${name}`)
52
+ this.hooks[name].push(hook)
53
+ return hook
54
+ },
55
+
56
+ removeHook(name, hook) {
57
+ if (!this.hooks[name]) throw new Error(`Unknown hook: ${name}`)
58
+ const idx = this.hooks[name].indexOf(hook)
59
+ if (idx > -1) this.hooks[name].splice(idx, 1)
60
+ },
61
+
62
+ async start() {
63
+ await this.callHooks(this.hooks.initialize)
64
+ await this.callHooks(this.hooks.initialized)
65
+ await this.callHooks(this.hooks.load)
66
+ await this.callHooks(this.hooks.loaded)
67
+
68
+ await this.callHooks(this.hooks.import)
69
+ await this.callHooks(this.hooks.imported)
70
+
71
+ this.started = true
72
+ await this.process()
73
+ },
74
+
75
+ async process() {
76
+ if (this.abortController?.signal.aborted) return
77
+ else if (this.abortController) {
78
+ await this.cancel()
79
+ }
80
+ this.mutex.use(async () => {
81
+ try {
82
+ this.abortController = new AbortController()
83
+ const { signal } = this.abortController
84
+
85
+ await this.callHooks(this.hooks.process, signal)
86
+ await this.callHooks(this.hooks.processed, signal)
87
+ await this.callHooks(this.hooks.persist, signal)
88
+ await this.callHooks(this.hooks.persisted, signal)
89
+
90
+ await this.render(signal)
91
+ } catch (e) {
92
+ if (e.name !== 'AbortError') throw e
93
+ for (let hook of this.hooks.cancelled) await hook()
94
+ }
95
+ })
96
+ },
97
+
98
+ async render(signal) {
99
+ await this.callHooks(this.hooks.beforeRender, signal)
100
+ await this.callHooks(this.hooks.render, signal)
101
+ await this.callHooks(this.hooks.afterRender, signal)
102
+
103
+ await this.postprocess(signal)
104
+ },
105
+
106
+ async postprocess(signal) {
107
+ await this.callHooks(this.hooks.beforePostprocess, signal)
108
+ await this.callHooks(this.hooks.postprocess, signal)
109
+ await this.callHooks(this.hooks.afterPostprocess, signal)
110
+
111
+ await this.finalize(signal)
112
+ },
113
+
114
+ async cancel() {
115
+ this.abortController?.abort()
116
+ await this.callHooks(this.hooks.cancel)
117
+ },
118
+
119
+ async finalize(signal) {
120
+ await this.callHooks(this.hooks.finalize, signal)
121
+ await this.callHooks(this.hooks.finalized, signal)
122
+ },
123
+
124
+ async sync(operation) {
125
+ let synced
126
+ for (let hook of this.hooks.sync) {
127
+ const result = await hook(operation)
128
+ if (result === true) {
129
+ synced = true
130
+ } else if (result === false && !synced) {
131
+ synced = false
132
+ }
133
+ }
134
+ return synced === undefined || synced
135
+ },
136
+
137
+ async validate(entry) {
138
+ for (let hook of this.validators) {
139
+ if (!await hook(entry)) return false
140
+ }
141
+ return true
142
+ },
143
+
144
+ async complete(entry) {
145
+ let success = true
146
+ for (let hook of this.hooks.completed) {
147
+ if (await hook(entry) === false) success = false
148
+ }
149
+ entry.success = success
150
+ }
151
+ }
152
+
153
+ export default runtime
@@ -0,0 +1,74 @@
1
+ import cliProgress from 'cli-progress'
2
+ import runtime from './runtime.js'
3
+ import { useLogger } from './engine.js'
4
+ import formatTime from 'cli-progress/lib/format-time.js'
5
+ import { onInitialized } from './lifecycle.js'
6
+ import util from 'util'
7
+
8
+ let progress = {}
9
+
10
+ function log(log) {
11
+ const { bar, total, value, name } = progress
12
+ const isActive = bar?.isActive
13
+ isActive && bar?.stop()
14
+ log()
15
+ isActive && bar?.start(total, value, { name, details: '' })
16
+ }
17
+
18
+ onInitialized(() => {
19
+ const logger = useLogger()
20
+ if (runtime.options.info) {
21
+ logger.info = (...args) => log(() => console.log(...args))
22
+ logger.warn = (...args) => log(() => console.log('🟡 ' + args[0], ...args.slice(1)))
23
+ logger.error = (...args) => log(() => console.log('🔴 ' + args[0], ...args.slice(1)))
24
+ logger.notice = (...args) => log(() => console.log('🟢 ' + args[0], ...args.slice(1)))
25
+ logger.trace = (...args) => updateProgressDetails(util.format(...args))
26
+ }
27
+ })
28
+
29
+ export function trackProgress(name, total) {
30
+ const logger = useLogger()
31
+ logger.debug('%s started: %d', name, total)
32
+ progress.bar?.stop()
33
+ progress = {
34
+ name,
35
+ total,
36
+ value: 0,
37
+ stamp: Date.now(),
38
+ bar: runtime.options.info ? new cliProgress.SingleBar({
39
+ noTTYOutput: true,
40
+ hideCursor: true,
41
+ clearOnComplete: true,
42
+ barsize: 30,
43
+ format: '{name}: {bar} {percentage}% | ETA: {eta_formatted} {details}',
44
+ }, cliProgress.Presets.shades_grey) : null
45
+ }
46
+ progress.bar?.start(total, 0, { name, details: '' })
47
+ }
48
+
49
+ export function stopProgress() {
50
+ const logger = useLogger()
51
+ const { value, total, bar, name } = progress
52
+ bar?.stop()
53
+ if (value < total) {
54
+ logger.warn('%s unfinished: %d', name, total - value)
55
+ } else {
56
+ const time = Math.round((Date.now() - progress.stamp) / 1000)
57
+ logger.info('%s: %s', name, formatTime(time, { autopaddingChar: '' }))
58
+ }
59
+ progress = {}
60
+ }
61
+
62
+ export function updateProgress() {
63
+ progress.name && progress.value++
64
+ progress.bar?.increment()
65
+ if (progress?.value == progress?.total) {
66
+ stopProgress()
67
+ }
68
+ }
69
+
70
+ export function updateProgressDetails(details) {
71
+ const logger = useLogger()
72
+ logger.debug(details)
73
+ progress.bar?.update({ details: `| ${details}` })
74
+ }
package/src/utils.js ADDED
@@ -0,0 +1,65 @@
1
+ import { hashFile, hashingStream } from 'hasha'
2
+ import { stat } from 'node:fs/promises'
3
+ import TruncateStream from 'truncate-stream'
4
+ import { createReadStream } from 'node:fs'
5
+ import _ from 'lodash'
6
+ import { minimatch } from 'minimatch'
7
+ import path from 'path'
8
+
9
+ export class AbortError extends Error {
10
+ constructor(message) {
11
+ super();
12
+ this.name = 'AbortError';
13
+ this.message = message;
14
+ }
15
+ }
16
+
17
+ export async function checksum(uri) {
18
+ const maxBytes = 300 * 1024
19
+ const { size } = await stat(uri)
20
+ if (size < maxBytes) {
21
+ return await hashFile(uri, { algorithm: 'md5' })
22
+ } else {
23
+ const truncate = new TruncateStream({ maxBytes })
24
+ const fileStream = createReadStream(uri)
25
+ fileStream.pipe(truncate)
26
+ const checksum = size.toString() + ':' + await hashingStream(truncate, { algorithm: 'md5' })
27
+ return checksum
28
+ }
29
+ }
30
+
31
+ export function normalize(object) {
32
+ return _.pickBy(
33
+ object,
34
+ (value, key) => {
35
+ let pick = value !== undefined &&
36
+ value !== '' &&
37
+ value !== null &&
38
+ key !== 'undefined' &&
39
+ key !== '' &&
40
+ key !== 'null' &&
41
+ (typeof (value) != 'number' || !isNaN(value))
42
+ return pick
43
+ }
44
+ )
45
+ }
46
+
47
+ export function matchEntity(entity, match) {
48
+ if (!match) return false
49
+ if (typeof match == 'function') return match(entity)
50
+ else if (typeof match == 'string') {
51
+ if (match.substring(0, 1) == '@/') {
52
+ return minimatch(entity.name, match.substring(2))
53
+ } else {
54
+ return minimatch(entity.id, match)
55
+ }
56
+ }
57
+ else if (typeof match == 'object') return _.isMatch(entity, match)
58
+ throw new Error('Ivalid match type')
59
+ }
60
+
61
+ export function changeExtension(file, format) {
62
+ let extension = path.extname(file)
63
+ let result = file.substring(0, file.length - extension.length) + '.' + format
64
+ return result
65
+ }