@gesslar/bedoc 1.11.0 → 2.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.txt +12 -0
- package/README.md +15 -3
- package/dist/schema/bedoc.action.json +42 -0
- package/dist/types/Action.d.ts +3 -0
- package/dist/types/Action.d.ts.map +1 -0
- package/dist/types/BeDoc.d.ts +208 -0
- package/dist/types/BeDoc.d.ts.map +1 -0
- package/dist/types/Configuration.d.ts +11 -0
- package/dist/types/Configuration.d.ts.map +1 -0
- package/dist/types/ConfigurationParameters.d.ts +3 -0
- package/dist/types/ConfigurationParameters.d.ts.map +1 -0
- package/dist/types/Conveyor.d.ts +27 -0
- package/dist/types/Conveyor.d.ts.map +1 -0
- package/dist/types/Discovery.d.ts +215 -0
- package/dist/types/Discovery.d.ts.map +1 -0
- package/dist/types/Environment.d.ts +3 -0
- package/dist/types/Environment.d.ts.map +1 -0
- package/dist/types/Logger.d.ts +47 -0
- package/dist/types/Logger.d.ts.map +1 -0
- package/dist/types/Schema.d.ts +3 -0
- package/dist/types/Schema.d.ts.map +1 -0
- package/dist/types/cli.d.ts +2 -2
- package/dist/types/cli.d.ts.map +1 -10
- package/package.json +24 -23
- package/src/Action.js +9 -0
- package/src/BeDoc.js +276 -0
- package/src/CLIOutput.js +198 -0
- package/src/{core/Configuration.js → Configuration.js} +72 -58
- package/src/{core/ConfigurationParameters.js → ConfigurationParameters.js} +35 -27
- package/src/Conveyor.js +256 -0
- package/src/Discovery.js +442 -0
- package/src/Environment.js +8 -0
- package/src/{core/Logger.js → Logger.js} +30 -18
- package/src/Schema.js +6 -0
- package/src/cli.js +77 -34
- package/tsconfig.types.json +42 -0
- package/LICENSE +0 -24
- package/dist/types/core/ActionManager.d.ts +0 -58
- package/dist/types/core/ActionManager.d.ts.map +0 -10
- package/dist/types/core/Configuration.d.ts +0 -27
- package/dist/types/core/Configuration.d.ts.map +0 -10
- package/dist/types/core/ConfigurationParameters.d.ts +0 -38
- package/dist/types/core/ConfigurationParameters.d.ts.map +0 -10
- package/dist/types/core/Conveyor.d.ts +0 -49
- package/dist/types/core/Conveyor.d.ts.map +0 -10
- package/dist/types/core/Core.d.ts +0 -48
- package/dist/types/core/Core.d.ts.map +0 -10
- package/dist/types/core/Discovery.d.ts +0 -73
- package/dist/types/core/Discovery.d.ts.map +0 -10
- package/dist/types/core/HookManager.d.ts +0 -60
- package/dist/types/core/HookManager.d.ts.map +0 -10
- package/dist/types/core/Logger.d.ts +0 -63
- package/dist/types/core/Logger.d.ts.map +0 -10
- package/dist/types/core/action/ParseManager.d.ts +0 -8
- package/dist/types/core/action/ParseManager.d.ts.map +0 -10
- package/dist/types/core/action/PrintManager.d.ts +0 -8
- package/dist/types/core/action/PrintManager.d.ts.map +0 -10
- package/dist/types/core/util/ActionUtil.d.ts +0 -35
- package/dist/types/core/util/ActionUtil.d.ts.map +0 -10
- package/dist/types/core/util/DataUtil.d.ts +0 -52
- package/dist/types/core/util/DataUtil.d.ts.map +0 -10
- package/dist/types/core/util/FDUtil.d.ts +0 -171
- package/dist/types/core/util/FDUtil.d.ts.map +0 -10
- package/dist/types/core/util/ModuleUtil.d.ts +0 -27
- package/dist/types/core/util/ModuleUtil.d.ts.map +0 -10
- package/dist/types/core/util/StringUtil.d.ts +0 -5
- package/dist/types/core/util/StringUtil.d.ts.map +0 -10
- package/dist/types/core/util/TypeSpec.d.ts +0 -42
- package/dist/types/core/util/TypeSpec.d.ts.map +0 -10
- package/dist/types/core/util/ValidUtil.d.ts +0 -29
- package/dist/types/core/util/ValidUtil.d.ts.map +0 -10
- package/src/core/ActionManager.js +0 -147
- package/src/core/ContractManager.js +0 -112
- package/src/core/Conveyor.js +0 -185
- package/src/core/Core.js +0 -166
- package/src/core/Discovery.js +0 -403
- package/src/core/HookManager.js +0 -143
- package/src/core/action/ParseManager.js +0 -7
- package/src/core/action/PrintManager.js +0 -7
- package/src/core/contract/ParseContract.js +0 -7
- package/src/core/contract/PrintContract.js +0 -7
- package/src/core/util/ActionUtil.js +0 -62
- package/src/core/util/ContractUtil.js +0 -63
- package/src/core/util/DataUtil.js +0 -540
- package/src/core/util/FDUtil.js +0 -388
- package/src/core/util/StringUtil.js +0 -11
- package/src/core/util/TypeSpec.js +0 -114
- package/src/core/util/ValidUtil.js +0 -50
package/src/core/Discovery.js
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import {execSync} from "child_process"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
|
|
4
|
-
import ContractManager from "./ContractManager.js"
|
|
5
|
-
import * as FDUtil from "./util/FDUtil.js"
|
|
6
|
-
import * as ActionUtil from "./util/ActionUtil.js"
|
|
7
|
-
import * as DataUtil from "./util/DataUtil.js"
|
|
8
|
-
|
|
9
|
-
const {composeDirectory,directoryExists,resolveDirectory} = FDUtil
|
|
10
|
-
const {newContract,parse} = ContractManager
|
|
11
|
-
const {ls,fileExists,composeFilename,getFiles} = FDUtil
|
|
12
|
-
const {actionTypes, actionMetaRequirements,loadDataFile} = ActionUtil
|
|
13
|
-
const {isType} = DataUtil
|
|
14
|
-
|
|
15
|
-
export default class Discovery {
|
|
16
|
-
#logger
|
|
17
|
-
#debug
|
|
18
|
-
|
|
19
|
-
constructor(core) {
|
|
20
|
-
this.core = core
|
|
21
|
-
this.#logger = core.logger
|
|
22
|
-
this.#debug = this.#logger.newDebug()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Discover actions from local or global node_modules
|
|
27
|
-
*
|
|
28
|
-
* @param {object} [specific] Configuration options for action discovery
|
|
29
|
-
* @param {object} [specific.print] Print-related configuration options
|
|
30
|
-
* @param {object} [specific.parse] Parse-related configuration options
|
|
31
|
-
* @returns {Promise<object>} A map of discovered modules
|
|
32
|
-
*/
|
|
33
|
-
async discoverActions(specific = {}) {
|
|
34
|
-
const debug = this.#debug
|
|
35
|
-
|
|
36
|
-
debug("Discovering actions", 2)
|
|
37
|
-
|
|
38
|
-
debug("Specific modules provided: %o", 2, Object.values(specific).filter(Boolean).length)
|
|
39
|
-
debug("Specific modules provided: %o", 3, specific)
|
|
40
|
-
|
|
41
|
-
const files = []
|
|
42
|
-
const options = this.core.options ?? {}
|
|
43
|
-
|
|
44
|
-
if(options?.mockPath) {
|
|
45
|
-
debug("Discovering mock actions in `%s`", 2, options.mockPath)
|
|
46
|
-
|
|
47
|
-
files.push(
|
|
48
|
-
...(await getFiles([
|
|
49
|
-
`${options.mockPath}/bedoc-*-printer.js`,
|
|
50
|
-
`${options.mockPath}/bedoc-*-parser.js`,
|
|
51
|
-
])),
|
|
52
|
-
)
|
|
53
|
-
} else {
|
|
54
|
-
debug("Mock path not set, discovering actions in node_modules", 2)
|
|
55
|
-
|
|
56
|
-
debug("Looking for actions in project's package.json", 2)
|
|
57
|
-
if(this.core.packageJson) {
|
|
58
|
-
const exported = (this.core.packageJson.modules || [])
|
|
59
|
-
.map(m => composeFilename(options.basePath, m))
|
|
60
|
-
.flat()
|
|
61
|
-
|
|
62
|
-
debug("Found %o modules in project's package.json", 2, exported.length)
|
|
63
|
-
debug("Found modules in project's package.json: %o", 2, exported)
|
|
64
|
-
|
|
65
|
-
files.push(...exported)
|
|
66
|
-
} else {
|
|
67
|
-
debug("No modules found in project's package.json", 2)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
debug("Looking for modules in node_modules (global and locally installed)", 2)
|
|
71
|
-
const directories = [
|
|
72
|
-
execSync("npm root").toString().trim(),
|
|
73
|
-
execSync("npm root -g").toString().trim(),
|
|
74
|
-
].filter(Boolean)
|
|
75
|
-
|
|
76
|
-
const nodeModulesDirs = directories
|
|
77
|
-
.map(composeDirectory)
|
|
78
|
-
.filter(directoryExists)
|
|
79
|
-
|
|
80
|
-
debug("Found %o directories to search for actions", 2, directories.length)
|
|
81
|
-
debug("Directories to search for actions: %o", 3, directories)
|
|
82
|
-
|
|
83
|
-
for(const nodeModulesDir of nodeModulesDirs) {
|
|
84
|
-
const dirsToSearch = []
|
|
85
|
-
const {directories: moduleDirs} = await ls(nodeModulesDir.absolutePath)
|
|
86
|
-
|
|
87
|
-
debug("Found %o directories in %o", 2, moduleDirs.length, nodeModulesDir.absolutePath)
|
|
88
|
-
|
|
89
|
-
// Handle scoped packages (e.g., @bedoc/something)
|
|
90
|
-
const scopedDirs = moduleDirs.filter(d => d.name.startsWith("@"))
|
|
91
|
-
|
|
92
|
-
dirsToSearch.push(...moduleDirs)
|
|
93
|
-
|
|
94
|
-
// If we find a scope (e.g., "@bedoc"), look inside it for bedoc modules
|
|
95
|
-
for(const scopedDir of scopedDirs) {
|
|
96
|
-
const {directories: scopedPackages} = await ls(scopedDir.absolutePath)
|
|
97
|
-
|
|
98
|
-
debug("Found %o directories under scoped package %o", 2, directories.length, scopedDir.name)
|
|
99
|
-
debug("Found directories under scoped package %o\n%o", 2, scopedDir.absolutePath, scopedPackages.map(d => d.absolutePath))
|
|
100
|
-
|
|
101
|
-
dirsToSearch.push(...scopedPackages)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
debug("1 Found %o directories to search for actions", 2, dirsToSearch.length)
|
|
105
|
-
debug("2 Found directories to search for actions: %o", 3, dirsToSearch)
|
|
106
|
-
|
|
107
|
-
const visibleDirs = dirsToSearch.filter(d => !d.name.startsWith("."))
|
|
108
|
-
|
|
109
|
-
for(const dir of visibleDirs) {
|
|
110
|
-
const packageJsonFile = composeFilename(dir, "package.json")
|
|
111
|
-
|
|
112
|
-
if(!fileExists(packageJsonFile))
|
|
113
|
-
continue
|
|
114
|
-
|
|
115
|
-
const packageJson = loadDataFile(packageJsonFile)
|
|
116
|
-
if(!packageJson.bedoc)
|
|
117
|
-
continue
|
|
118
|
-
|
|
119
|
-
const {modules} = packageJson.bedoc ?? null
|
|
120
|
-
if(!modules || !Array.isArray(modules))
|
|
121
|
-
continue
|
|
122
|
-
|
|
123
|
-
const moduleFiles = modules
|
|
124
|
-
.map(f => composeFilename(dir, f))
|
|
125
|
-
.filter(f => fileExists(f))
|
|
126
|
-
|
|
127
|
-
debug("Discovered %d modules from package.json file: %o", 2,
|
|
128
|
-
modules.length,
|
|
129
|
-
packageJsonFile.absolutePath
|
|
130
|
-
)
|
|
131
|
-
debug("Discovered from package.json files: %o", 3, modules)
|
|
132
|
-
|
|
133
|
-
files.push(...moduleFiles)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
debug("Discovered %d modules", 2, files.length)
|
|
139
|
-
debug("Discovered modules", 2, files.map(f => f.path))
|
|
140
|
-
debug("Discovered modules %o", 3, files)
|
|
141
|
-
|
|
142
|
-
// const available = files.map(f => this.#getModuleExports(f))
|
|
143
|
-
|
|
144
|
-
return await this.#loadActionsAndContracts(files, specific)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Process the discovered file objects and return the action and their
|
|
149
|
-
* respective contracts.
|
|
150
|
-
*
|
|
151
|
-
* @param {object[]} moduleFiles The module file objects to process
|
|
152
|
-
* @param {object} specificModules The specific modules to load
|
|
153
|
-
* @returns {Promise<object>} The discovered action
|
|
154
|
-
*/
|
|
155
|
-
async #loadActionsAndContracts(moduleFiles, specificModules) {
|
|
156
|
-
const debug = this.#debug
|
|
157
|
-
|
|
158
|
-
debug("Loading actions and contracts", 2)
|
|
159
|
-
debug("Loading %d module files", 2, moduleFiles.length)
|
|
160
|
-
debug("Specific modules to load: %o", 3, specificModules)
|
|
161
|
-
|
|
162
|
-
const resultActions = {}
|
|
163
|
-
actionTypes.forEach(actionType => (resultActions[actionType] = []))
|
|
164
|
-
|
|
165
|
-
// Tag the specific actions to load, so we can filter them later
|
|
166
|
-
for(const [type, file] of Object.entries(specificModules)) {
|
|
167
|
-
if(file) {
|
|
168
|
-
debug("Tagging specific module `%s` as `%s`", 3, file.absolutePath, type)
|
|
169
|
-
file.specificType = file.specificType || []
|
|
170
|
-
file.specificType.push(type)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const toLoad = [
|
|
175
|
-
...moduleFiles,
|
|
176
|
-
...Object.values(specificModules).filter(Boolean),
|
|
177
|
-
]
|
|
178
|
-
|
|
179
|
-
debug("Loading %d discovered modules", 2, toLoad.length)
|
|
180
|
-
debug("Modules to load: %o", 3, toLoad)
|
|
181
|
-
|
|
182
|
-
const loadedActions = []
|
|
183
|
-
for(const file of toLoad) {
|
|
184
|
-
debug("Loading module `%s`", 2, file.absoluteUri)
|
|
185
|
-
|
|
186
|
-
const loading = await this.#loadModule(file)
|
|
187
|
-
for(let index = 0; index < loading.actions.length; index++) {
|
|
188
|
-
const action = loading.actions[index]
|
|
189
|
-
|
|
190
|
-
if(!file.directory)
|
|
191
|
-
file.directory = resolveDirectory(path.dirname(file.path))
|
|
192
|
-
|
|
193
|
-
debug(`Loading %o contract from %o`, 2, action.meta.action, file.path)
|
|
194
|
-
|
|
195
|
-
const terms = parse(loading.contracts[index], file.directory)
|
|
196
|
-
const contract = await newContract(action.meta.action, terms)
|
|
197
|
-
|
|
198
|
-
loadedActions.push({file, action, contract})
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
debug("Loaded %d actions", 2, loadedActions.length)
|
|
203
|
-
debug("Loaded actions", 3, loadedActions)
|
|
204
|
-
|
|
205
|
-
const filteredActions = []
|
|
206
|
-
for(const actionType of actionTypes) {
|
|
207
|
-
const module = specificModules[actionType]
|
|
208
|
-
const matchingActions = []
|
|
209
|
-
if(module) {
|
|
210
|
-
debug("Filtering actions for specific: %o", 2, actionType)
|
|
211
|
-
const found = loadedActions.find(
|
|
212
|
-
e => e.file.specificType?.includes(actionType) &&
|
|
213
|
-
e.action.meta?.action === actionType
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
if(!found)
|
|
217
|
-
throw new Error(`Could not find specific action: ${module.absolutePath}`)
|
|
218
|
-
|
|
219
|
-
matchingActions.push(found)
|
|
220
|
-
} else {
|
|
221
|
-
debug("No specific action required for `%s`", 2, actionType)
|
|
222
|
-
|
|
223
|
-
const found = loadedActions.filter(
|
|
224
|
-
e => e.action.meta.action === actionType
|
|
225
|
-
)
|
|
226
|
-
matchingActions.push(...found)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
debug("Filtered %d actions for `%s`", 2,
|
|
230
|
-
matchingActions.length, actionType
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
filteredActions.push(...matchingActions)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
debug("Filtered %d actions", 2, filteredActions.length)
|
|
237
|
-
debug("Filtered actions %o", 3, filteredActions)
|
|
238
|
-
|
|
239
|
-
// Now check the metas for validity
|
|
240
|
-
for(const filtered of filteredActions) {
|
|
241
|
-
const {action, contract, file: moduleFile} = filtered
|
|
242
|
-
const meta = action.meta
|
|
243
|
-
|
|
244
|
-
if(!meta)
|
|
245
|
-
throw new TypeError("Action has no meta object:\n" +
|
|
246
|
-
JSON.stringify(moduleFile, null, 2) + "\n" +
|
|
247
|
-
JSON.stringify(action, null, 2))
|
|
248
|
-
|
|
249
|
-
const metaAction = meta.action
|
|
250
|
-
if(!metaAction)
|
|
251
|
-
throw new TypeError("Action has no meta action:\n" +
|
|
252
|
-
JSON.stringify(moduleFile, null, 2) + "\n" +
|
|
253
|
-
JSON.stringify(action, null, 2))
|
|
254
|
-
|
|
255
|
-
debug("Checking action %o", 2, metaAction)
|
|
256
|
-
|
|
257
|
-
const isValid = this.#validMeta(metaAction, {action, contract})
|
|
258
|
-
|
|
259
|
-
debug("Meta in action %o in %o is %o", 3,
|
|
260
|
-
metaAction, moduleFile.module, isValid ? "valid" : "invalid"
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
if(isValid) {
|
|
264
|
-
debug("Action is a valid `%s` action", 3, metaAction)
|
|
265
|
-
|
|
266
|
-
resultActions[metaAction].push({file: moduleFile, action, contract})
|
|
267
|
-
} else {
|
|
268
|
-
debug("Action is not a valid `%s` action", 3, metaAction)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
debug("Processed action `%s`", 2, metaAction)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
for(const actionType of actionTypes) {
|
|
275
|
-
const total = resultActions[actionType].length
|
|
276
|
-
debug("Found %o %o actions", 2, total, actionType)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const total = Object.keys(resultActions).reduce((acc, curr) => {
|
|
280
|
-
return acc + resultActions[curr].length
|
|
281
|
-
}, 0)
|
|
282
|
-
|
|
283
|
-
debug("Loaded %d action definitions from %d modules", 2,
|
|
284
|
-
total, moduleFiles.length
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
return resultActions
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
satisfyCriteria(actions, validatedConfig) {
|
|
291
|
-
const debug = this.#debug
|
|
292
|
-
|
|
293
|
-
debug("Available actions to check %o", 3, actions)
|
|
294
|
-
|
|
295
|
-
const satisfied = {parse: [], print: []}
|
|
296
|
-
const toMatch = {
|
|
297
|
-
parse: {criterion: "language", config: "parser"},
|
|
298
|
-
print: {criterion: "format", config: "printer"}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
debug("Satisfying criteria for actions", 2)
|
|
302
|
-
for(const [actionType, search] of Object.entries(toMatch)) {
|
|
303
|
-
debug("Satisfying criteria for `%s` actions", 2, actionType)
|
|
304
|
-
|
|
305
|
-
const {criterion, config} = search
|
|
306
|
-
debug("Criterion: %s, Config: %s", 3, criterion, config)
|
|
307
|
-
|
|
308
|
-
// First let's check if we wanted something specific
|
|
309
|
-
if(validatedConfig[config]) {
|
|
310
|
-
debug("Checking for specific %o action", 3, actionType)
|
|
311
|
-
const found = actions[actionType].find(
|
|
312
|
-
a => a.file.specificType.includes(actionType)
|
|
313
|
-
)
|
|
314
|
-
if(found) {
|
|
315
|
-
debug("Found specific %o action", 3, actionType)
|
|
316
|
-
satisfied[actionType].push(found)
|
|
317
|
-
continue
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
debug("No specific `%s` action found", 3, actionType)
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Hmm! We didn't find anything specific. Let's check the criterion
|
|
324
|
-
debug("Checking for %o actions with criterion %o", 3, actionType, criterion)
|
|
325
|
-
debug("Validated config to check against: %o", 3, validatedConfig)
|
|
326
|
-
const found = actions[actionType].filter(a => {
|
|
327
|
-
debug("Meta criterion value: %o", 4, a.action.meta[criterion])
|
|
328
|
-
debug("Config criterion value: %o", 4, validatedConfig[criterion])
|
|
329
|
-
return a.action.meta[criterion] === validatedConfig[criterion]
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
debug("Found %o %o actions with criterion %o", 3,
|
|
333
|
-
found.length, actionType, criterion
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
// Shove them into the result!
|
|
337
|
-
satisfied[actionType].push(...found)
|
|
338
|
-
|
|
339
|
-
// That should about cover it!
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return satisfied
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Load a module and return its exports
|
|
347
|
-
*
|
|
348
|
-
* @param {object} module The module object to load
|
|
349
|
-
* @returns {Promise<object>} The module exports {actions, contracts}
|
|
350
|
-
*/
|
|
351
|
-
async #loadModule(module) {
|
|
352
|
-
const debug = this.#debug
|
|
353
|
-
|
|
354
|
-
debug("Loading module `%j`", 2, module)
|
|
355
|
-
|
|
356
|
-
const {absoluteUri} = module
|
|
357
|
-
const moduleExports = await import(absoluteUri)
|
|
358
|
-
|
|
359
|
-
return {file: module, ...moduleExports}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Validates the meta requirements for an action
|
|
364
|
-
*
|
|
365
|
-
* @param {string} actionType The action type to validate
|
|
366
|
-
* @param {object} toValidate - The action object to validate
|
|
367
|
-
* @returns {boolean} Whether the action object meets the meta requirements
|
|
368
|
-
*/
|
|
369
|
-
#validMeta(actionType, toValidate) {
|
|
370
|
-
const debug = this.#debug
|
|
371
|
-
debug("Checking meta requirements for %o", 3, actionType)
|
|
372
|
-
|
|
373
|
-
const requirements = actionMetaRequirements[actionType]
|
|
374
|
-
if(!requirements)
|
|
375
|
-
throw new Error(
|
|
376
|
-
`No meta requirements found for action type \`${actionType}\``,
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
for(const requirement of requirements) {
|
|
380
|
-
debug("Checking requirement %o", 4, requirement)
|
|
381
|
-
|
|
382
|
-
if(isType(requirement, "object")) {
|
|
383
|
-
for(const [key, value] of Object.entries(requirement)) {
|
|
384
|
-
debug("Checking object requirement %o", 4, {key, value})
|
|
385
|
-
|
|
386
|
-
if(toValidate.action.meta[key] !== value)
|
|
387
|
-
return false
|
|
388
|
-
|
|
389
|
-
debug("Requirement met: %o", 4, {key, value})
|
|
390
|
-
}
|
|
391
|
-
} else if(isType(requirement, "string")) {
|
|
392
|
-
debug("Checking string requirement: %s", 4, requirement)
|
|
393
|
-
|
|
394
|
-
if(!toValidate.action.meta[requirement])
|
|
395
|
-
return false
|
|
396
|
-
|
|
397
|
-
debug("Requirement met: %s", 4, requirement)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return true
|
|
402
|
-
}
|
|
403
|
-
}
|
package/src/core/HookManager.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import {setTimeout as timeoutPromise} from "timers/promises"
|
|
2
|
-
import * as DataUtil from "./util/DataUtil.js"
|
|
3
|
-
import * as ValidUtil from "./util/ValidUtil.js"
|
|
4
|
-
|
|
5
|
-
const {isEmpty, isType, allocateObject} = DataUtil
|
|
6
|
-
const {assert} = ValidUtil
|
|
7
|
-
|
|
8
|
-
const freeze = Object.freeze
|
|
9
|
-
|
|
10
|
-
const hookEvents = freeze(["start", "section_load", "enter", "exit", "end"])
|
|
11
|
-
export const HookPoints = freeze(
|
|
12
|
-
await allocateObject(
|
|
13
|
-
hookEvents.map(event => event.toUpperCase()),
|
|
14
|
-
hookEvents,
|
|
15
|
-
),
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
export default class HookManager {
|
|
19
|
-
#hooksFile = null
|
|
20
|
-
#log = null
|
|
21
|
-
#hooks = null
|
|
22
|
-
#action = null
|
|
23
|
-
#timeout = 1
|
|
24
|
-
|
|
25
|
-
constructor({action, hooksFile, logger, timeOut: timeout}) {
|
|
26
|
-
this.#action = action
|
|
27
|
-
this.#hooksFile = hooksFile
|
|
28
|
-
this.#log = logger
|
|
29
|
-
this.#timeout = timeout
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get action() {
|
|
33
|
-
return this.#action
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get hooksFile() {
|
|
37
|
-
return this.#hooksFile
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
get hooks() {
|
|
41
|
-
return this.#hooks
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
get log() {
|
|
45
|
-
return this.#log
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get timeout() {
|
|
49
|
-
return this.#timeout
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get setup() {
|
|
53
|
-
return this.hooks?.setup || null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
get cleanup() {
|
|
57
|
-
return this.hooks?.cleanup || null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static async new(arg) {
|
|
61
|
-
const instance = new HookManager(arg)
|
|
62
|
-
const debug = instance.log.newDebug()
|
|
63
|
-
|
|
64
|
-
debug("Creating new HookManager instance with args: `%o`", 2, arg)
|
|
65
|
-
|
|
66
|
-
const hooksFile = instance.hooksFile
|
|
67
|
-
|
|
68
|
-
debug("Loading hooks from `%s", 2, hooksFile.absoluteUri)
|
|
69
|
-
|
|
70
|
-
debug("Checking hooks file exists: %j", 2, hooksFile)
|
|
71
|
-
const hooksFileContent = await import(hooksFile.absoluteUri)
|
|
72
|
-
|
|
73
|
-
debug("Hooks file loaded successfully", 2)
|
|
74
|
-
|
|
75
|
-
if(!hooksFileContent)
|
|
76
|
-
throw new Error(`Hooks file is empty: ${hooksFile.absoluteUri}`)
|
|
77
|
-
|
|
78
|
-
const hooks = hooksFileContent.default || hooksFileContent.Hooks
|
|
79
|
-
|
|
80
|
-
if(!hooks)
|
|
81
|
-
throw new Error(`\`${hooksFile.absoluteUri}\` contains no hooks.`)
|
|
82
|
-
|
|
83
|
-
const hooksObj = hooks[instance.action]
|
|
84
|
-
if(isEmpty(hooksObj))
|
|
85
|
-
return null
|
|
86
|
-
|
|
87
|
-
debug("Hooks found for action: `%s`", 2, instance.action)
|
|
88
|
-
|
|
89
|
-
if(!hooksObj)
|
|
90
|
-
return null
|
|
91
|
-
|
|
92
|
-
hooksObj.log = instance.log
|
|
93
|
-
hooksObj.timeout = this.timeout
|
|
94
|
-
instance.#hooks = hooksObj
|
|
95
|
-
|
|
96
|
-
debug("Hooks loaded successfully for `%s`", 2, instance.action)
|
|
97
|
-
|
|
98
|
-
return instance
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Trigger a hook
|
|
103
|
-
*
|
|
104
|
-
* @param {string} event - The type of hook to trigger
|
|
105
|
-
* @param {object} args - The hook arguments as an object
|
|
106
|
-
* @returns {Promise<any>} The result of the hook
|
|
107
|
-
*/
|
|
108
|
-
async on(event, args) {
|
|
109
|
-
const debug = this.log.newDebug()
|
|
110
|
-
|
|
111
|
-
debug("Triggering hook for event `%s`", 4, event)
|
|
112
|
-
|
|
113
|
-
if(!event)
|
|
114
|
-
throw new Error("Event type is required for hook invocation")
|
|
115
|
-
|
|
116
|
-
if(!hookEvents.includes(event))
|
|
117
|
-
throw new Error(`Invalid event type: ${event}`)
|
|
118
|
-
|
|
119
|
-
const hook = this.hooks[event]
|
|
120
|
-
|
|
121
|
-
if(hook) {
|
|
122
|
-
assert(isType(hook, "function"), `Hook "${event}" is not a function`, 1)
|
|
123
|
-
|
|
124
|
-
const hookExecution = await hook.call(this.hooks, args)
|
|
125
|
-
const hookTimeout = this.timeout
|
|
126
|
-
const expireAsync = () =>
|
|
127
|
-
timeoutPromise(
|
|
128
|
-
hookTimeout,
|
|
129
|
-
new Error(`Hook execution exceeded timeout of ${hookTimeout}ms`),
|
|
130
|
-
)
|
|
131
|
-
const result = await Promise.race([hookExecution, expireAsync()])
|
|
132
|
-
|
|
133
|
-
if(result?.status === "error")
|
|
134
|
-
throw result.error
|
|
135
|
-
|
|
136
|
-
debug("Hook executed successfully for event: `%s`", 4, event)
|
|
137
|
-
|
|
138
|
-
return result
|
|
139
|
-
} else {
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import * as FDUtil from "./FDUtil.js"
|
|
2
|
-
import process from "node:process"
|
|
3
|
-
import JSON5 from "json5"
|
|
4
|
-
import YAML from "yaml"
|
|
5
|
-
|
|
6
|
-
const {readFile, fileExists, composeFilename} = FDUtil
|
|
7
|
-
|
|
8
|
-
const freeze = Object.freeze
|
|
9
|
-
|
|
10
|
-
const actionTypes = freeze(["parse", "print"])
|
|
11
|
-
|
|
12
|
-
const actionMetaRequirements = freeze({
|
|
13
|
-
parse: [{action: "parse"}, "language"],
|
|
14
|
-
print: [{action: "print"}, "format"],
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Loads an object from JSON or YAML provided a fileMap
|
|
19
|
-
*
|
|
20
|
-
* @param {object} fileMap - The FileObj file to load containing
|
|
21
|
-
* JSON or YAML text.
|
|
22
|
-
* @returns {object} The parsed data object.
|
|
23
|
-
*/
|
|
24
|
-
function loadDataFile(fileMap) {
|
|
25
|
-
const content = readFile(fileMap)
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
return JSON5.parse(content)
|
|
29
|
-
} catch{
|
|
30
|
-
try {
|
|
31
|
-
return YAML.parse(content)
|
|
32
|
-
} catch{
|
|
33
|
-
throw new Error("Content is neither valid JSON nor valid YAML")
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Loads the package.json file asynchronously
|
|
40
|
-
*
|
|
41
|
-
* @param {string|object|null} basePath - The base path to use
|
|
42
|
-
* @returns {object?} The parsed package.json content or null if the file does
|
|
43
|
-
* not exist
|
|
44
|
-
*/
|
|
45
|
-
function loadPackageJson(basePath = process.cwd()) {
|
|
46
|
-
const packageJsonFileObject = composeFilename(basePath, "./package.json")
|
|
47
|
-
if(fileExists(packageJsonFileObject)) {
|
|
48
|
-
const jsonContent = readFile(packageJsonFileObject)
|
|
49
|
-
const json = JSON5.parse(jsonContent)
|
|
50
|
-
return json
|
|
51
|
-
} else
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export {
|
|
56
|
-
// Constants
|
|
57
|
-
actionMetaRequirements,
|
|
58
|
-
actionTypes,
|
|
59
|
-
// Functions
|
|
60
|
-
loadDataFile,
|
|
61
|
-
loadPackageJson,
|
|
62
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fetch from "node-fetch"
|
|
2
|
-
import JSON5 from "json5"
|
|
3
|
-
import Ajv from "ajv"
|
|
4
|
-
import {fileURLToPath,URL} from "node:url"
|
|
5
|
-
|
|
6
|
-
import * as FDUtil from "./FDUtil.js"
|
|
7
|
-
|
|
8
|
-
const {composeFilename,fileExists,readFile,writeFile} = FDUtil
|
|
9
|
-
|
|
10
|
-
const schemaUrl = "https://bedoc.gesslar.dev/schemas/v1/bedoc.action.json"
|
|
11
|
-
const localSchema = "./dist/bedoc.action.json"
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Takes a schema and returns a validator function
|
|
15
|
-
*
|
|
16
|
-
* @param {object} schema The schema to compile
|
|
17
|
-
* @returns {Function} The schema validator function
|
|
18
|
-
*/
|
|
19
|
-
function getValidator(schema) {
|
|
20
|
-
const ajv = new Ajv({allErrors: true, verbose: true})
|
|
21
|
-
const f = ajv.compile(schema)
|
|
22
|
-
|
|
23
|
-
return f
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Downloads and preserves a copy of the action schema
|
|
28
|
-
* within the dist/ folder.
|
|
29
|
-
*
|
|
30
|
-
* @returns {object} The schema validator
|
|
31
|
-
*/
|
|
32
|
-
async function fetchSchema() {
|
|
33
|
-
const response = await fetch(schemaUrl)
|
|
34
|
-
const schema = await response.text()
|
|
35
|
-
|
|
36
|
-
const output = composeFilename(fileURLToPath(new URL("../../../", import.meta.url)), localSchema)
|
|
37
|
-
writeFile(output, schema)
|
|
38
|
-
|
|
39
|
-
return JSON5.parse(schema)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Loads a schema from file or fetches it if it is missing.
|
|
44
|
-
*
|
|
45
|
-
* @returns {object} The schema object
|
|
46
|
-
*/
|
|
47
|
-
async function loadSchema() {
|
|
48
|
-
const schemaFile = composeFilename(fileURLToPath(new URL("../../../", import.meta.url)), localSchema)
|
|
49
|
-
|
|
50
|
-
if(fileExists(schemaFile)) {
|
|
51
|
-
const schema = readFile(schemaFile)
|
|
52
|
-
|
|
53
|
-
return JSON5.parse(schema)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return await fetchSchema()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export {
|
|
60
|
-
fetchSchema,
|
|
61
|
-
getValidator,
|
|
62
|
-
loadSchema,
|
|
63
|
-
}
|