@gesslar/bedoc 1.11.0 → 2.1.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 (88) 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 +304 -0
  27. package/src/CLIOutput.js +198 -0
  28. package/src/{core/Configuration.js → Configuration.js} +73 -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 +86 -38
  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 -62
  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/StringUtil.js +0 -11
  87. package/src/core/util/TypeSpec.js +0 -114
  88. package/src/core/util/ValidUtil.js +0 -50
@@ -1,10 +1 @@
1
- {
2
- "version": 3,
3
- "file": "cli.d.ts",
4
- "sourceRoot": "",
5
- "sources": [
6
- "../../src/cli.js"
7
- ],
8
- "names": [],
9
- "mappings": ""
10
- }
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.js"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gesslar/bedoc",
3
- "version": "1.11.0",
3
+ "version": "2.1.0",
4
4
  "description": "Pluggable documentation engine for any language and format",
5
5
  "publisher": "gesslar",
6
6
  "author": "gesslar",
@@ -10,9 +10,6 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/gesslar/BeDoc.git"
12
12
  },
13
- "bugs": {
14
- "url": "https://github.com/gesslar/BeDoc/issues"
15
- },
16
13
  "bin": {
17
14
  "bedoc": "src/cli.js"
18
15
  },
@@ -32,30 +29,34 @@
32
29
  },
33
30
  "type": "module",
34
31
  "types": "dist/types/index.d.ts",
32
+ "engines": {
33
+ "node": ">=24"
34
+ },
35
35
  "scripts": {
36
- "lint:check": "npx eslint .",
37
- "lint:fix": "npx eslint . --fix"
36
+ "update": "npx npm-check-updates -u && npm install",
37
+ "lint": "npx eslint .",
38
+ "lint:fix": "npx eslint . --fix",
39
+ "pr": "gt submit -p --ai",
40
+ "patch": "npm version patch",
41
+ "minor": "npm version minor",
42
+ "major": "npm version major"
38
43
  },
39
44
  "dependencies": {
40
- "ajv": "^8.17.1",
41
- "commander": "^13.0.0",
42
- "dotenv": "^16.4.7",
43
- "error-stack-parser": "^2.1.4",
44
- "globby": "^14.0.2",
45
- "json5": "^2.2.3",
46
- "micromatch": "^4.0.8",
47
- "node-fetch": "^3.3.2",
48
- "yaml": "^2.7.0"
45
+ "@gesslar/actioneer": "^3.0.1",
46
+ "@gesslar/colours": "^1.0.0",
47
+ "@gesslar/negotiator": "^1.0.0",
48
+ "@gesslar/toolkit": "^5.5.1",
49
+ "commander": "^15.0.0",
50
+ "error-stack-parser": "^2.1.4"
49
51
  },
50
52
  "devDependencies": {
51
- "@stylistic/eslint-plugin-js": "^3.0.0",
52
- "@typescript-eslint/eslint-plugin": "^8.22.0",
53
- "@typescript-eslint/parser": "^8.22.0",
54
- "axios": "^1.7.9",
55
- "chokidar": "^4.0.3",
56
- "eslint": "^9.18.0",
57
- "eslint-plugin-jsdoc": "^50.6.3",
58
- "form-data": "^4.0.1"
53
+ "@gesslar/uglier": "^2.4.1",
54
+ "axios": "^1.17.0",
55
+ "chokidar": "^5.0.0",
56
+ "dotenv": "^17.4.2",
57
+ "eslint": "^10.4.1",
58
+ "form-data": "^4.0.5",
59
+ "node-fetch": "^3.3.2"
59
60
  },
60
61
  "keywords": [
61
62
  "documentation",
package/src/Action.js ADDED
@@ -0,0 +1,9 @@
1
+ import {Data} from "@gesslar/toolkit"
2
+
3
+ export default Data.deepFreezeObject({
4
+ actionTypes: ["parser", "formatter"],
5
+ actionMetaRequirements: {
6
+ parser: [{kind: "parser"}, "input"],
7
+ formatter: [{kind: "formatter"}, "format"],
8
+ },
9
+ })
package/src/BeDoc.js ADDED
@@ -0,0 +1,304 @@
1
+ import {Contract} from "@gesslar/negotiator"
2
+ import {Data, Sass, Tantrum} from "@gesslar/toolkit"
3
+ import {hrtime} from "node:process"
4
+
5
+ import Configuration from "./Configuration.js"
6
+ import Conveyor from "./Conveyor.js"
7
+ import Discovery from "./Discovery.js"
8
+
9
+ /**
10
+ * @import {DirectoryObject, FileObject, Glog} from "@gesslar/toolkit"
11
+ */
12
+
13
+ export default class BeDoc {
14
+ #glog
15
+ #options
16
+ #actionDefs
17
+ #validCrit
18
+ #validSchemas
19
+ #contract
20
+ #actions
21
+ #validateBeDocSchema
22
+ #hooks
23
+ #basePath
24
+ #cli
25
+
26
+ constructor({basePath, glog, cliOutput}) {
27
+ this.#glog = glog
28
+ this.#basePath = basePath
29
+ this.#cli = cliOutput
30
+ }
31
+
32
+ /**
33
+ * Resolve and validate configuration from all sources (CLI, environment,
34
+ * package.json, config file). This runs independently of any BeDoc instance
35
+ * so the resulting config object can be built first and shared with other
36
+ * consumers (e.g. CLIOutput) before a BeDoc instance exists.
37
+ *
38
+ * @param {object} args
39
+ * @param {object} args.options - The raw options (with sources) to resolve
40
+ * @param {string} args.source - The environment BeDoc is running in
41
+ * @returns {Promise<object>} The validated configuration object
42
+ */
43
+ static async resolveConfig({options, source}) {
44
+ const config = new Configuration()
45
+
46
+ return await config.validate({options, source})
47
+ }
48
+
49
+ /**
50
+ * Create a new instance of BeDoc.
51
+ *
52
+ * Configuration may be supplied pre-resolved (via {@link resolveConfig}) when
53
+ * a caller needs the config object before the instance exists — e.g. the CLI
54
+ * resolves it first to share with CLIOutput. Otherwise pass raw `options` and
55
+ * `source` and BeDoc resolves them itself, so any environment can construct a
56
+ * BeDoc in a single call.
57
+ *
58
+ * @param {object} args
59
+ * @param {object} [args.config] - Pre-validated configuration (see resolveConfig)
60
+ * @param {object} [args.options] - Raw options to resolve, if config is absent
61
+ * @param {string} [args.source] - The environment BeDoc is running in
62
+ * @param {DirectoryObject} [args.basePath] - The project base path
63
+ * @param {Glog} args.glog - The Glog logger instance
64
+ * @param {Function} args.validateBeDocSchema - The action schema validator
65
+ * @param {object} args.cliOutput - The CLI output instance
66
+ * @returns {Promise<BeDoc>} A new instance of BeDoc
67
+ */
68
+ static async new({
69
+ config, options, source, basePath, glog, validateBeDocSchema, cliOutput
70
+ }) {
71
+ const resolved = config ?? await BeDoc.resolveConfig({options, source})
72
+ const base = basePath ?? resolved.basePath ?? options?.basePath
73
+
74
+ const bedoc = new this({basePath: base, glog, cliOutput})
75
+
76
+ bedoc.#configure({config: resolved, glog, validateBeDocSchema})
77
+
78
+ const discovered = await bedoc.#discover()
79
+
80
+ if(!discovered) {
81
+ return {
82
+ status: "fail",
83
+ message: "No matching actions specified or discovered.",
84
+ }
85
+ }
86
+
87
+ return await (await bedoc.#negotiate())
88
+ .#validateActions()
89
+ .#setupHooks()
90
+ }
91
+
92
+ /**
93
+ * Apply validated configuration to this instance.
94
+ *
95
+ * @param {object} config - The validated configuration object
96
+ * @param {Glog} glog - The Glog logger instance
97
+ * @param {Function} validateBeDocSchema - The action schema validator
98
+ */
99
+ #configure({config, glog, validateBeDocSchema}) {
100
+ this.#glog = glog
101
+ this.#validateBeDocSchema = validateBeDocSchema
102
+
103
+ if(config.debug && config.debugLevel > 0)
104
+ glog.withLogLevel(config.debugLevel)
105
+ else
106
+ glog.withLogLevel(0)
107
+
108
+ if(config.status === "error")
109
+ throw Tantrum.new("BeDoc configuration failed", config.errors)
110
+
111
+ glog.debug("Creating new BeDoc instance with options: `%o`", 4, config)
112
+
113
+ this.#options = config
114
+ }
115
+
116
+ /**
117
+ * Discover and filter actions that match the configuration criteria.
118
+ *
119
+ * @returns {Promise<boolean>} Whether matching actions were found
120
+ */
121
+ async #discover() {
122
+ const glog = this.#glog
123
+ const options = this.#options
124
+
125
+ const discovery = new Discovery({options, glog})
126
+
127
+ this.#actionDefs = await discovery.discoverActions({
128
+ parser: options.parser,
129
+ formatter: options.formatter,
130
+ }, this.#validateBeDocSchema)
131
+
132
+ this.#validCrit = discovery.satisfyCriteria(this.#actionDefs, options)
133
+
134
+ glog.debug("Actions that met criteria %o", 4, this.#validCrit)
135
+
136
+ return !Object.values(this.#validCrit).some(arr => arr.length === 0)
137
+ }
138
+
139
+ /**
140
+ * Negotiate contracts between discovered parsers and formatters.
141
+ *
142
+ * @returns {Promise<BeDoc>} This object for chaining.
143
+ */
144
+ async #negotiate() {
145
+ const glog = this.#glog
146
+ const validSchemas = {parser: [], formatter: []}
147
+
148
+ let formatters = this.#validCrit.formatter.length
149
+
150
+ while(formatters--) {
151
+ const formatter = this.#validCrit.formatter[formatters]
152
+ const {terms: consumes} = formatter
153
+ const satisfied = []
154
+
155
+ for(const parser of this.#validCrit.parser) {
156
+ try {
157
+ const {terms: provides} = parser
158
+
159
+ const contract = await Contract.negotiate(provides, consumes)
160
+
161
+ satisfied.push({...parser, contract})
162
+ } catch(err) {
163
+ glog.error(err)
164
+
165
+ glog.debug("%o action incompatible with %o action", 3,
166
+ parser.action.default.meta.input,
167
+ formatter.action.default.meta.format
168
+ )
169
+ }
170
+ }
171
+
172
+ if(satisfied.length > 0) {
173
+ validSchemas.formatter.push(formatter)
174
+ validSchemas.parser.push(...satisfied)
175
+ }
176
+ }
177
+
178
+ this.#validSchemas = validSchemas
179
+
180
+ return this
181
+ }
182
+
183
+ /**
184
+ * Validate that exactly one action per type was negotiated, then
185
+ * instantiate the action classes.
186
+ *
187
+ * @returns {BeDoc} This object for chaining.
188
+ */
189
+ #validateActions() {
190
+ const glog = this.#glog
191
+
192
+ const schemas = {}
193
+
194
+ for(const [key, value] of Object.entries(this.#validSchemas)) {
195
+ if(value.length === 0)
196
+ throw Sass.new(`No matching '${key}' action found`)
197
+
198
+ if(value.length > 1)
199
+ throw Sass.new(`Multiple matching '${key}' actions found`)
200
+
201
+ schemas[key] = value[0]
202
+ }
203
+
204
+ this.#validSchemas = schemas
205
+ this.#contract = schemas.parser.contract
206
+
207
+ glog.debug("Contracts satisfied between parser and formatter", 2)
208
+
209
+ const actions = {}
210
+
211
+ for(const [, {action}] of Object.entries(this.#validSchemas)) {
212
+ const {kind} = action.default.meta
213
+
214
+ glog.debug("Assigning %o action", 2, kind)
215
+
216
+ actions[kind] = action.default
217
+ }
218
+
219
+ this.#actions = actions
220
+
221
+ return this
222
+ }
223
+
224
+ async #setupHooks() {
225
+ /** @type {Glog} */
226
+ const glog = this.#glog
227
+
228
+ if(!this.#options.hooks)
229
+ return this
230
+
231
+ /** @type {FileObject} */
232
+ const hooksFile = this.#options.hooks
233
+
234
+ if(!hooksFile)
235
+ return this
236
+
237
+ if(!await hooksFile.exists) {
238
+ glog.warn(`File not found: ${hooksFile.path}`)
239
+
240
+ return this
241
+ }
242
+
243
+ const loaded = await hooksFile.import()
244
+ if(!loaded)
245
+ return this
246
+
247
+ const hooks = {}
248
+ if(loaded.Parse && Data.isType(loaded.Parse, "Function"))
249
+ hooks.Parse = loaded.Parse
250
+
251
+ if(loaded.Format && Data.isType(loaded.Format, "Function"))
252
+ hooks.Format = loaded.Format
253
+
254
+ if(Data.isEmpty(hooks)) {
255
+ glog.warn(`No hooks found in ${hooksFile.path}`)
256
+
257
+ return this
258
+ }
259
+
260
+ this.#hooks = hooks
261
+
262
+ return this
263
+ }
264
+
265
+ async processFiles() {
266
+ const glog = this.#glog
267
+
268
+ glog.debug("Starting file processing with conveyor", 1)
269
+
270
+ const {input, output, maxConcurrent} = this.#options
271
+
272
+ if(!input?.length)
273
+ throw Sass.new("No input files specified")
274
+
275
+ const conveyor = new Conveyor({
276
+ parser: this.#actions.parser,
277
+ formatter: this.#actions.formatter,
278
+ contract: this.#contract,
279
+ hooks: this.#hooks,
280
+ glog,
281
+ output,
282
+ basePath: this.#basePath,
283
+ cli: this.#cli
284
+ })
285
+
286
+ const processStart = hrtime.bigint()
287
+ const processResult = await conveyor.convey(input, maxConcurrent)
288
+ const processEnd = hrtime.bigint()
289
+
290
+ glog.debug("Conveyor complete", 1)
291
+
292
+ const result = {
293
+ totalFiles: input.length,
294
+ succeeded: processResult.succeeded,
295
+ warned: processResult.warned,
296
+ errored: processResult.errored,
297
+ duration: ((Number(processEnd - processStart)) / 1_000_000).toFixed(2),
298
+ }
299
+
300
+ glog.debug("File processing complete", 1)
301
+
302
+ return result
303
+ }
304
+ }
@@ -0,0 +1,198 @@
1
+ import c from "@gesslar/colours"
2
+ import {FileSystem as FS, Notify, Term} from "@gesslar/toolkit"
3
+ import process from "node:process"
4
+ import {stripVTControlCharacters} from "node:util"
5
+
6
+ /**
7
+ * Glyph sets for the framed output blocks. The `unicode` set is used when the
8
+ * terminal can render box-drawing/geometric characters; `ascii` is the plain
9
+ * fallback. In the ascii set the status markers are shape-distinct (not just
10
+ * colour-distinct) so they remain legible when colour is also unavailable.
11
+ */
12
+ const GLYPHS = {
13
+ unicode: {
14
+ upper: "╭▸", upperDone: "╭─", mid: "│", lower: "╰▸", lowerDone: "╰─",
15
+ pending: "□", active: "▸", success: "■", warning: "■", error: "■",
16
+ },
17
+ ascii: {
18
+ upper: ",>", upperDone: ",-", mid: "|", lower: "`>", lowerDone: "`-",
19
+ pending: ".", active: ">", success: "#", warning: "!", error: "x",
20
+ },
21
+ }
22
+
23
+ /**
24
+ * Rough check for whether the terminal can render Unicode glyphs. Mirrors the
25
+ * common heuristic: everything outside the raw Linux kernel console is assumed
26
+ * capable on POSIX; on Windows, only modern terminals are.
27
+ *
28
+ * @returns {boolean} True if Unicode glyphs are safe to emit.
29
+ */
30
+ function supportsUnicode() {
31
+ if(process.platform === "win32")
32
+ return Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM)
33
+
34
+ return process.env.TERM !== "linux"
35
+ }
36
+
37
+ /**
38
+ * Handles CLI output for the BeDoc pipeline, rendering progress and status
39
+ * for each file as it moves through the read → parse → validate → format →
40
+ * write stages.
41
+ *
42
+ * Listens to a {@link Notify} instance and tracks per-file state in an
43
+ * internal map so that output can be updated in-place as each stage completes.
44
+ *
45
+ * Each file is drawn as a framed block: an Input line (top border), one line
46
+ * per pipeline stage, and an Output line (bottom border). Colour is emitted
47
+ * only when {@link Term.hasColor} is true (honouring NO_COLOR/FORCE_COLOR), and
48
+ * the glyph set degrades to ASCII when Unicode is unsupported.
49
+ */
50
+ export default class CLIOutput {
51
+ /** Pipeline stages, in order, matching the Conveyor activity names. */
52
+ #stages = ["read", "parse", "validate", "format", "write"]
53
+
54
+ /** The active glyph set (unicode or ascii). */
55
+ #g = supportsUnicode() ? GLYPHS.unicode : GLYPHS.ascii
56
+
57
+ #basePath
58
+
59
+ /** When true, only Input/Output lines are drawn (no per-stage lines). */
60
+ #terse
61
+
62
+ /**
63
+ * Tracks per-file state keyed by file path/identifier.
64
+ *
65
+ * @type {Map<string, object>}
66
+ */
67
+ #files = new Map()
68
+
69
+ constructor({config}) {
70
+ this.#basePath = config.basePath
71
+ this.#terse = Boolean(config.terse)
72
+
73
+ Notify.on("conveyor-start", this.#conveyorStart)
74
+ Notify.on("update-data", this.#updateData)
75
+
76
+ c.alias.set("border", "{F033}")
77
+ c.alias.set("fileName", "{<B}")
78
+ c.alias.set("fileSize", "{F070}")
79
+
80
+ c.alias.set("success", "{F064}")
81
+ c.alias.set("pending", "{F033}")
82
+ c.alias.set("warning", "{F178}")
83
+ c.alias.set("error", "{F124}")
84
+ }
85
+
86
+ /**
87
+ * Seeds the tracking map when the conveyor announces its work set.
88
+ *
89
+ * @param {Array<object>} ctx - One entry per file, each {file, output}.
90
+ */
91
+ #conveyorStart = ctx => {
92
+ ctx.forEach(e => this.#files.set(e.file, {
93
+ output: e.output,
94
+ stages: Object.fromEntries(this.#stages.map(s => [s, "pending"])),
95
+ size: {
96
+ input: undefined, // undefined = pending, null = err, 0 = warning, number = success
97
+ output: undefined, // undefined = pending, null = err, 0 = warning, number = success
98
+ }
99
+ }))
100
+ }
101
+
102
+ /**
103
+ * Applies a single update for a file and re-renders. Messages are either a
104
+ * size update ({kind: "input-size"|"output-size", value}) or a stage
105
+ * transition ({kind: "stage", stage, state}).
106
+ *
107
+ * @param {object} update - The update payload.
108
+ * @param {object} update.file - The file the update pertains to.
109
+ * @param {object} update.message - The update message (see above).
110
+ */
111
+ #updateData = ({file, message}) => {
112
+ const item = this.#files.get(file)
113
+
114
+ if(!item)
115
+ return
116
+
117
+ switch(message.kind) {
118
+ case "input-size":
119
+ item.size.input = message.value
120
+ break
121
+
122
+ case "output-size":
123
+ item.size.output = message.value
124
+ break
125
+
126
+ case "stage":
127
+ item.stages[message.stage] = message.state
128
+ break
129
+ }
130
+
131
+ this.render()
132
+ }
133
+
134
+ /**
135
+ * Maps a stage state to its coloured glyph.
136
+ *
137
+ * @param {string} state - One of pending|active|done|warning|error.
138
+ * @returns {string} The coloured marker.
139
+ */
140
+ #marker(state) {
141
+ switch(state) {
142
+ case "active": return `{pending}${this.#g.active}{/}`
143
+ case "done": return `{success}${this.#g.success}{/}`
144
+ case "warning": return `{warning}${this.#g.warning}{/}`
145
+ case "error": return `{error}${this.#g.error}{/}`
146
+ default: return `{pending}${this.#g.pending}{/}`
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Maps an input/output size to its coloured glyph. undefined is pending,
152
+ * null is error, 0 is warning, any other number is success.
153
+ *
154
+ * @param {number|null|undefined} size - The tracked size value.
155
+ * @returns {string} The coloured marker.
156
+ */
157
+ #sizeMarker(size) {
158
+ if(size === undefined)
159
+ return `{pending}${this.#g.pending}{/}`
160
+
161
+ if(size === null)
162
+ return `{error}${this.#g.error}{/}`
163
+
164
+ if(size === 0)
165
+ return `{warning}${this.#g.warning}{/}`
166
+
167
+ return `{success}${this.#g.success}{/}`
168
+ }
169
+
170
+ render(cls=true) {
171
+ const lines = []
172
+
173
+ for(const [file, {output, stages, size}] of this.#files) {
174
+ const srcRel = FS.toRelativePath(this.#basePath.path, file.path)
175
+ const outRel = FS.toRelativePath(this.#basePath.path, output.path)
176
+ const done = size.output !== undefined
177
+
178
+ lines.push(
179
+ `{border}${done ? this.#g.upperDone : this.#g.upper}{/}${this.#sizeMarker(size.input)} Input {fileName}${srcRel}{B>}` +
180
+ (size.input ? ` ({fileSize}${size.input.toLocaleString()}{/} bytes)` : "")
181
+ )
182
+
183
+ if(!this.#terse)
184
+ for(const stage of this.#stages)
185
+ lines.push(`{border}${this.#g.mid}{/} ${this.#marker(stages[stage])} ${stage}`)
186
+
187
+ lines.push(
188
+ `{border}${done ? this.#g.lowerDone : this.#g.lower}{/}${this.#sizeMarker(size.output)} Output {fileName}${outRel}{/}` +
189
+ (size.output ? ` ({fileSize}${size.output.toLocaleString()}{/} bytes)` : "")
190
+ )
191
+ }
192
+
193
+ const out = c`${lines.join("\n")}\n`
194
+
195
+ cls && Term.cls()
196
+ Term.write(Term.hasColor ? out : stripVTControlCharacters(out))
197
+ }
198
+ }