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.
- package/LICENSE +199 -0
- package/README.md +29 -59
- package/bin/miravo.js +84 -0
- package/package.json +21 -37
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- 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
|
-
}
|
package/src/output.test.ts
DELETED
|
@@ -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
|
-
}
|