@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,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.10.0",
3
+ "version": "2.0.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,276 @@
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 {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
+ * Create a new instance of BeDoc.
34
+ *
35
+ * @param {object} args
36
+ * @param {object} args.options - The options passed into BeDoc
37
+ * @param {string} args.source - The environment BeDoc is running in
38
+ * @param {Glog} args.glog - The Glog logger instance
39
+ * @returns {Promise<BeDoc>} A new instance of BeDoc
40
+ */
41
+ static async new({options, source, glog, validateBeDocSchema, cliOutput}) {
42
+ const {basePath} = options
43
+
44
+ const bedoc = new this({basePath, glog, cliOutput})
45
+
46
+ await bedoc.#configure({options, source, glog, validateBeDocSchema})
47
+
48
+ const discovered = await bedoc.#discover()
49
+
50
+ if(!discovered) {
51
+ return {
52
+ status: "fail",
53
+ message: "No matching actions specified or discovered.",
54
+ }
55
+ }
56
+
57
+ return await (await bedoc.#negotiate())
58
+ .#validateActions()
59
+ .#setupHooks()
60
+ }
61
+
62
+ /**
63
+ * Validate configuration and store as options.
64
+ *
65
+ * @param {object} options - The raw options passed into BeDoc
66
+ * @param {string} source - The environment BeDoc is running in
67
+ */
68
+ async #configure({options, source, glog, validateBeDocSchema}) {
69
+ this.#glog = glog
70
+ this.#validateBeDocSchema = validateBeDocSchema
71
+
72
+ const config = new Configuration()
73
+ const validConfig = await config.validate({options, source})
74
+
75
+ if(validConfig.debug && validConfig.debugLevel > 0)
76
+ glog.withLogLevel(validConfig.debugLevel)
77
+ else
78
+ glog.withLogLevel(0)
79
+
80
+ if(validConfig.status === "error")
81
+ throw Tantrum.new("BeDoc configuration failed", validConfig.errors)
82
+
83
+ glog.debug("Creating new BeDoc instance with options: `%o`", 4, validConfig)
84
+
85
+ this.#options = validConfig
86
+ }
87
+
88
+ /**
89
+ * Discover and filter actions that match the configuration criteria.
90
+ *
91
+ * @returns {Promise<boolean>} Whether matching actions were found
92
+ */
93
+ async #discover() {
94
+ const glog = this.#glog
95
+ const options = this.#options
96
+
97
+ const discovery = new Discovery({options, glog})
98
+
99
+ this.#actionDefs = await discovery.discoverActions({
100
+ parser: options.parser,
101
+ formatter: options.formatter,
102
+ }, this.#validateBeDocSchema)
103
+
104
+ this.#validCrit = discovery.satisfyCriteria(this.#actionDefs, options)
105
+
106
+ glog.debug("Actions that met criteria %o", 4, this.#validCrit)
107
+
108
+ return !Object.values(this.#validCrit).some(arr => arr.length === 0)
109
+ }
110
+
111
+ /**
112
+ * Negotiate contracts between discovered parsers and formatters.
113
+ *
114
+ * @returns {Promise<BeDoc>} This object for chaining.
115
+ */
116
+ async #negotiate() {
117
+ const glog = this.#glog
118
+ const validSchemas = {parser: [], formatter: []}
119
+
120
+ let formatters = this.#validCrit.formatter.length
121
+
122
+ while(formatters--) {
123
+ const formatter = this.#validCrit.formatter[formatters]
124
+ const {terms: consumes} = formatter
125
+ const satisfied = []
126
+
127
+ for(const parser of this.#validCrit.parser) {
128
+ try {
129
+ const {terms: provides} = parser
130
+
131
+ const contract = await Contract.negotiate(provides, consumes)
132
+
133
+ satisfied.push({...parser, contract})
134
+ } catch(err) {
135
+ glog.error(err)
136
+
137
+ glog.debug("%o action incompatible with %o action", 3,
138
+ parser.action.default.meta.input,
139
+ formatter.action.default.meta.format
140
+ )
141
+ }
142
+ }
143
+
144
+ if(satisfied.length > 0) {
145
+ validSchemas.formatter.push(formatter)
146
+ validSchemas.parser.push(...satisfied)
147
+ }
148
+ }
149
+
150
+ this.#validSchemas = validSchemas
151
+
152
+ return this
153
+ }
154
+
155
+ /**
156
+ * Validate that exactly one action per type was negotiated, then
157
+ * instantiate the action classes.
158
+ *
159
+ * @returns {BeDoc} This object for chaining.
160
+ */
161
+ #validateActions() {
162
+ const glog = this.#glog
163
+
164
+ const schemas = {}
165
+
166
+ for(const [key, value] of Object.entries(this.#validSchemas)) {
167
+ if(value.length === 0)
168
+ throw Sass.new(`No matching '${key}' action found`)
169
+
170
+ if(value.length > 1)
171
+ throw Sass.new(`Multiple matching '${key}' actions found`)
172
+
173
+ schemas[key] = value[0]
174
+ }
175
+
176
+ this.#validSchemas = schemas
177
+ this.#contract = schemas.parser.contract
178
+
179
+ glog.debug("Contracts satisfied between parser and formatter", 2)
180
+
181
+ const actions = {}
182
+
183
+ for(const [, {action}] of Object.entries(this.#validSchemas)) {
184
+ const {kind} = action.default.meta
185
+
186
+ glog.debug("Assigning %o action", 2, kind)
187
+
188
+ actions[kind] = action.default
189
+ }
190
+
191
+ this.#actions = actions
192
+
193
+ return this
194
+ }
195
+
196
+ async #setupHooks() {
197
+ /** @type {Glog} */
198
+ const glog = this.#glog
199
+
200
+ if(!this.#options.hooks)
201
+ return this
202
+
203
+ /** @type {FileObject} */
204
+ const hooksFile = this.#options.hooks
205
+
206
+ if(!hooksFile)
207
+ return this
208
+
209
+ if(!await hooksFile.exists) {
210
+ glog.warn(`File not found: ${hooksFile.path}`)
211
+
212
+ return this
213
+ }
214
+
215
+ const loaded = await hooksFile.import()
216
+ if(!loaded)
217
+ return this
218
+
219
+ const hooks = {}
220
+ if(loaded.Parse && Data.isType(loaded.Parse, "Function"))
221
+ hooks.Parse = loaded.Parse
222
+
223
+ if(loaded.Format && Data.isType(loaded.Format, "Function"))
224
+ hooks.Format = loaded.Format
225
+
226
+ if(Data.isEmpty(hooks)) {
227
+ glog.warn(`No hooks found in ${hooksFile.path}`)
228
+
229
+ return this
230
+ }
231
+
232
+ this.#hooks = hooks
233
+
234
+ return this
235
+ }
236
+
237
+ async processFiles() {
238
+ const glog = this.#glog
239
+
240
+ glog.debug("Starting file processing with conveyor", 1)
241
+
242
+ const {input, output, maxConcurrent} = this.#options
243
+
244
+ if(!input?.length)
245
+ throw Sass.new("No input files specified")
246
+
247
+ const conveyor = new Conveyor({
248
+ parser: this.#actions.parser,
249
+ formatter: this.#actions.formatter,
250
+ contract: this.#contract,
251
+ hooks: this.#hooks,
252
+ glog,
253
+ output,
254
+ basePath: this.#basePath,
255
+ cli: this.#cli
256
+ })
257
+
258
+ const processStart = hrtime.bigint()
259
+ const processResult = await conveyor.convey(input, maxConcurrent)
260
+ const processEnd = hrtime.bigint()
261
+
262
+ glog.debug("Conveyor complete", 1)
263
+
264
+ const result = {
265
+ totalFiles: input.length,
266
+ succeeded: processResult.succeeded,
267
+ warned: processResult.warned,
268
+ errored: processResult.errored,
269
+ duration: ((Number(processEnd - processStart)) / 1_000_000).toFixed(2),
270
+ }
271
+
272
+ glog.debug("File processing complete", 1)
273
+
274
+ return result
275
+ }
276
+ }
@@ -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({basePath, terse = false}) {
70
+ this.#basePath = basePath
71
+ this.#terse = 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
+ }