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/LICENSE +21 -0
- package/README.md +49 -0
- package/app.js +7 -0
- package/index.js +11 -0
- package/mikser-lockup-stacked.svg +14 -0
- package/package.json +64 -0
- package/src/catalog.js +56 -0
- package/src/config.js +37 -0
- package/src/constants.js +20 -0
- package/src/engine.js +317 -0
- package/src/journal.js +108 -0
- package/src/lifecycle.js +299 -0
- package/src/manager.js +119 -0
- package/src/plugins/api.js +176 -0
- package/src/plugins/assets.js +346 -0
- package/src/plugins/commands.js +75 -0
- package/src/plugins/data.js +138 -0
- package/src/plugins/documents.js +96 -0
- package/src/plugins/files.js +153 -0
- package/src/plugins/front-matter.js +24 -0
- package/src/plugins/json.js +20 -0
- package/src/plugins/layouts.js +368 -0
- package/src/plugins/mapper.js +29 -0
- package/src/plugins/post/pdf.js +60 -0
- package/src/plugins/render/asset.js +11 -0
- package/src/plugins/render/file.js +16 -0
- package/src/plugins/render/hbs.js +52 -0
- package/src/plugins/render/href.js +45 -0
- package/src/plugins/render/preset.js +13 -0
- package/src/plugins/render/resource.js +17 -0
- package/src/plugins/resources.js +216 -0
- package/src/plugins/rest.js +143 -0
- package/src/plugins/shares.js +40 -0
- package/src/plugins/validator.js +19 -0
- package/src/plugins/yaml.js +26 -0
- package/src/plugins.js +54 -0
- package/src/postprocess.js +47 -0
- package/src/render.js +71 -0
- package/src/runtime.js +153 -0
- package/src/tracking.js +74 -0
- package/src/utils.js +65 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { mkdir, writeFile, unlink, rm, readFile, symlink, } from 'fs/promises'
|
|
3
|
+
import { globby } from 'globby'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
import map from 'p-map'
|
|
6
|
+
|
|
7
|
+
export default ({
|
|
8
|
+
runtime,
|
|
9
|
+
onLoaded,
|
|
10
|
+
useLogger,
|
|
11
|
+
onImport,
|
|
12
|
+
watch,
|
|
13
|
+
onProcessed,
|
|
14
|
+
onBeforeRender,
|
|
15
|
+
useJournal,
|
|
16
|
+
createEntity,
|
|
17
|
+
updateEntity,
|
|
18
|
+
deleteEntity,
|
|
19
|
+
renderEntities,
|
|
20
|
+
onComplete,
|
|
21
|
+
onSync,
|
|
22
|
+
onFinalize,
|
|
23
|
+
findEntity,
|
|
24
|
+
matchEntity,
|
|
25
|
+
changeExtension,
|
|
26
|
+
constants: { ACTION, OPERATION },
|
|
27
|
+
}) => {
|
|
28
|
+
const collection = 'presets'
|
|
29
|
+
const type = 'preset'
|
|
30
|
+
const checksumMap = new Set()
|
|
31
|
+
|
|
32
|
+
async function getEntityPresets(entity) {
|
|
33
|
+
const entityPresets = []
|
|
34
|
+
for (let preset in (runtime.config.assets?.presets || {})) {
|
|
35
|
+
const matches = Array.isArray(runtime.config.assets.presets[preset]) ? runtime.config.assets.presets[preset] : [runtime.config.assets.presets[preset]]
|
|
36
|
+
for (let match of matches) {
|
|
37
|
+
if (matchEntity(entity, match)) {
|
|
38
|
+
entityPresets.push(preset)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return entityPresets
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getRevisions(entity) {
|
|
46
|
+
let revisions = await globby(`${entity.destination.replaceAll('(', '\\(').replaceAll(')', '\\)')}.*.md5`, {
|
|
47
|
+
cwd: path.join(runtime.options.assetsFolder, entity.preset.name),
|
|
48
|
+
expandDirectories: false,
|
|
49
|
+
onlyFiles: true
|
|
50
|
+
})
|
|
51
|
+
return revisions
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function isPresetRendered(entity) {
|
|
55
|
+
let result = false
|
|
56
|
+
let revisions = []
|
|
57
|
+
const assetChecksum = `${entity.destination}.${entity.preset.checksum}.md5`
|
|
58
|
+
if (checksumMap.has(assetChecksum)) {
|
|
59
|
+
revisions.push(assetChecksum)
|
|
60
|
+
} else {
|
|
61
|
+
revisions = await getRevisions(entity)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (let revision of revisions) {
|
|
65
|
+
const [assetsRevision] = revision.split('.').slice(-2, -1)
|
|
66
|
+
if (entity.preset.checksum <= Number.parseInt(assetsRevision)) {
|
|
67
|
+
if (entity.preset.options?.checksum === false) {
|
|
68
|
+
result = true
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let checksum = await readFile(revision, 'utf8')
|
|
73
|
+
result ||= checksum == entity.checksum
|
|
74
|
+
if (result) break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function renderPresets(entities) {
|
|
81
|
+
const { presets, assetsMap } = runtime.state.assets
|
|
82
|
+
|
|
83
|
+
const tasks = []
|
|
84
|
+
for (let entityToRender of entities) {
|
|
85
|
+
for (let entityPreset of assetsMap[entityToRender.id] || []) {
|
|
86
|
+
const entity = _.cloneDeep(entityToRender)
|
|
87
|
+
entity.preset = presets[entityPreset]
|
|
88
|
+
let destination = entity.name
|
|
89
|
+
if (entity.preset.format) {
|
|
90
|
+
destination = changeExtension(destination, entity.preset.format)
|
|
91
|
+
}
|
|
92
|
+
entity.destination = path.join(runtime.options.assetsFolder, entityPreset, destination)
|
|
93
|
+
const ignore = await isPresetRendered(entity)
|
|
94
|
+
tasks.push({
|
|
95
|
+
entity,
|
|
96
|
+
options: {
|
|
97
|
+
...entity.preset.options,
|
|
98
|
+
renderer: 'preset',
|
|
99
|
+
ignore
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
await renderEntities(tasks)
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onLoaded(async () => {
|
|
109
|
+
const logger = useLogger()
|
|
110
|
+
|
|
111
|
+
const assetsName = runtime.config.assets?.assetsFolder || 'assets'
|
|
112
|
+
runtime.state.assets = {
|
|
113
|
+
presets: {},
|
|
114
|
+
assetsMap: {},
|
|
115
|
+
assetsFolder: runtime.config.assets?.outputFolder
|
|
116
|
+
? path.join(runtime.config.assets.outputFolder, assetsName)
|
|
117
|
+
: assetsName,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
runtime.options.presets = runtime.config.presets?.presetsFolder || collection
|
|
121
|
+
runtime.options.presetsFolder = path.join(runtime.options.workingFolder, runtime.options.presets)
|
|
122
|
+
logger.info('Presets folder: %s', runtime.options.presetsFolder)
|
|
123
|
+
await mkdir(runtime.options.presetsFolder, { recursive: true })
|
|
124
|
+
|
|
125
|
+
runtime.options.assets = runtime.config.assets?.assetsFolder || 'assets'
|
|
126
|
+
runtime.options.assetsFolder = path.join(runtime.options.workingFolder, runtime.options.assets)
|
|
127
|
+
logger.info('Assets folder: %s', runtime.options.assetsFolder)
|
|
128
|
+
await mkdir(runtime.options.assetsFolder, { recursive: true })
|
|
129
|
+
|
|
130
|
+
let link = path.join(runtime.options.outputFolder, runtime.options.assets)
|
|
131
|
+
if (runtime.config.assets?.outputFolder) link = path.join(runtime.options.outputFolder, runtime.config.assets?.outputFolder, runtime.options.assets)
|
|
132
|
+
try {
|
|
133
|
+
await mkdir(path.dirname(link), { recursive: true })
|
|
134
|
+
await symlink(path.resolve(runtime.options.assetsFolder), link, 'dir')
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err.code != 'EEXIST')
|
|
137
|
+
throw err
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
watch(collection, runtime.options.presetsFolder)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
onSync(collection, async ({ action, context }) => {
|
|
144
|
+
if (!context.relativePath) return false
|
|
145
|
+
const { relativePath } = context
|
|
146
|
+
|
|
147
|
+
const logger = useLogger()
|
|
148
|
+
const { presets } = runtime.state.assets
|
|
149
|
+
|
|
150
|
+
const name = relativePath.replace(path.extname(relativePath), '')
|
|
151
|
+
const uri = path.join(runtime.options.presetsFolder, relativePath)
|
|
152
|
+
const source = uri
|
|
153
|
+
|
|
154
|
+
let synced = true
|
|
155
|
+
switch (action) {
|
|
156
|
+
case ACTION.CREATE:
|
|
157
|
+
try {
|
|
158
|
+
const { revision = 1, format, options } = await import(`${uri}?stamp=${Date.now()}`)
|
|
159
|
+
const preset = {
|
|
160
|
+
id: path.join('/presets', relativePath),
|
|
161
|
+
collection,
|
|
162
|
+
type,
|
|
163
|
+
uri,
|
|
164
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
165
|
+
source,
|
|
166
|
+
format,
|
|
167
|
+
checksum: revision,
|
|
168
|
+
options
|
|
169
|
+
}
|
|
170
|
+
presets[name] = preset
|
|
171
|
+
await createEntity(preset)
|
|
172
|
+
} catch (err) {
|
|
173
|
+
synced = false
|
|
174
|
+
logger.error('Preset loading error: %s %s', uri, err.message)
|
|
175
|
+
}
|
|
176
|
+
break
|
|
177
|
+
case ACTION.UPDATE:
|
|
178
|
+
try {
|
|
179
|
+
const { revision = 1, format, options } = await import(`${uri}?stamp=${Date.now()}`)
|
|
180
|
+
const preset = {
|
|
181
|
+
id: path.join('/presets', relativePath),
|
|
182
|
+
collection,
|
|
183
|
+
type,
|
|
184
|
+
uri,
|
|
185
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
186
|
+
checksum: revision,
|
|
187
|
+
source,
|
|
188
|
+
format,
|
|
189
|
+
options
|
|
190
|
+
}
|
|
191
|
+
if (!preset[name]) {
|
|
192
|
+
presets[name] = preset
|
|
193
|
+
await createEntity(preset)
|
|
194
|
+
} else if (presets[name].checksum != preset.checksum) {
|
|
195
|
+
presets[name] = preset
|
|
196
|
+
await updateEntity(preset)
|
|
197
|
+
} else {
|
|
198
|
+
synced = false
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
synced = false
|
|
202
|
+
logger.error('Preset loading error: %s %s', uri, err.message)
|
|
203
|
+
}
|
|
204
|
+
break
|
|
205
|
+
case ACTION.DELETE:
|
|
206
|
+
delete presets[name]
|
|
207
|
+
await deleteEntity({
|
|
208
|
+
id: path.join('/presets', relativePath),
|
|
209
|
+
collection,
|
|
210
|
+
type,
|
|
211
|
+
})
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
return synced
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
onImport(async () => {
|
|
218
|
+
const logger = useLogger()
|
|
219
|
+
const { presets } = runtime.state.assets
|
|
220
|
+
|
|
221
|
+
const paths = await globby('*.js', { cwd: runtime.options.presetsFolder })
|
|
222
|
+
for (let relativePath of paths) {
|
|
223
|
+
const uri = path.join(runtime.options.presetsFolder, relativePath)
|
|
224
|
+
const source = uri
|
|
225
|
+
try {
|
|
226
|
+
const { revision = 1, format, options } = await import(`${uri}?stamp=${Date.now()}`)
|
|
227
|
+
const name = relativePath.replace(path.extname(relativePath), '')
|
|
228
|
+
|
|
229
|
+
const preset = {
|
|
230
|
+
id: path.join('/presets', relativePath),
|
|
231
|
+
collection,
|
|
232
|
+
type,
|
|
233
|
+
uri,
|
|
234
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
235
|
+
source,
|
|
236
|
+
format,
|
|
237
|
+
checksum: revision,
|
|
238
|
+
options,
|
|
239
|
+
options
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await createEntity(preset)
|
|
243
|
+
presets[name] = preset
|
|
244
|
+
} catch (err) {
|
|
245
|
+
logger.error(err, 'Preset loading error: %s', uri)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
onProcessed(async (signal) => {
|
|
251
|
+
const logger = useLogger()
|
|
252
|
+
const { assetsMap } = runtime.state.assets
|
|
253
|
+
|
|
254
|
+
for await (let { entity, operation } of useJournal('Assets processing', [OPERATION.CREATE, OPERATION.UPDATE, OPERATION.DELETE], signal)) {
|
|
255
|
+
if (entity.collection != collection) {
|
|
256
|
+
switch (operation) {
|
|
257
|
+
case OPERATION.CREATE:
|
|
258
|
+
case OPERATION.UPDATE:
|
|
259
|
+
const entityPresets = await getEntityPresets(entity)
|
|
260
|
+
if (entityPresets.length) {
|
|
261
|
+
logger.debug('Presets matched for: %s %s', entity.collection, entity.id, entityPresets.length)
|
|
262
|
+
assetsMap[entity.id] = entityPresets
|
|
263
|
+
}
|
|
264
|
+
break
|
|
265
|
+
case OPERATION.DELETE:
|
|
266
|
+
delete assetsMap[entity.id]
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
onBeforeRender(async signal => {
|
|
274
|
+
const { assetsMap } = runtime.state.assets
|
|
275
|
+
|
|
276
|
+
checksumMap.clear()
|
|
277
|
+
const checksumFiles = await globby('**/*.md5', { cwd: runtime.options.assetsFolder })
|
|
278
|
+
for (let checksumFile of checksumFiles) {
|
|
279
|
+
checksumMap.add(path.join(runtime.options.assetsFolder, checksumFile))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const entitiesToRender = new Map()
|
|
283
|
+
await map(useJournal('Assets provision', [OPERATION.CREATE, OPERATION.UPDATE], signal), async ({ entity }) => {
|
|
284
|
+
if (entity.collection == collection) {
|
|
285
|
+
for (let entityId in assetsMap) {
|
|
286
|
+
if (assetsMap[entityId].find(preset => preset == entity.name)) {
|
|
287
|
+
const entityToRender = await findEntity({
|
|
288
|
+
id: entityId
|
|
289
|
+
})
|
|
290
|
+
if (!entitiesToRender.has(entityToRender.id)) {
|
|
291
|
+
entitiesToRender.set(entityToRender.id, entityToRender)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
if (assetsMap[entity.id] && !entitiesToRender.has(entity.id)) {
|
|
297
|
+
entitiesToRender.set(entity.id, entity)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}, { concurrency: 10, signal })
|
|
301
|
+
|
|
302
|
+
await renderPresets(entitiesToRender.values())
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
onComplete(async ({ entity, options }) => {
|
|
306
|
+
const logger = useLogger()
|
|
307
|
+
if (entity.preset && !options?.ignore) {
|
|
308
|
+
await mkdir(path.dirname(entity.destination), { recursive: true })
|
|
309
|
+
const assetChecksum = `${entity.destination}.${entity.preset.checksum}.md5`
|
|
310
|
+
await writeFile(assetChecksum, entity.checksum, 'utf8')
|
|
311
|
+
logger.debug('Asset render finished: [%s] %s', assetChecksum, entity.destination.replace(runtime.options.workingFolder, ''))
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
onFinalize(async () => {
|
|
316
|
+
const logger = useLogger()
|
|
317
|
+
const { presets } = runtime.state.assets
|
|
318
|
+
|
|
319
|
+
let revisions = await globby('**/*.md5', { cwd: runtime.options.assetsFolder })
|
|
320
|
+
for (let revision of revisions) {
|
|
321
|
+
const [preset] = revision.split(path.sep)
|
|
322
|
+
const [assetsRevision] = revision.split('.').slice(-2, -1)
|
|
323
|
+
|
|
324
|
+
if (!presets[preset]) {
|
|
325
|
+
const assetsPresetFolder = path.join(runtime.options.assetsFolder, preset)
|
|
326
|
+
const assetsPresetRemoved = false
|
|
327
|
+
try {
|
|
328
|
+
await rm(assetsPresetFolder, { recursive: true, force: true })
|
|
329
|
+
assetsPresetRemoved = true
|
|
330
|
+
} catch { }
|
|
331
|
+
if (assetsPresetRemoved) {
|
|
332
|
+
logger.debug('Assets preset removed: %s', assetsPresetFolder)
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
if (Number.parseInt(assetsRevision) < presets[preset].checksum) {
|
|
336
|
+
await unlink(path.join(runtime.options.assetsFolder, revision))
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
collection,
|
|
344
|
+
type
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execaCommand } from 'execa'
|
|
2
|
+
import lineReader from 'line-reader'
|
|
3
|
+
import { promisify } from 'util'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
|
|
6
|
+
export default ({
|
|
7
|
+
runtime,
|
|
8
|
+
useLogger,
|
|
9
|
+
onLoad,
|
|
10
|
+
onLoaded,
|
|
11
|
+
onImport,
|
|
12
|
+
onImported,
|
|
13
|
+
onProcess,
|
|
14
|
+
onProcessed,
|
|
15
|
+
onPersist,
|
|
16
|
+
onPersisted,
|
|
17
|
+
onCancel,
|
|
18
|
+
onCancelled,
|
|
19
|
+
onBeforeRender,
|
|
20
|
+
onRender,
|
|
21
|
+
onAfterRender,
|
|
22
|
+
onFinalize,
|
|
23
|
+
onFinalized
|
|
24
|
+
}) => {
|
|
25
|
+
const eachLine = promisify(lineReader.eachLine)
|
|
26
|
+
const running = {}
|
|
27
|
+
|
|
28
|
+
async function executeCommand(command) {
|
|
29
|
+
const logger = useLogger()
|
|
30
|
+
if (_.endsWith(command, '&')) {
|
|
31
|
+
command = command.slice(0, -1)
|
|
32
|
+
if (!running[command]) {
|
|
33
|
+
logger.info('Command: %s', command, runtime.options.wokrkingFolder)
|
|
34
|
+
const subprocess = execaCommand(command, { cwd: runtime.options.wokrkingFolder, all: true })
|
|
35
|
+
eachLine(subprocess.all, line => logger.info(line))
|
|
36
|
+
running[command] = subprocess
|
|
37
|
+
.then(() => delete running[command])
|
|
38
|
+
.catch(err => logger.error(err, 'Command error'))
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
logger.info('Command: %s', command, runtime.options.wokrkingFolder)
|
|
42
|
+
const subprocess = execaCommand(command, { cwd: runtime.options.wokrkingFolder, all: true })
|
|
43
|
+
await eachLine(subprocess.all, line => logger.debug(line))
|
|
44
|
+
await subprocess
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function executeCommands(hook) {
|
|
49
|
+
let commands = runtime.config.commands && runtime.config.commands[hook] || []
|
|
50
|
+
if (typeof commands == 'function') commands = await commands()
|
|
51
|
+
if (typeof commands == 'string') commands = [commands]
|
|
52
|
+
|
|
53
|
+
for (let command of commands) {
|
|
54
|
+
await executeCommand(command)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onLoad(async () => await executeCommands('load'))
|
|
59
|
+
onLoaded(async () => await executeCommands('loaded'))
|
|
60
|
+
onImport(async () => await executeCommands('import'))
|
|
61
|
+
onImported(async () => await executeCommands('imported'))
|
|
62
|
+
onProcess(async () => await executeCommands('process'))
|
|
63
|
+
onProcessed(async () => await executeCommands('processed'))
|
|
64
|
+
onPersist(async () => await executeCommands('persist'))
|
|
65
|
+
onPersisted(async () => await executeCommands('persisted'))
|
|
66
|
+
onBeforeRender(async () => await executeCommands('beforeRender'))
|
|
67
|
+
onRender(async () => await executeCommands('render'))
|
|
68
|
+
onAfterRender(async () => await executeCommands('afterRender'))
|
|
69
|
+
onCancel(async () => await executeCommands('cancel'))
|
|
70
|
+
onCancelled(async () => await executeCommands('canceled'))
|
|
71
|
+
onFinalize(async () => await executeCommands('finalize'))
|
|
72
|
+
onFinalized(async () => await executeCommands('finalized'))
|
|
73
|
+
|
|
74
|
+
return { executeCommand }
|
|
75
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { mkdir, writeFile, unlink } from 'fs/promises'
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
import pMap from 'p-map'
|
|
5
|
+
|
|
6
|
+
export default ({
|
|
7
|
+
onLoaded,
|
|
8
|
+
useLogger,
|
|
9
|
+
runtime,
|
|
10
|
+
useJournal,
|
|
11
|
+
normalize,
|
|
12
|
+
findEntities,
|
|
13
|
+
onAfterRender,
|
|
14
|
+
onFinalize,
|
|
15
|
+
onBeforeRender,
|
|
16
|
+
constants: { OPERATION },
|
|
17
|
+
}) => {
|
|
18
|
+
onLoaded(async () => {
|
|
19
|
+
const logger = useLogger()
|
|
20
|
+
runtime.options.data = runtime.config.data?.dataFolder || 'data'
|
|
21
|
+
runtime.options.dataFolder = path.join(runtime.options.outputFolder, runtime.options.data)
|
|
22
|
+
|
|
23
|
+
logger.info('Data folder: %s', runtime.options.dataFolder)
|
|
24
|
+
await mkdir(runtime.options.dataFolder, { recursive: true })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
onBeforeRender(async () => {
|
|
28
|
+
const logger = useLogger()
|
|
29
|
+
|
|
30
|
+
let entitiesConfig = runtime.config.data?.entities
|
|
31
|
+
if (entitiesConfig === undefined) {
|
|
32
|
+
entitiesConfig = {
|
|
33
|
+
document: {
|
|
34
|
+
query: entity => entity.type == 'document'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (let entitiesName in entitiesConfig) {
|
|
39
|
+
const {
|
|
40
|
+
query,
|
|
41
|
+
map,
|
|
42
|
+
pick,
|
|
43
|
+
save: saveEntity = async entity => {
|
|
44
|
+
if (!entity.name) {
|
|
45
|
+
logger.warn('Entity name is missing: %o', entity)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const dump = JSON.stringify(normalize(entity))
|
|
49
|
+
const entityFile = path.join(runtime.options.dataFolder, `${entity.name}.${entitiesName}.json`)
|
|
50
|
+
await mkdir(path.dirname(entityFile), { recursive: true })
|
|
51
|
+
await writeFile(entityFile, dump, 'utf8')
|
|
52
|
+
},
|
|
53
|
+
delete: deleteEntity = async entity => {
|
|
54
|
+
const entityFile = path.join(runtime.options.dataFolder, `${entity.name}.json`)
|
|
55
|
+
await unlink(entityFile)
|
|
56
|
+
}
|
|
57
|
+
} = entitiesConfig[entitiesName]
|
|
58
|
+
|
|
59
|
+
for await (let { operation, entity } of useJournal('Data entities', [OPERATION.CREATE, OPERATION.UPDATE, OPERATION.DELETE])) {
|
|
60
|
+
if (query(entity)) {
|
|
61
|
+
switch (operation) {
|
|
62
|
+
case OPERATION.CREATE:
|
|
63
|
+
case OPERATION.UPDATE:
|
|
64
|
+
logger.debug('Data export entity %s %s: %s', entity.collection, operation, entity.id)
|
|
65
|
+
await saveEntity(map ? await map(entity) : {
|
|
66
|
+
refId: ('/' + entity.name.replaceAll('\\', '/')).replace(/\/index$/g, '/'),
|
|
67
|
+
name: entity.name,
|
|
68
|
+
date: new Date(entity.time),
|
|
69
|
+
data: _.pick(entity, pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
70
|
+
})
|
|
71
|
+
break
|
|
72
|
+
case OPERATION.DELETE:
|
|
73
|
+
await deleteEntity(entity)
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
onAfterRender(async () => {
|
|
82
|
+
const logger = useLogger()
|
|
83
|
+
|
|
84
|
+
let contextConfig = runtime.config.data?.context
|
|
85
|
+
if (contextConfig === undefined) {
|
|
86
|
+
contextConfig = {
|
|
87
|
+
context: {
|
|
88
|
+
query: entity => entity.type == 'document'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (let contextName in contextConfig) {
|
|
93
|
+
const {
|
|
94
|
+
query,
|
|
95
|
+
map,
|
|
96
|
+
pick,
|
|
97
|
+
save: saveConext = async (entity, context) => {
|
|
98
|
+
if (context?.data) {
|
|
99
|
+
const entityName = entity.name
|
|
100
|
+
const contextFile = path.join(runtime.options.dataFolder, `${entityName}.${contextName}.json`)
|
|
101
|
+
await mkdir(path.dirname(contextFile), { recursive: true })
|
|
102
|
+
await writeFile(contextFile, JSON.stringify(context), 'utf8')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} = contextConfig[contextName]
|
|
106
|
+
|
|
107
|
+
for await (let { entity, context } of useJournal('Data context', [OPERATION.RENDER])) {
|
|
108
|
+
if (query(entity)) {
|
|
109
|
+
logger.debug('Data export context: %s', entity.name)
|
|
110
|
+
await saveConext(entity, map ? await map(entity, context) : _.pick(context, pick || ['data']))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
onFinalize(async () => {
|
|
117
|
+
const logger = useLogger()
|
|
118
|
+
for (let catalogName in runtime.config.data?.catalog || {}) {
|
|
119
|
+
const {
|
|
120
|
+
query: queryEntities = entity => entity.type == 'document',
|
|
121
|
+
map,
|
|
122
|
+
pick,
|
|
123
|
+
save: saveEntities = async entities => {
|
|
124
|
+
const entitiesFile = path.join(runtime.options.dataFolder, `${catalogName}.json`)
|
|
125
|
+
logger.debug('Data export catalog %s %s: %s', catalogName, entities.length, entitiesFile)
|
|
126
|
+
await writeFile(entitiesFile, JSON.stringify(entities), 'utf8')
|
|
127
|
+
}
|
|
128
|
+
} = runtime.config.data?.catalog[catalogName]
|
|
129
|
+
const entities = await findEntities(queryEntities)
|
|
130
|
+
await saveEntities(await pMap(entities, async entity => map ? await map(entity) : {
|
|
131
|
+
refId: ('/' + entity.name.replaceAll('\\', '/')).replace(/\/index$/g, '/'),
|
|
132
|
+
name: entity.name,
|
|
133
|
+
date: new Date(entity.time),
|
|
134
|
+
data: _.pick(entity, pick || ['collection', 'format', 'type', 'destination', 'stamp', 'meta', 'id',])
|
|
135
|
+
}))
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { mkdir, readFile } from 'fs/promises'
|
|
3
|
+
import { globby } from 'globby'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
|
|
6
|
+
export default ({
|
|
7
|
+
runtime,
|
|
8
|
+
onLoaded,
|
|
9
|
+
useLogger,
|
|
10
|
+
onImport,
|
|
11
|
+
createEntity,
|
|
12
|
+
updateEntity,
|
|
13
|
+
deleteEntity,
|
|
14
|
+
watch,
|
|
15
|
+
onSync,
|
|
16
|
+
trackProgress,
|
|
17
|
+
updateProgress,
|
|
18
|
+
constants: { ACTION }
|
|
19
|
+
}) => {
|
|
20
|
+
const collection = 'documents'
|
|
21
|
+
const type = 'document'
|
|
22
|
+
|
|
23
|
+
onSync(collection, async ({ action, context }) => {
|
|
24
|
+
if (!context.relativePath) return false
|
|
25
|
+
const { relativePath } = context
|
|
26
|
+
const id = path.join(`/${collection}`, relativePath)
|
|
27
|
+
const uri = path.join(runtime.options.documentsFolder, relativePath)
|
|
28
|
+
switch (action) {
|
|
29
|
+
case ACTION.CREATE:
|
|
30
|
+
await createEntity({
|
|
31
|
+
id,
|
|
32
|
+
uri,
|
|
33
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
34
|
+
collection,
|
|
35
|
+
type,
|
|
36
|
+
format: path.extname(relativePath).substring(1).toLowerCase(),
|
|
37
|
+
content: await readFile(uri, 'utf8')
|
|
38
|
+
})
|
|
39
|
+
break
|
|
40
|
+
case ACTION.UPDATE:
|
|
41
|
+
await updateEntity({
|
|
42
|
+
id,
|
|
43
|
+
uri,
|
|
44
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
45
|
+
collection,
|
|
46
|
+
type,
|
|
47
|
+
format: path.extname(relativePath).substring(1).toLowerCase(),
|
|
48
|
+
content: await readFile(uri, 'utf8')
|
|
49
|
+
})
|
|
50
|
+
break
|
|
51
|
+
case ACTION.DELETE:
|
|
52
|
+
await deleteEntity({
|
|
53
|
+
id,
|
|
54
|
+
collection,
|
|
55
|
+
type,
|
|
56
|
+
format: path.extname(relativePath).substring(1).toLowerCase(),
|
|
57
|
+
})
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
onLoaded(async () => {
|
|
63
|
+
const logger = useLogger()
|
|
64
|
+
runtime.options.documents = runtime.config.documents?.documentsFolder || collection
|
|
65
|
+
runtime.options.documentsFolder = path.join(runtime.options.workingFolder, runtime.options.documents)
|
|
66
|
+
|
|
67
|
+
logger.info('Documents folder: %s', runtime.options.documentsFolder)
|
|
68
|
+
await mkdir(runtime.options.documentsFolder, { recursive: true })
|
|
69
|
+
|
|
70
|
+
watch(collection, runtime.options.documentsFolder)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
onImport(async () => {
|
|
74
|
+
const paths = await globby('**/*', { cwd: runtime.options.documentsFolder })
|
|
75
|
+
|
|
76
|
+
trackProgress('Documents import', paths.length)
|
|
77
|
+
return Promise.all(paths.map(async relativePath => {
|
|
78
|
+
const uri = path.join(runtime.options.documentsFolder, relativePath)
|
|
79
|
+
await createEntity({
|
|
80
|
+
id: path.join(`/${collection}`, relativePath),
|
|
81
|
+
uri,
|
|
82
|
+
name: relativePath.replace(path.extname(relativePath), ''),
|
|
83
|
+
collection,
|
|
84
|
+
type,
|
|
85
|
+
format: path.extname(relativePath).substring(1).toLowerCase(),
|
|
86
|
+
content: await readFile(uri, 'utf8')
|
|
87
|
+
})
|
|
88
|
+
updateProgress()
|
|
89
|
+
}))
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
collection,
|
|
94
|
+
type
|
|
95
|
+
}
|
|
96
|
+
}
|