miravo 0.6.0 → 0.6.2

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 (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. package/src/output.ts +0 -160
package/src/logger.ts DELETED
@@ -1,402 +0,0 @@
1
- import { MiravoError } from "@miravo/shared";
2
- import type { ConsolaInstance } from "consola";
3
- import { createConsola } from "consola";
4
- import { colors, stripAnsi } from "consola/utils";
5
-
6
- type LogFormat = "pretty" | "json";
7
- type LogLevel = "debug" | "info" | "warn" | "error";
8
-
9
- const LOG_LEVEL_MAP: Readonly<Record<LogLevel, number>> = {
10
- debug: 4,
11
- info: 3,
12
- warn: 2,
13
- error: 0,
14
- };
15
-
16
- /**
17
- * Color map for log tags. Used in pretty mode to prepend a colored label.
18
- */
19
- /**
20
- * Aligned with Miravo brand palette (packages/shared/src/brand.css):
21
- * engine → yellow brand primary #e5a218 (amber)
22
- * mqtt → green brand success #10b981 (emerald)
23
- * opcua → blue brand chart-3 #3b82f6
24
- * modbus → cyan brand info #38bdf8 (sky blue)
25
- * config → gray low-priority setup messages
26
- * lifecycle → magenta brand chart-4 #8b5cf6 (violet)
27
- * fault → redBright brand destructive #f43f5e (rose)
28
- * control → blueBright lighter blue, distinct from opcua
29
- * platform → greenBright LE apply/cleanup operations
30
- */
31
- const TAG_COLORS: Readonly<Record<string, (s: string) => string>> = {
32
- engine: colors.yellow,
33
- mqtt: colors.green,
34
- opcua: colors.blue,
35
- modbus: colors.cyan,
36
- config: colors.gray,
37
- lifecycle: colors.magenta,
38
- fault: colors.redBright,
39
- control: colors.blueBright,
40
- platform: colors.greenBright,
41
- };
42
-
43
- /**
44
- * Display names for log tags. Tags are internal identifiers; these are
45
- * the user-facing labels shown at the start of each log line.
46
- */
47
- const TAG_DISPLAY_NAMES: Readonly<Record<string, string>> = {
48
- engine: "Engine",
49
- mqtt: "MQTT",
50
- opcua: "OPC UA",
51
- modbus: "Modbus",
52
- config: "Config",
53
- lifecycle: "Lifecycle",
54
- fault: "Fault",
55
- control: "Control",
56
- platform: "Platform",
57
- };
58
-
59
- /**
60
- * Root consola logger for the Miravo CLI.
61
- * `fancy: true` enables icons and color when a TTY is available.
62
- * consola automatically disables styling in non-TTY environments.
63
- *
64
- * We wrap the default FancyReporter to:
65
- * - Prepend a space to the first arg (breathing room after Unicode icons)
66
- * - Color-code tags by domain (engine=cyan, mqtt=green, etc.)
67
- */
68
- export const logger: ConsolaInstance = createConsola({
69
- ...({ fancy: true } as Record<string, unknown>),
70
- formatOptions: { date: true },
71
- });
72
-
73
- interface ReporterLogObject {
74
- date?: Date;
75
- level: number;
76
- type?: string;
77
- tag?: string;
78
- args: unknown[];
79
- }
80
-
81
- const fancyReporters = [...logger.options.reporters];
82
- const prettyReporter = {
83
- log(logObj: ReporterLogObject, ctx: unknown) {
84
- // Move tag from the right side to a colored prefix at the start of the message
85
- if (logObj.tag) {
86
- const rawTag = logObj.tag;
87
- const displayName = TAG_DISPLAY_NAMES[rawTag] ?? rawTag;
88
- const colorFn = TAG_COLORS[rawTag] ?? colors.gray;
89
- const prefix = `${colorFn(displayName)}${colors.dim(":")}`;
90
- if (logObj.args.length > 0 && typeof logObj.args[0] === "string") {
91
- logObj.args[0] = ` ${prefix} ${logObj.args[0]}`;
92
- } else {
93
- logObj.args.unshift(` ${prefix}`);
94
- }
95
- // Clear the tag so consola doesn't also render it on the right
96
- logObj.tag = "";
97
- } else if (logObj.args.length > 0 && typeof logObj.args[0] === "string") {
98
- logObj.args[0] = ` ${logObj.args[0]}`;
99
- }
100
- for (const reporter of fancyReporters) {
101
- reporter.log(logObj as never, ctx as never);
102
- }
103
- },
104
- };
105
-
106
- function normalizeMessageArgs(args: readonly unknown[]): string {
107
- return args
108
- .map((arg) => {
109
- if (arg instanceof Error) return arg.message;
110
- if (typeof arg === "string") return stripAnsi(arg);
111
- if (typeof arg === "object" && arg !== null) {
112
- try {
113
- return stripAnsi(JSON.stringify(arg));
114
- } catch {
115
- return stripAnsi(String(arg));
116
- }
117
- }
118
- return stripAnsi(String(arg));
119
- })
120
- .join(" ")
121
- .trim();
122
- }
123
-
124
- function levelToString(level: number): "trace" | "debug" | "info" | "warn" | "error" | "fatal" {
125
- if (level >= 5) return "trace";
126
- if (level === 4) return "debug";
127
- if (level === 3) return "info";
128
- if (level === 2) return "warn";
129
- if (level === 1) return "warn";
130
- if (level === 0) return "error";
131
- return "fatal";
132
- }
133
-
134
- const jsonReporter = {
135
- log(logObj: ReporterLogObject) {
136
- const errorArg = logObj.args.find((arg) => arg instanceof Error);
137
- const metadataArg = logObj.args.find(
138
- (arg) => typeof arg === "object" && arg !== null && !(arg instanceof Error),
139
- );
140
- const level = typeof logObj.type === "string" ? logObj.type : levelToString(logObj.level);
141
- const entry = {
142
- ts: (logObj.date ?? new Date()).toISOString(),
143
- level,
144
- type: logObj.type ?? "log",
145
- ...(logObj.tag ? { tag: stripAnsi(logObj.tag) } : {}),
146
- ...(runtimeLogContext.simulationName
147
- ? { simulationName: runtimeLogContext.simulationName }
148
- : {}),
149
- ...(runtimeLogContext.runId ? { runId: runtimeLogContext.runId } : {}),
150
- message: normalizeMessageArgs(logObj.args),
151
- ...(errorArg instanceof Error
152
- ? {
153
- error: {
154
- name: errorArg.name,
155
- message: errorArg.message,
156
- stack: errorArg.stack,
157
- },
158
- }
159
- : {}),
160
- ...(metadataArg !== undefined ? { metadata: metadataArg } : {}),
161
- };
162
- try {
163
- console.log(JSON.stringify(entry));
164
- } catch {
165
- const { metadata, ...safeEntry } = entry as Record<string, unknown>;
166
- console.log(JSON.stringify({ ...safeEntry, metadata: "[unserializable]" }));
167
- }
168
- },
169
- };
170
-
171
- /** Engine lifecycle and tick events. */
172
- export const engineLog: ConsolaInstance = logger.withTag("engine");
173
-
174
- /** MQTT adapter connection, publish, and broker events. */
175
- export const mqttLog: ConsolaInstance = logger.withTag("mqtt");
176
-
177
- /** Config loading, parsing, and validation events. */
178
- export const configLog: ConsolaInstance = logger.withTag("config");
179
-
180
- /** Instance lifecycle stage transitions. */
181
- export const lifecycleLog: ConsolaInstance = logger.withTag("lifecycle");
182
-
183
- /** Fault trigger and clear events. */
184
- export const faultLog: ConsolaInstance = logger.withTag("fault");
185
-
186
- /** OPC UA adapter connection and server events. */
187
- export const opcuaLog: ConsolaInstance = logger.withTag("opcua");
188
-
189
- /** Modbus TCP adapter connection and server events. */
190
- export const modbusLog: ConsolaInstance = logger.withTag("modbus");
191
-
192
- /** Control plane status and command routing events. */
193
- export const controlLog: ConsolaInstance = logger.withTag("control");
194
-
195
- /** Litmus Edge platform apply/cleanup operation events. */
196
- export const platformLog: ConsolaInstance = logger.withTag("platform");
197
-
198
- /** Route an adapter name to its dedicated logger. Falls back to engineLog for unknown names. */
199
- export function adapterLogger(name: string): ConsolaInstance {
200
- if (name === "mqtt") return mqttLog;
201
- if (name === "opcua") return opcuaLog;
202
- if (name === "modbus") return modbusLog;
203
- return engineLog;
204
- }
205
-
206
- /** All tag loggers, for bulk level updates. */
207
- const allLoggers: ConsolaInstance[] = [
208
- logger,
209
- engineLog,
210
- mqttLog,
211
- opcuaLog,
212
- modbusLog,
213
- configLog,
214
- lifecycleLog,
215
- faultLog,
216
- controlLog,
217
- platformLog,
218
- ];
219
-
220
- function applyLogReporter(format: LogFormat): void {
221
- const reporter = format === "json" ? jsonReporter : prettyReporter;
222
- for (const l of allLoggers) {
223
- l.setReporters([reporter]);
224
- }
225
- }
226
-
227
- /** Options for configuring log verbosity from CLI flags. */
228
- interface LoggerOptions {
229
- readonly verbose?: boolean | undefined;
230
- readonly quiet?: boolean | undefined;
231
- readonly plain?: boolean | undefined;
232
- readonly logFormat?: LogFormat | undefined;
233
- readonly logLevel?: LogLevel | undefined;
234
- }
235
-
236
- let _outputSuppressed = false;
237
- let _plainOutput = false;
238
- let _locked = false;
239
- let _logFormat: LogFormat = "pretty";
240
- const runtimeLogContext: { simulationName: string | undefined; runId: string | undefined } = {
241
- simulationName: undefined,
242
- runId: undefined,
243
- };
244
-
245
- export function parseLogFormat(raw: string): LogFormat {
246
- if (raw === "pretty" || raw === "json") return raw;
247
- throw new MiravoError(
248
- `Invalid log format "${raw}".`,
249
- "Use --log-format pretty or --log-format json.",
250
- );
251
- }
252
-
253
- export function parseLogLevel(raw: string): LogLevel {
254
- if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error") return raw;
255
- throw new MiravoError(
256
- `Invalid log level "${raw}".`,
257
- "Use --log-level debug | info | warn | error.",
258
- );
259
- }
260
-
261
- export function setRuntimeLogContext(context: { simulationName?: string; runId?: string }): void {
262
- runtimeLogContext.simulationName = context.simulationName;
263
- runtimeLogContext.runId = context.runId;
264
- }
265
-
266
- export function clearRuntimeLogContext(): void {
267
- runtimeLogContext.simulationName = undefined;
268
- runtimeLogContext.runId = undefined;
269
- }
270
-
271
- /**
272
- * Set the global log level based on CLI flags.
273
- * Priority: --quiet > --log-level > --verbose > default (info).
274
- *
275
- * Consola levels: 0 = errors/fatals, 2 = warn, 3 = info, 4 = debug.
276
- */
277
- export function configureLogger(opts: LoggerOptions): void {
278
- if (_locked) return;
279
- _logFormat = opts.logFormat ?? "pretty";
280
- applyLogReporter(_logFormat);
281
- _plainOutput = opts.plain ?? false;
282
-
283
- // NO_COLOR convention: disable colors when set to any non-empty value
284
- if (!_plainOutput && Bun.env["NO_COLOR"] !== undefined && Bun.env["NO_COLOR"] !== "") {
285
- _plainOutput = true;
286
- }
287
-
288
- let level: number;
289
- if (opts.quiet) {
290
- level = 0;
291
- _outputSuppressed = true;
292
- } else if (opts.logLevel) {
293
- level = LOG_LEVEL_MAP[opts.logLevel];
294
- _outputSuppressed = false;
295
- } else if (opts.verbose) {
296
- level = LOG_LEVEL_MAP.debug;
297
- _outputSuppressed = false;
298
- } else {
299
- level = LOG_LEVEL_MAP.info;
300
- _outputSuppressed = false;
301
- }
302
- for (const l of allLoggers) {
303
- l.level = level;
304
- }
305
- }
306
-
307
- /** Returns true when JSON log output is active. */
308
- export function isJsonLogFormat(): boolean {
309
- return _logFormat === "json";
310
- }
311
-
312
- /**
313
- * Write informational output to stdout, respecting --quiet.
314
- * Use this for primary formatted output (tables, boxes, status renders).
315
- * Do NOT use for machine-readable output (--json, --raw) — those should
316
- * always go through `process.stdout.write()` or `console.log()` directly.
317
- */
318
- export function writeOutput(...args: unknown[]): void {
319
- if (_outputSuppressed) return;
320
-
321
- if (_plainOutput) {
322
- if (args.length === 0) {
323
- console.log();
324
- return;
325
- }
326
-
327
- const normalized = args
328
- .map((arg) => {
329
- if (typeof arg === "string") {
330
- return stripAnsi(arg);
331
- }
332
- return stripAnsi(String(arg));
333
- })
334
- .join(" ");
335
-
336
- console.log(normalized);
337
- return;
338
- }
339
-
340
- console.log(...args);
341
- }
342
-
343
- /** Returns true when --quiet is active. */
344
- export function isQuiet(): boolean {
345
- return _outputSuppressed;
346
- }
347
-
348
- /**
349
- * Lock logger configuration so subsequent `configureLogger()` calls are no-ops.
350
- * Used by test setup to prevent Commander's preAction hook from resetting levels.
351
- */
352
- export function lockLogger(): void {
353
- _locked = true;
354
- }
355
-
356
- /** Unlock logger configuration. */
357
- export function unlockLogger(): void {
358
- _locked = false;
359
- }
360
-
361
- /** Returns true when --plain is active. */
362
- export function isPlain(): boolean {
363
- return _plainOutput;
364
- }
365
-
366
- /**
367
- * Handle a caught error in a CLI command action. If it's a `MiravoError`,
368
- * log the message and fix string and set a non-zero exit code. Otherwise
369
- * re-throw so unexpected errors surface with a full stack trace.
370
- */
371
- export function handleCommandError(error: unknown, log: ConsolaInstance = configLog): void {
372
- if (error instanceof MiravoError) {
373
- log.error(error.message);
374
- log.error(`Fix: ${error.fix}`);
375
- process.exitCode = 1;
376
- } else {
377
- throw error;
378
- }
379
- }
380
-
381
- interface CommandResultLike {
382
- readonly success: boolean;
383
- readonly message: string;
384
- readonly error?: string | undefined;
385
- }
386
-
387
- /**
388
- * Handle the result of an admin command, respecting --json output.
389
- * Writes success messages to stdout and errors to the control log.
390
- * Sets process.exitCode = 1 on failure.
391
- */
392
- export function handleCommandResult(result: CommandResultLike, json?: boolean): void {
393
- if (json) {
394
- console.log(JSON.stringify(result, null, 2));
395
- } else if (result.success) {
396
- writeOutput(result.message);
397
- } else {
398
- controlLog.error(result.message);
399
- if (result.error) controlLog.error(`Fix: ${result.error}`);
400
- }
401
- if (!result.success) process.exitCode = 1;
402
- }
@@ -1,64 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { colors, stripAnsi } from "consola/utils";
3
- import { formatDuration, formatKeyValue, formatTable } from "./output.ts";
4
-
5
- describe("formatTable", () => {
6
- test("aligns columns to widest value", () => {
7
- const result = formatTable({ name: "Name", sensors: "Sensors" }, [
8
- { name: "centrifugal-pump", sensors: "5" },
9
- { name: "conveyor", sensors: "12" },
10
- ]);
11
- const lines = result.split("\n");
12
- expect(lines).toHaveLength(3);
13
- expect(lines[0]).toContain("Name");
14
- expect(lines[0]).toContain("Sensors");
15
- expect(lines[1]).toContain("centrifugal-pump");
16
- expect(lines[2]).toContain("conveyor");
17
- });
18
-
19
- test("aligns columns correctly when cells contain ANSI color codes", () => {
20
- const result = formatTable({ name: colors.dim("Name"), count: colors.dim("Count") }, [
21
- { name: colors.cyan("short"), count: "1" },
22
- { name: colors.cyan("much-longer-name"), count: "2" },
23
- ]);
24
- const lines = result.split("\n");
25
- expect(lines).toHaveLength(3);
26
- const [header, row1, row2] = lines.map((l) => stripAnsi(l));
27
- const countCol0 = header?.indexOf("Count") ?? -1;
28
- const countCol1 = row1?.indexOf("1") ?? -1;
29
- const countCol2 = row2?.indexOf("2") ?? -1;
30
- expect(countCol0).toBeGreaterThan(0);
31
- expect(countCol0).toBe(countCol1);
32
- expect(countCol0).toBe(countCol2);
33
- });
34
- });
35
-
36
- describe("formatKeyValue", () => {
37
- test("aligns values by padding keys", () => {
38
- const result = formatKeyValue([
39
- ["Template", "plant.miravo.yaml"],
40
- ["Seed", "42"],
41
- ]);
42
- const lines = result.split("\n");
43
- expect(lines).toHaveLength(2);
44
- expect(lines[0]).toMatch(/^ {4}Template:/);
45
- expect(lines[1]).toMatch(/^ {4}Seed:/);
46
- expect(lines[0]).toContain("plant.miravo.yaml");
47
- expect(lines[1]).toContain("42");
48
- });
49
- });
50
-
51
- describe("formatDuration", () => {
52
- test("formats sub-second as milliseconds", () => {
53
- expect(formatDuration(500)).toBe("500ms");
54
- expect(formatDuration(0)).toBe("0ms");
55
- });
56
-
57
- test("formats minutes and seconds", () => {
58
- expect(formatDuration(323_000)).toBe("5m 23s");
59
- });
60
-
61
- test("formats exact hours with no trailing zeros", () => {
62
- expect(formatDuration(3_600_000)).toBe("1h");
63
- });
64
- });
package/src/output.ts DELETED
@@ -1,160 +0,0 @@
1
- /**
2
- * Shared output formatting for the Miravo CLI.
3
- * Uses consola/utils for ANSI-aware width calculation.
4
- */
5
-
6
- import type { AdapterHealthSnapshot, AdapterMetrics } from "@miravo/shared";
7
- import { colors, stripAnsi } from "consola/utils";
8
-
9
- /** A single row in a table, where each cell is a string value keyed by column key. */
10
- interface TableRow {
11
- readonly [key: string]: string;
12
- }
13
-
14
- /** Dim separator used between values on a single summary line. */
15
- export const sep = colors.dim(" \u00B7 ");
16
-
17
- /**
18
- * Format a list of objects as a column-aligned table.
19
- * Column widths are auto-calculated from header labels and cell values.
20
- * Each line is indented with a consistent prefix for clean CLI output.
21
- *
22
- * @param headers - Column definitions: `{ key: "Display Label" }`.
23
- * @param rows - Data rows with string values keyed by header keys.
24
- * @returns Multi-line string ready for console output.
25
- */
26
- export function formatTable(
27
- headers: Readonly<Record<string, string>>,
28
- rows: readonly TableRow[],
29
- ): string {
30
- const keys = Object.keys(headers);
31
- const gap = 3;
32
- const indent = " ";
33
-
34
- const widths = new Map<string, number>();
35
- for (const key of keys) {
36
- const headerLabel = headers[key];
37
- if (headerLabel === undefined) continue;
38
- let maxWidth = stripAnsi(headerLabel).length;
39
- for (const row of rows) {
40
- const value = row[key];
41
- if (value !== undefined) {
42
- const visible = stripAnsi(value).length;
43
- if (visible > maxWidth) {
44
- maxWidth = visible;
45
- }
46
- }
47
- }
48
- widths.set(key, maxWidth);
49
- }
50
-
51
- const headerLine = keys
52
- .map((key) => {
53
- const label = headers[key] ?? key;
54
- const target = widths.get(key) ?? stripAnsi(label).length;
55
- return label + " ".repeat(Math.max(0, target - stripAnsi(label).length));
56
- })
57
- .join(" ".repeat(gap));
58
-
59
- const dataLines = rows.map((row) =>
60
- keys
61
- .map((key) => {
62
- const value = row[key] ?? "";
63
- const target = widths.get(key) ?? stripAnsi(value).length;
64
- return value + " ".repeat(Math.max(0, target - stripAnsi(value).length));
65
- })
66
- .join(" ".repeat(gap)),
67
- );
68
-
69
- return [headerLine, ...dataLines].map((line) => `${indent}${line}`).join("\n");
70
- }
71
-
72
- /**
73
- * Format key-value pairs as aligned output.
74
- * Keys are right-padded to the width of the longest key.
75
- *
76
- * @example
77
- * formatKeyValue([["Template", "plant.miravo.yaml"], ["Seed", "42"]])
78
- * // " Template: plant.miravo.yaml\n Seed: 42"
79
- */
80
- export function formatKeyValue(entries: ReadonlyArray<readonly [string, string]>): string {
81
- if (entries.length === 0) return "";
82
-
83
- const maxKeyWidth = Math.max(...entries.map(([key]) => stripAnsi(key).length));
84
-
85
- return entries
86
- .map(([key, value]) => {
87
- const keyWithColon = `${key}:`;
88
- const visibleLen = stripAnsi(keyWithColon).length;
89
- const padded = keyWithColon + " ".repeat(Math.max(0, maxKeyWidth + 2 - visibleLen));
90
- return ` ${padded} ${value}`;
91
- })
92
- .join("\n");
93
- }
94
-
95
- /**
96
- * Format a number with locale-aware comma separators.
97
- * Used for tick counts, message counts, etc.
98
- */
99
- export function formatCount(value: number): string {
100
- return value.toLocaleString("en-US");
101
- }
102
-
103
- /**
104
- * Format milliseconds as a human-readable duration string.
105
- * Examples: "0s", "1.2s", "5m 23s", "1h 2m 15s"
106
- */
107
- export function formatDuration(ms: number): string {
108
- if (ms < 1_000) return `${ms.toFixed(0)}ms`;
109
-
110
- const totalSeconds = Math.floor(ms / 1_000);
111
- const hours = Math.floor(totalSeconds / 3_600);
112
- const minutes = Math.floor((totalSeconds % 3_600) / 60);
113
- const seconds = totalSeconds % 60;
114
-
115
- const parts: string[] = [];
116
- if (hours > 0) parts.push(`${hours}h`);
117
- if (minutes > 0) parts.push(`${minutes}m`);
118
- if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
119
-
120
- return parts.join(" ");
121
- }
122
-
123
- /**
124
- * Format the health suffix for an adapter line in pretty/plain CLI output.
125
- * Returns a string like: " · State: healthy" or " · Pending: 18,420 · State: saturated"
126
- */
127
- export function formatHealthSuffix(metrics: AdapterMetrics, sep: string): string {
128
- const health = metrics.health;
129
- if (!health) return "";
130
-
131
- let suffix = "";
132
-
133
- if (health.pendingWorkCount > 0) {
134
- suffix += `${sep}Pending: ${colors.yellow(formatCount(health.pendingWorkCount))}`;
135
- }
136
-
137
- suffix += `${sep}State: ${healthStateColor(health.state)}`;
138
-
139
- return suffix;
140
- }
141
-
142
- function healthStateColor(state: AdapterHealthSnapshot["state"]): string {
143
- switch (state) {
144
- case "healthy":
145
- return colors.green(state);
146
- case "busy":
147
- return colors.yellow(state);
148
- case "saturated":
149
- return colors.red(state);
150
- }
151
- }
152
-
153
- /**
154
- * Compute average throughput over the entire run.
155
- * Returns null when there's no meaningful data (zero uptime or zero messages).
156
- */
157
- export function computeAverageThroughput(totalMessages: number, uptimeMs: number): number | null {
158
- if (uptimeMs <= 0 || totalMessages === 0) return null;
159
- return Math.round(totalMessages / (uptimeMs / 1_000));
160
- }