@gesslar/bedoc 1.10.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.
Files changed (89) hide show
  1. package/LICENSE.txt +12 -0
  2. package/README.md +15 -3
  3. package/dist/schema/bedoc.action.json +42 -0
  4. package/dist/types/Action.d.ts +3 -0
  5. package/dist/types/Action.d.ts.map +1 -0
  6. package/dist/types/BeDoc.d.ts +208 -0
  7. package/dist/types/BeDoc.d.ts.map +1 -0
  8. package/dist/types/Configuration.d.ts +11 -0
  9. package/dist/types/Configuration.d.ts.map +1 -0
  10. package/dist/types/ConfigurationParameters.d.ts +3 -0
  11. package/dist/types/ConfigurationParameters.d.ts.map +1 -0
  12. package/dist/types/Conveyor.d.ts +27 -0
  13. package/dist/types/Conveyor.d.ts.map +1 -0
  14. package/dist/types/Discovery.d.ts +215 -0
  15. package/dist/types/Discovery.d.ts.map +1 -0
  16. package/dist/types/Environment.d.ts +3 -0
  17. package/dist/types/Environment.d.ts.map +1 -0
  18. package/dist/types/Logger.d.ts +47 -0
  19. package/dist/types/Logger.d.ts.map +1 -0
  20. package/dist/types/Schema.d.ts +3 -0
  21. package/dist/types/Schema.d.ts.map +1 -0
  22. package/dist/types/cli.d.ts +2 -2
  23. package/dist/types/cli.d.ts.map +1 -10
  24. package/package.json +24 -23
  25. package/src/Action.js +9 -0
  26. package/src/BeDoc.js +276 -0
  27. package/src/CLIOutput.js +198 -0
  28. package/src/{core/Configuration.js → Configuration.js} +72 -58
  29. package/src/{core/ConfigurationParameters.js → ConfigurationParameters.js} +35 -27
  30. package/src/Conveyor.js +256 -0
  31. package/src/Discovery.js +442 -0
  32. package/src/Environment.js +8 -0
  33. package/src/{core/Logger.js → Logger.js} +30 -18
  34. package/src/Schema.js +6 -0
  35. package/src/cli.js +77 -34
  36. package/tsconfig.types.json +42 -0
  37. package/LICENSE +0 -24
  38. package/dist/types/core/ActionManager.d.ts +0 -58
  39. package/dist/types/core/ActionManager.d.ts.map +0 -10
  40. package/dist/types/core/Configuration.d.ts +0 -27
  41. package/dist/types/core/Configuration.d.ts.map +0 -10
  42. package/dist/types/core/ConfigurationParameters.d.ts +0 -38
  43. package/dist/types/core/ConfigurationParameters.d.ts.map +0 -10
  44. package/dist/types/core/Conveyor.d.ts +0 -49
  45. package/dist/types/core/Conveyor.d.ts.map +0 -10
  46. package/dist/types/core/Core.d.ts +0 -48
  47. package/dist/types/core/Core.d.ts.map +0 -10
  48. package/dist/types/core/Discovery.d.ts +0 -73
  49. package/dist/types/core/Discovery.d.ts.map +0 -10
  50. package/dist/types/core/HookManager.d.ts +0 -60
  51. package/dist/types/core/HookManager.d.ts.map +0 -10
  52. package/dist/types/core/Logger.d.ts +0 -63
  53. package/dist/types/core/Logger.d.ts.map +0 -10
  54. package/dist/types/core/action/ParseManager.d.ts +0 -8
  55. package/dist/types/core/action/ParseManager.d.ts.map +0 -10
  56. package/dist/types/core/action/PrintManager.d.ts +0 -8
  57. package/dist/types/core/action/PrintManager.d.ts.map +0 -10
  58. package/dist/types/core/util/ActionUtil.d.ts +0 -35
  59. package/dist/types/core/util/ActionUtil.d.ts.map +0 -10
  60. package/dist/types/core/util/DataUtil.d.ts +0 -52
  61. package/dist/types/core/util/DataUtil.d.ts.map +0 -10
  62. package/dist/types/core/util/FDUtil.d.ts +0 -171
  63. package/dist/types/core/util/FDUtil.d.ts.map +0 -10
  64. package/dist/types/core/util/ModuleUtil.d.ts +0 -27
  65. package/dist/types/core/util/ModuleUtil.d.ts.map +0 -10
  66. package/dist/types/core/util/StringUtil.d.ts +0 -5
  67. package/dist/types/core/util/StringUtil.d.ts.map +0 -10
  68. package/dist/types/core/util/TypeSpec.d.ts +0 -42
  69. package/dist/types/core/util/TypeSpec.d.ts.map +0 -10
  70. package/dist/types/core/util/ValidUtil.d.ts +0 -29
  71. package/dist/types/core/util/ValidUtil.d.ts.map +0 -10
  72. package/src/core/ActionManager.js +0 -147
  73. package/src/core/ContractManager.js +0 -112
  74. package/src/core/Conveyor.js +0 -185
  75. package/src/core/Core.js +0 -166
  76. package/src/core/Discovery.js +0 -403
  77. package/src/core/HookManager.js +0 -143
  78. package/src/core/action/ParseManager.js +0 -7
  79. package/src/core/action/PrintManager.js +0 -7
  80. package/src/core/contract/ParseContract.js +0 -7
  81. package/src/core/contract/PrintContract.js +0 -7
  82. package/src/core/util/ActionUtil.js +0 -53
  83. package/src/core/util/ContractUtil.js +0 -63
  84. package/src/core/util/DataUtil.js +0 -540
  85. package/src/core/util/FDUtil.js +0 -388
  86. package/src/core/util/ModuleUtil.js +0 -40
  87. package/src/core/util/StringUtil.js +0 -11
  88. package/src/core/util/TypeSpec.js +0 -114
  89. package/src/core/util/ValidUtil.js +0 -50
@@ -1,147 +0,0 @@
1
- import {HookPoints} from "./HookManager.js"
2
-
3
- export default class ActionManager {
4
- #action = null
5
- #hookManager = null
6
- #contract
7
- #log
8
- #debug
9
- #file
10
- #variables
11
-
12
- constructor({actionDefinition, logger, variables}) {
13
- this.#log = logger
14
- this.#debug = this.#log.newDebug()
15
- this.#variables = variables
16
-
17
- this.#initialize(actionDefinition)
18
- }
19
-
20
- #initialize(actionDefinition) {
21
- const debug = this.#debug
22
-
23
- debug("Setting up action", 2)
24
-
25
- const {action, file, contract} = actionDefinition
26
-
27
- if(!action)
28
- throw new Error("Action is required")
29
-
30
- if(!contract)
31
- throw new Error("Contract is required")
32
-
33
- this.#action = action
34
- this.#contract = contract
35
- this.#file = file
36
-
37
- debug("Action setup complete", 2)
38
- }
39
-
40
- get action() {
41
- return this.#action
42
- }
43
-
44
- get hookManager() {
45
- return this.#hookManager
46
- }
47
-
48
- set hookManager(hookManager) {
49
- if(this.hookManager)
50
- throw new Error("Hooks already set")
51
-
52
- this.action.hook = hookManager.on.bind(this.action)
53
- this.action.HOOKS = HookPoints
54
- this.#hookManager = hookManager
55
- this.action.hooks = hookManager.hooks
56
- }
57
-
58
- get contract() {
59
- return this.#contract
60
- }
61
-
62
- get meta() {
63
- return this.#action.meta
64
- }
65
-
66
- get log() {
67
- return this.#log
68
- }
69
-
70
- get variables() {
71
- return this.#variables
72
- }
73
-
74
- async #setupAction() {
75
- const setup = this.action?.setup
76
-
77
- if(!setup)
78
- return
79
-
80
- await this.action.setup.call(this.action, {log: this.#log})
81
- }
82
-
83
- async #cleanupAction() {
84
- const cleanup = this.action?.cleanup
85
-
86
- if(!cleanup)
87
- return
88
-
89
- await this.action.cleanup.call(this.action)
90
- }
91
-
92
- async #setupHooks() {
93
- const setup = this.hookManager?.setup
94
-
95
- if(!setup)
96
- return
97
-
98
- await this.hookManager.setup.call(
99
- this.hookManager.hooks, {
100
- action: this.action,
101
- variables: this.#variables,
102
- log: this.#log
103
- }
104
- )
105
- }
106
-
107
- async #cleanupHooks() {
108
- const cleanup = this.hookManager?.cleanup
109
-
110
- if(!cleanup)
111
- return
112
-
113
- await this.hookManager.cleanup.call(this.hookManager.hooks)
114
- }
115
-
116
- async setupAction() {
117
- this.#debug("Setting up action for %s", 2, this.meta.action)
118
-
119
- await this.#setupHooks()
120
- await this.#setupAction()
121
- }
122
-
123
- async runAction({file,content}) {
124
- const func = this.action.run
125
-
126
- if(!func)
127
- throw new Error(`No \`run\` function found for action \`${this.meta.action}\``)
128
-
129
- const actionResult = await func.call(
130
- this.action, {file, moduleContent: content}
131
- )
132
-
133
- return actionResult
134
- }
135
-
136
- async cleanupAction() {
137
- this.#debug("Post action", 2)
138
- this.#debug("Cleaning up action for %s", 2, this.meta.action)
139
-
140
- await this.#cleanupHooks()
141
- await this.#cleanupAction()
142
- }
143
-
144
- toString() {
145
- return `${this.#file?.module || "UNDEFINED"} (${this.meta?.action || "UNDEFINED"})`
146
- }
147
- }
@@ -1,112 +0,0 @@
1
- import yaml from "yaml"
2
- import JSON5 from "json5"
3
-
4
- import * as FDUtil from "./util/FDUtil.js"
5
- import * as ContractUtil from "./util/ContractUtil.js"
6
- import * as DataUtil from "./util/DataUtil.js"
7
-
8
- const {resolveFilename, readFile} = FDUtil
9
- const {loadSchema, getValidator} = ContractUtil
10
- const {findClosestMatch} = DataUtil
11
-
12
- const refex = /^ref:\/\/(.*)$/
13
-
14
- export default class ContractManager {
15
- static async newContract(actionType, terms) {
16
- // Load and validate against the BeDoc contract schema
17
- const schema = await loadSchema()
18
- const validator = getValidator(schema)
19
- const valid = validator(terms)
20
-
21
- if(!valid) {
22
- const error = ContractManager.reportValidationErrors(validator.errors)
23
-
24
- throw new Error(`Invalid contract terms:\n${error}`)
25
- }
26
-
27
- const dataValidator = getValidator({
28
- "$schema": "http://json-schema.org/draft-07/schema#",
29
- "$id": `${actionType} Schema`,
30
- title: `${actionType} Schema`,
31
- type: "object",
32
- properties: terms,
33
- })
34
-
35
- return new Contract(dataValidator)
36
- }
37
-
38
- static parse(contractData, directoryObject) {
39
- if(typeof contractData === "string") {
40
- const match = refex.exec(contractData)
41
-
42
- if(match)
43
- contractData = readFile(resolveFilename(match[1], directoryObject))
44
-
45
- return yaml.parse(String(contractData))
46
- }
47
-
48
- throw new Error(`Invalid contract data: ${JSON5.stringify(contractData)}`)
49
- }
50
-
51
- static reportValidationErrors(errors) {
52
- return errors.reduce((acc, error) => {
53
- let msg = `- "${error.instancePath || "(root)"}" ${error.message}`
54
-
55
- if(error.params) {
56
- const details = []
57
-
58
- if(error.params.type)
59
- details.push(` ➜ Expected type: ${error.params.type}`)
60
-
61
- if(error.params.missingProperty)
62
- details.push(` ➜ Missing required field: ${error.params.missingProperty}`)
63
-
64
- if(error.params.allowedValues) {
65
- details.push(` ➜ Allowed values: "${error.params.allowedValues.join('", "')}"`)
66
- details.push(` ➜ Received value: "${error.data}"`)
67
- const closestMatch =
68
- findClosestMatch(error.data, error.params.allowedValues)
69
- if(closestMatch)
70
- details.push(` ➜ Did you mean: "${closestMatch}"?`)
71
- }
72
-
73
- if(error.params.pattern)
74
- details.push(` ➜ Expected pattern: ${error.params.pattern}`)
75
-
76
- if(error.params.format)
77
- details.push(` ➜ Expected format: ${error.params.format}`)
78
-
79
- if(error.params.additionalProperty)
80
- details.push(` ➜ Unexpected property: ${error.params.additionalProperty}`)
81
-
82
- if(details.length)
83
- msg += `\n${details.join("\n")}`
84
- }
85
-
86
- return acc ? `${acc}\n${msg}` : msg
87
- }, "")
88
- }
89
- }
90
-
91
- class Contract {
92
- #validator = null
93
-
94
- constructor(validator) {
95
- this.#validator = validator
96
- }
97
-
98
- get validator() {
99
- return this.#validator
100
- }
101
-
102
- validate(data) {
103
- const validator = this.validator
104
- const valid = validator(data)
105
-
106
- if(!valid) {
107
- const error = ContractManager.reportValidationErrors(validator.errors)
108
-
109
- throw new Error(`This document violates the agreed upon contract:\n${error}`)
110
- }
111
- }
112
- }
@@ -1,185 +0,0 @@
1
- import {format} from "node:util"
2
-
3
- import * as FDUtil from "./util/FDUtil.js"
4
-
5
- const {readFile, writeFile, composeFilename} = FDUtil
6
-
7
- export default class Conveyor {
8
- #succeeded = []
9
- #warned = []
10
- #errored = []
11
-
12
- constructor(parse, print, logger, output) {
13
- this.parse = parse
14
- this.print = print
15
- this.logger = logger
16
- this.output = output
17
- }
18
-
19
- /**
20
- * Processes files with a concurrency limit.
21
- *
22
- * @param {Array} files - List of files to process.
23
- * @param {number} maxConcurrent - Maximum number of concurrent tasks.
24
- * @returns {Promise<object>} - Resolves when all files are processed.
25
- */
26
- async convey(files, maxConcurrent = 10) {
27
- const fileQueue = [...files]
28
- const activePromises = []
29
-
30
- await Promise.all([
31
- this.parse.setupAction(),
32
- this.print.setupAction()
33
- ])
34
-
35
- const processNextFile = file => {
36
- return this.#processFile(file).then(processedResult => {
37
- // Store result
38
- if(processedResult.status === "success") {
39
- this.#succeeded.push({input: file, output: processedResult.file})
40
- } else if(processedResult.status === "warning") {
41
- this.#warned.push({input: file, warning: processedResult.warning})
42
- } else {
43
- this.#errored.push({input: file, error: processedResult.error})
44
- }
45
-
46
- // Start next job if queue isn't empty
47
- if(fileQueue.length > 0) {
48
- const nextFile = fileQueue.shift()
49
- return processNextFile(nextFile)
50
- }
51
- })
52
- }
53
-
54
- // Initial fill of the worker pool
55
- while(activePromises.length < maxConcurrent && fileQueue.length > 0) {
56
- const file = fileQueue.shift()
57
- activePromises.push(processNextFile(file))
58
- }
59
-
60
- // Wait for all processing to complete
61
- await Promise.all(activePromises)
62
-
63
- await Promise.all([
64
- this.parse.cleanupAction(),
65
- this.print.cleanupAction()
66
- ])
67
-
68
- return {
69
- succeeded: this.#succeeded,
70
- errored: this.#errored,
71
- warned: this.#warned
72
- }
73
- }
74
-
75
- /**
76
- * Processes a single file.
77
- *
78
- * @param {object} file - FileMap object representing a file.
79
- * @returns {Promise<object>} - Resolves when the file is processed
80
- */
81
- async #processFile(file) {
82
- const debug = this.logger.newDebug()
83
- const {parse, print} = this
84
-
85
- try {
86
- debug("Processing file: %o", 2, file.path)
87
-
88
- // Step 1: Read file
89
- const fileContent = readFile(file)
90
- debug("Read file content %o (%o bytes)", 2, file.path, fileContent.length)
91
-
92
- // Step 2: Parse file
93
- const parseResult = await parse.runAction({
94
- file,
95
- content: fileContent
96
- })
97
- if(parseResult.status === "error")
98
- return parseResult
99
-
100
- if(parseResult.status === "warning")
101
- debug("Parsed file successfully, but with warnings: %o", 2, file.path)
102
- else
103
- debug("Parsed file successfully: %o", 2, file.path)
104
-
105
- if(!parseResult.result) {
106
- const mess = format("No content found in %o. No file written.", file.path)
107
- return {status: "warning", file, warning: mess}
108
- }
109
-
110
- parse.contract.validate(parseResult)
111
- print.contract.validate(parseResult)
112
-
113
- // Step 3: Print file
114
- const printResult = await print.runAction({
115
- file,
116
- content: parseResult.result,
117
- })
118
- if(printResult.status === "error")
119
- return printResult
120
-
121
- debug("Printed file successfully: %o", 2, file.path)
122
-
123
- // Step 4: Write output
124
- const {status: printStatus, destFile, destContent} = printResult
125
- const isNullish = value => value == null // Checks null or undefined
126
-
127
- switch(printStatus) {
128
- case "warning":
129
- case "error":
130
- return printResult
131
- case "success":
132
- if(isNullish(destFile) || isNullish(destContent))
133
- return {
134
- status: "warning",
135
- warning: format("No content or destination file for %o", file.path)
136
- }
137
-
138
- break
139
- default:
140
- throw new Error(`Invalid status received from printing ${file.module}`)
141
- }
142
-
143
- if(this.output) {
144
- const writeResult = await this.#writeOutput(destFile, destContent)
145
-
146
- if(writeResult.status === "success")
147
- debug("Wrote output %o (%o bytes)", 2, writeResult.file.path, destContent.length)
148
- else
149
- debug("Error writing output for: `%s`", 2, file.path)
150
-
151
- return writeResult
152
- }
153
-
154
- debug("Output not specified. Writing skipped.", 2)
155
-
156
- return {status: "success"}
157
-
158
- } catch(error) {
159
- return {status: "error", file, error}
160
- }
161
- }
162
-
163
- /**
164
- * Writes the output to the destination.
165
- *
166
- * @param {string} destFile - Destination file path.
167
- * @param {string} destContent - File content.
168
- * @returns {Promise<object>} - Resolves when the file is written.
169
- */
170
- async #writeOutput(destFile, destContent) {
171
- const debug = this.logger.newDebug()
172
-
173
- const destFileMap = composeFilename(this.output.path, destFile)
174
-
175
- debug("Writing output to %o => %o", 2, destFile, destFileMap.absolutePath)
176
-
177
- try {
178
- writeFile(destFileMap, destContent)
179
-
180
- return {status: "success", file: destFileMap}
181
- } catch(error) {
182
- return {status: "error", output: destFileMap, error}
183
- }
184
- }
185
- }
package/src/core/Core.js DELETED
@@ -1,166 +0,0 @@
1
- import {hrtime} from "node:process"
2
-
3
- import Discovery from "./Discovery.js"
4
- import HookManager from "./HookManager.js"
5
- import Logger from "./Logger.js"
6
- import ParseManager from "./action/ParseManager.js"
7
- import PrintManager from "./action/PrintManager.js"
8
- import Conveyor from "./Conveyor.js"
9
- import Configuration from "./Configuration.js"
10
-
11
- import * as ActionUtil from "./util/ActionUtil.js"
12
- import * as DataUtil from "./util/DataUtil.js"
13
- import * as FDUtil from "./util/FDUtil.js"
14
-
15
- const {loadPackageJson} = ActionUtil
16
- const {schemaCompare} = DataUtil
17
- const {getFiles} = FDUtil
18
-
19
- export const Environment = Object.freeze({
20
- EXTENSION: "extension",
21
- NPM: "npm",
22
- ACTION: "action",
23
- CLI: "cli",
24
- })
25
-
26
- export default class Core {
27
- constructor(options) {
28
- this.options = options
29
- const {debug: debugMode, debugLevel} = options
30
- this.logger = new Logger({name: "BeDoc", debugMode, debugLevel})
31
- this.packageJson = loadPackageJson(options.basePath)?.bedoc ?? {}
32
- this.debugOptions = this.logger.options
33
- }
34
-
35
- static async new({options, source}) {
36
- const config = new Configuration()
37
-
38
- const validConfig = await config.validate({options, source})
39
- if(validConfig.status === "error")
40
- throw new AggregateError(validConfig.errors,"BeDoc configuration failed")
41
-
42
- const instance = new Core({...validConfig, name: "BeDoc"})
43
- const debug = instance.logger.newDebug()
44
-
45
- debug("Creating new BeDoc instance with options: `%o`", 3, validConfig)
46
-
47
- const discovery = new Discovery(instance)
48
- const actionDefs = await discovery.discoverActions({
49
- print: validConfig.printer,
50
- parse: validConfig.parser
51
- })
52
-
53
- const validCrit = discovery.satisfyCriteria(actionDefs, validConfig)
54
-
55
- debug("Actions that met criteria: `%o`", 3, validCrit)
56
-
57
- if(Object.values(validCrit).some(arr => arr.length === 0))
58
- throw new Error("No found matching parser and printer")
59
-
60
- const validSchemas = {print: [], parse: []}
61
- let printers = validCrit.print.length
62
- while(printers--) {
63
- const printer = validCrit.print[printers]
64
- const printerSchema = printer.contract
65
- const satisfied = []
66
-
67
- for(const parser of validCrit.parse) {
68
- const parserSchema = parser.contract
69
- const result = schemaCompare(parserSchema, printerSchema)
70
- if(result.status === "success")
71
- satisfied.push(parser)
72
- }
73
-
74
- if(satisfied.length > 0) {
75
- validSchemas.print.push(printer)
76
- validSchemas.parse.push(...satisfied)
77
- }
78
- }
79
-
80
- const finalActions = {}
81
- for(const [key, value] of Object.entries(validSchemas)) {
82
- if(value.length === 0)
83
- throw new Error(`No matching ${key} found`)
84
-
85
- if(value.length > 1)
86
- throw new Error(`Multiple matching ${key} found`)
87
-
88
- finalActions[key] = validSchemas[key][0]
89
- }
90
-
91
- debug("Contracts satisfied between parser and printer", 2)
92
-
93
- // Adding to instance
94
- instance.actions = {}
95
- const {variables} = validConfig
96
- const managers = {print: PrintManager, parse: ParseManager}
97
- for(const [, actionDefinition] of Object.entries(finalActions)) {
98
- const {action: actionType} = actionDefinition.action.meta
99
-
100
- debug("Attaching %o action to instance", 2, actionType)
101
- instance.actions[actionType] =
102
- new managers[actionType] ({
103
- actionDefinition,
104
- logger: instance.logger,
105
- variables
106
- })
107
-
108
- if(validConfig.hooks) {
109
- const hookManager = await HookManager.new({
110
- action: actionType,
111
- hooksFile: validConfig.hooks,
112
- logger: new Logger(instance.debugOptions),
113
- timeout: validConfig.hooksTimeout,
114
- })
115
-
116
- if(hookManager)
117
- instance.actions[actionType].hookManager = hookManager
118
- }
119
- }
120
-
121
- return instance
122
- }
123
-
124
- async processFiles(glob) {
125
- const debug = this.logger.newDebug()
126
-
127
- debug("Starting file processing with conveyor", 1)
128
-
129
- const {output} = this.options
130
-
131
- const input = await getFiles(glob)
132
- if(!input?.length)
133
- throw new Error("No input files specified")
134
-
135
- // Instantiate the conveyor
136
- const conveyor = new Conveyor(
137
- this.actions.parse,
138
- this.actions.print,
139
- this.logger,
140
- output,
141
- )
142
-
143
- const processStart = hrtime.bigint()
144
-
145
- // Initiate the conveyor
146
- const processResult = await conveyor.convey(
147
- input, this.options.maxConcurrent
148
- )
149
-
150
- debug("Conveyor complete", 1)
151
-
152
- const processEnd = hrtime.bigint()
153
-
154
- const result = {
155
- totalFiles: input.length,
156
- succeeded: processResult.succeeded,
157
- warned: processResult.warned,
158
- errored: processResult.errored,
159
- duration: ((Number(processEnd - processStart)) / 1_000_000).toFixed(2)
160
- }
161
-
162
- debug("File processing complete", 1)
163
-
164
- return result
165
- }
166
- }