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
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { addEnvironment, loadEnvironments } from "../lib/le/le-environments.ts";
|
|
6
|
-
import { configureLogger, lockLogger, unlockLogger } from "../logger.ts";
|
|
7
|
-
import { findCliCommand, resetExitCode, runCliCommand } from "./cli-test-setup.ts";
|
|
8
|
-
|
|
9
|
-
let testHome = "";
|
|
10
|
-
let prevMiravoHome: string | undefined;
|
|
11
|
-
const originalFetch = globalThis.fetch;
|
|
12
|
-
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
resetExitCode();
|
|
15
|
-
prevMiravoHome = Bun.env["MIRAVO_HOME"];
|
|
16
|
-
testHome = await mkdtemp(join(tmpdir(), "miravo-env-command-test-"));
|
|
17
|
-
Bun.env["MIRAVO_HOME"] = testHome;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
globalThis.fetch = originalFetch;
|
|
22
|
-
if (prevMiravoHome === undefined) {
|
|
23
|
-
delete Bun.env["MIRAVO_HOME"];
|
|
24
|
-
} else {
|
|
25
|
-
Bun.env["MIRAVO_HOME"] = prevMiravoHome;
|
|
26
|
-
}
|
|
27
|
-
resetExitCode();
|
|
28
|
-
if (testHome) {
|
|
29
|
-
await rm(testHome, { recursive: true, force: true });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("miravo platforms env", () => {
|
|
34
|
-
test("platforms command is registered with env subcommand", async () => {
|
|
35
|
-
const { cmd } = await findCliCommand("platforms");
|
|
36
|
-
const env = cmd?.commands.find((command) => command.name() === "env");
|
|
37
|
-
expect(cmd).toBeDefined();
|
|
38
|
-
expect(env).toBeDefined();
|
|
39
|
-
expect(env?.aliases()).toContain("environments");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("add stores an environment and inspect masks token by default", async () => {
|
|
43
|
-
unlockLogger();
|
|
44
|
-
configureLogger({ plain: true });
|
|
45
|
-
lockLogger();
|
|
46
|
-
using logSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
47
|
-
|
|
48
|
-
await runCliCommand([
|
|
49
|
-
"platforms",
|
|
50
|
-
"env",
|
|
51
|
-
"add",
|
|
52
|
-
"--label",
|
|
53
|
-
"Prod",
|
|
54
|
-
"--hostname",
|
|
55
|
-
"le.example.local",
|
|
56
|
-
"--api-token",
|
|
57
|
-
"super-secret-token",
|
|
58
|
-
"--source-hostname",
|
|
59
|
-
"miravo.local",
|
|
60
|
-
]);
|
|
61
|
-
|
|
62
|
-
const envs = await loadEnvironments();
|
|
63
|
-
expect(envs).toHaveLength(1);
|
|
64
|
-
expect(envs[0]?.label).toBe("Prod");
|
|
65
|
-
expect(process.exitCode).toBe(0);
|
|
66
|
-
|
|
67
|
-
logSpy.mockClear();
|
|
68
|
-
await runCliCommand(["platforms", "env", "inspect", "Prod"]);
|
|
69
|
-
|
|
70
|
-
const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
|
|
71
|
-
expect(output).toContain("Prod");
|
|
72
|
-
expect(output).toContain("••••");
|
|
73
|
-
expect(output).not.toContain("super-secret-token");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("inspect --show-token prints full token", async () => {
|
|
77
|
-
const created = await addEnvironment({
|
|
78
|
-
platformId: "litmus-edge",
|
|
79
|
-
label: "Prod",
|
|
80
|
-
hostname: "le.example.local",
|
|
81
|
-
apiToken: "super-secret-token",
|
|
82
|
-
sourceHostname: "miravo.local",
|
|
83
|
-
});
|
|
84
|
-
unlockLogger();
|
|
85
|
-
configureLogger({ plain: true });
|
|
86
|
-
lockLogger();
|
|
87
|
-
using logSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
88
|
-
|
|
89
|
-
await runCliCommand(["platforms", "env", "inspect", created.id, "--show-token"]);
|
|
90
|
-
|
|
91
|
-
const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
|
|
92
|
-
expect(output).toContain("super-secret-token");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("update resolves by label and updates the environment", async () => {
|
|
96
|
-
await addEnvironment({
|
|
97
|
-
platformId: "litmus-edge",
|
|
98
|
-
label: "Prod",
|
|
99
|
-
hostname: "le.example.local",
|
|
100
|
-
apiToken: "super-secret-token",
|
|
101
|
-
sourceHostname: "miravo.local",
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
await runCliCommand(["platforms", "env", "update", "Prod", "--hostname", "new.example.local"]);
|
|
105
|
-
|
|
106
|
-
const envs = await loadEnvironments();
|
|
107
|
-
expect(envs[0]?.hostname).toBe("new.example.local");
|
|
108
|
-
expect(process.exitCode).toBe(0);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("remove resolves by id and deletes the environment", async () => {
|
|
112
|
-
const created = await addEnvironment({
|
|
113
|
-
platformId: "litmus-edge",
|
|
114
|
-
label: "Prod",
|
|
115
|
-
hostname: "le.example.local",
|
|
116
|
-
apiToken: "super-secret-token",
|
|
117
|
-
sourceHostname: "miravo.local",
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
await runCliCommand(["platforms", "env", "remove", created.id, "--yes"]);
|
|
121
|
-
|
|
122
|
-
const envs = await loadEnvironments();
|
|
123
|
-
expect(envs).toHaveLength(0);
|
|
124
|
-
expect(process.exitCode).toBe(0);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("inspect fails when label is ambiguous", async () => {
|
|
128
|
-
await addEnvironment({
|
|
129
|
-
platformId: "litmus-edge",
|
|
130
|
-
label: "Prod",
|
|
131
|
-
hostname: "le-1.example.local",
|
|
132
|
-
apiToken: "token-1",
|
|
133
|
-
sourceHostname: "miravo.local",
|
|
134
|
-
});
|
|
135
|
-
await addEnvironment({
|
|
136
|
-
platformId: "litmus-edge",
|
|
137
|
-
label: "prod",
|
|
138
|
-
hostname: "le-2.example.local",
|
|
139
|
-
apiToken: "token-2",
|
|
140
|
-
sourceHostname: "miravo.local",
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await runCliCommand(["platforms", "env", "inspect", "Prod"]);
|
|
144
|
-
|
|
145
|
-
expect(process.exitCode).toBe(1);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("test succeeds for a reachable environment", async () => {
|
|
149
|
-
await addEnvironment({
|
|
150
|
-
platformId: "litmus-edge",
|
|
151
|
-
label: "Prod",
|
|
152
|
-
hostname: "le.example.local",
|
|
153
|
-
apiToken: "super-secret-token",
|
|
154
|
-
sourceHostname: "miravo.local",
|
|
155
|
-
});
|
|
156
|
-
globalThis.fetch = (async (input) => {
|
|
157
|
-
const url = String(input);
|
|
158
|
-
expect(url).toContain("/sysinfo/version");
|
|
159
|
-
return Response.json({ version: "1.0.0" }, { status: 200 });
|
|
160
|
-
}) as typeof fetch;
|
|
161
|
-
unlockLogger();
|
|
162
|
-
configureLogger({ plain: true });
|
|
163
|
-
lockLogger();
|
|
164
|
-
using logSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
165
|
-
|
|
166
|
-
await runCliCommand(["platforms", "env", "test", "Prod"]);
|
|
167
|
-
|
|
168
|
-
const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
|
|
169
|
-
expect(output).toContain("connected");
|
|
170
|
-
expect(process.exitCode).toBe(0);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("test fails for unsupported platform ids", async () => {
|
|
174
|
-
await addEnvironment({
|
|
175
|
-
platformId: "coming-soon-platform",
|
|
176
|
-
label: "Future",
|
|
177
|
-
hostname: "future.example.local",
|
|
178
|
-
apiToken: "super-secret-token",
|
|
179
|
-
sourceHostname: "miravo.local",
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await runCliCommand(["platforms", "env", "test", "Future"]);
|
|
183
|
-
|
|
184
|
-
expect(process.exitCode).toBe(1);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import { confirm, isCancel } from "@clack/prompts";
|
|
2
|
-
import { MiravoError } from "@miravo/shared";
|
|
3
|
-
import type { Command } from "commander";
|
|
4
|
-
import { colors } from "consola/utils";
|
|
5
|
-
import {
|
|
6
|
-
assertSupportedEnvironmentPlatform,
|
|
7
|
-
testLeConnection,
|
|
8
|
-
} from "../lib/le/le-connection-test.ts";
|
|
9
|
-
import {
|
|
10
|
-
addEnvironment,
|
|
11
|
-
DEFAULT_PLATFORM_ID,
|
|
12
|
-
getEnvironment,
|
|
13
|
-
getEnvironmentByLabel,
|
|
14
|
-
type LEEnvironment,
|
|
15
|
-
loadEnvironments,
|
|
16
|
-
removeEnvironment,
|
|
17
|
-
updateEnvironment,
|
|
18
|
-
} from "../lib/le/le-environments.ts";
|
|
19
|
-
import { handleCommandError, writeOutput } from "../logger.ts";
|
|
20
|
-
import { formatKeyValue, formatTable } from "../output.ts";
|
|
21
|
-
|
|
22
|
-
interface SharedOptions {
|
|
23
|
-
readonly json?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface AddEnvironmentOptions extends SharedOptions {
|
|
27
|
-
readonly label?: string;
|
|
28
|
-
readonly hostname?: string;
|
|
29
|
-
readonly apiToken?: string;
|
|
30
|
-
readonly sourceHostname?: string;
|
|
31
|
-
readonly platform?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface UpdateEnvironmentOptions extends SharedOptions {
|
|
35
|
-
readonly label?: string;
|
|
36
|
-
readonly hostname?: string;
|
|
37
|
-
readonly apiToken?: string;
|
|
38
|
-
readonly sourceHostname?: string;
|
|
39
|
-
readonly platform?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface InspectEnvironmentOptions extends SharedOptions {
|
|
43
|
-
readonly showToken?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface RemoveEnvironmentOptions {
|
|
47
|
-
readonly yes?: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function maskToken(token: string): string {
|
|
51
|
-
if (token.length <= 8) return "••••••••";
|
|
52
|
-
return `${"•".repeat(token.length - 4)}${token.slice(-4)}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function userError(message: string, fix: string): MiravoError {
|
|
56
|
-
return new MiravoError(message, fix);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function resolveEnvironmentByIdOrLabel(idOrLabel: string): Promise<LEEnvironment> {
|
|
60
|
-
const byId = await getEnvironment(idOrLabel);
|
|
61
|
-
if (byId) return byId;
|
|
62
|
-
|
|
63
|
-
const envs = await loadEnvironments();
|
|
64
|
-
const lower = idOrLabel.toLowerCase();
|
|
65
|
-
const labelMatches = envs.filter((env) => env.label.toLowerCase() === lower);
|
|
66
|
-
|
|
67
|
-
if (labelMatches.length === 1) return labelMatches[0] as LEEnvironment;
|
|
68
|
-
if (labelMatches.length > 1) {
|
|
69
|
-
const ids = labelMatches.map((env) => env.id).join(", ");
|
|
70
|
-
throw userError(
|
|
71
|
-
`Multiple environments use label "${idOrLabel}".`,
|
|
72
|
-
`Use an id instead (${ids}).`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
throw userError(
|
|
77
|
-
`Environment "${idOrLabel}" not found.`,
|
|
78
|
-
"Use 'miravo platforms env list' to inspect ids and labels.",
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function printEnvironmentDetails(
|
|
83
|
-
env: LEEnvironment,
|
|
84
|
-
options: {
|
|
85
|
-
readonly json?: boolean | undefined;
|
|
86
|
-
readonly showToken?: boolean | undefined;
|
|
87
|
-
},
|
|
88
|
-
): void {
|
|
89
|
-
const token = options.showToken ? env.apiToken : maskToken(env.apiToken);
|
|
90
|
-
if (options.json) {
|
|
91
|
-
writeOutput(
|
|
92
|
-
JSON.stringify(
|
|
93
|
-
{
|
|
94
|
-
...env,
|
|
95
|
-
apiToken: token,
|
|
96
|
-
},
|
|
97
|
-
null,
|
|
98
|
-
2,
|
|
99
|
-
),
|
|
100
|
-
);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
writeOutput(
|
|
105
|
-
formatKeyValue([
|
|
106
|
-
["Id", env.id],
|
|
107
|
-
["Label", env.label],
|
|
108
|
-
["Platform", env.platformId],
|
|
109
|
-
["Hostname", env.hostname],
|
|
110
|
-
["Source Host", env.sourceHostname],
|
|
111
|
-
["API Token", token],
|
|
112
|
-
["Last Used", env.lastUsedAt ? new Date(env.lastUsedAt).toISOString() : "never"],
|
|
113
|
-
]),
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function printEnvironmentList(
|
|
118
|
-
envs: readonly LEEnvironment[],
|
|
119
|
-
jsonOutput: boolean | undefined,
|
|
120
|
-
): void {
|
|
121
|
-
if (jsonOutput) {
|
|
122
|
-
writeOutput(
|
|
123
|
-
JSON.stringify(
|
|
124
|
-
envs.map((env) => ({
|
|
125
|
-
...env,
|
|
126
|
-
apiToken: maskToken(env.apiToken),
|
|
127
|
-
})),
|
|
128
|
-
null,
|
|
129
|
-
2,
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (envs.length === 0) {
|
|
136
|
-
writeOutput("No environments configured.");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
writeOutput(
|
|
141
|
-
formatTable(
|
|
142
|
-
{
|
|
143
|
-
id: colors.dim("Id"),
|
|
144
|
-
label: colors.dim("Label"),
|
|
145
|
-
platform: colors.dim("Platform"),
|
|
146
|
-
hostname: colors.dim("Hostname"),
|
|
147
|
-
sourceHost: colors.dim("Source Host"),
|
|
148
|
-
lastUsed: colors.dim("Last Used"),
|
|
149
|
-
},
|
|
150
|
-
envs.map((env) => ({
|
|
151
|
-
id: env.id,
|
|
152
|
-
label: env.label,
|
|
153
|
-
platform: env.platformId,
|
|
154
|
-
hostname: env.hostname,
|
|
155
|
-
sourceHost: env.sourceHostname,
|
|
156
|
-
lastUsed: env.lastUsedAt ? new Date(env.lastUsedAt).toISOString() : "never",
|
|
157
|
-
})),
|
|
158
|
-
),
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function assertSupportedPlatform(platformId: string): void {
|
|
163
|
-
if (platformId !== DEFAULT_PLATFORM_ID) {
|
|
164
|
-
throw userError(
|
|
165
|
-
`Unsupported platform "${platformId}". Only "${DEFAULT_PLATFORM_ID}" is currently supported.`,
|
|
166
|
-
`Use --platform ${DEFAULT_PLATFORM_ID}.`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function registerEnvironmentsCommand(program: Command): void {
|
|
172
|
-
const platforms = program
|
|
173
|
-
.command("platforms")
|
|
174
|
-
.description("Manage platform integrations and saved environments");
|
|
175
|
-
|
|
176
|
-
const env = platforms
|
|
177
|
-
.command("env")
|
|
178
|
-
.alias("environments")
|
|
179
|
-
.description("Manage saved platform environments");
|
|
180
|
-
|
|
181
|
-
env
|
|
182
|
-
.command("list")
|
|
183
|
-
.description("List configured environments")
|
|
184
|
-
.option("--json", "Machine-readable JSON output")
|
|
185
|
-
.action(async (options: SharedOptions) => {
|
|
186
|
-
try {
|
|
187
|
-
const envs = await loadEnvironments();
|
|
188
|
-
printEnvironmentList(envs, options.json);
|
|
189
|
-
} catch (error) {
|
|
190
|
-
handleCommandError(error);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
env
|
|
195
|
-
.command("add")
|
|
196
|
-
.description("Add a new environment")
|
|
197
|
-
.requiredOption("--label <label>", "Environment label")
|
|
198
|
-
.requiredOption("--hostname <host>", "Litmus Edge hostname or IP")
|
|
199
|
-
.requiredOption("--api-token <token>", "Litmus Edge API token")
|
|
200
|
-
.requiredOption("--source-hostname <host>", "Hostname LE uses to reach Miravo")
|
|
201
|
-
.option("--platform <id>", `Platform id (default: ${DEFAULT_PLATFORM_ID})`, DEFAULT_PLATFORM_ID)
|
|
202
|
-
.option("--json", "Machine-readable JSON output")
|
|
203
|
-
.action(async (options: AddEnvironmentOptions) => {
|
|
204
|
-
try {
|
|
205
|
-
const platformId = options.platform ?? DEFAULT_PLATFORM_ID;
|
|
206
|
-
assertSupportedPlatform(platformId);
|
|
207
|
-
|
|
208
|
-
const existing = await getEnvironmentByLabel(options.label as string);
|
|
209
|
-
if (existing) {
|
|
210
|
-
throw userError(
|
|
211
|
-
`Environment label "${options.label}" already exists. Use 'miravo platforms env update ${existing.id}' instead.`,
|
|
212
|
-
`Choose a different --label or update ${existing.id}.`,
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const created = await addEnvironment({
|
|
217
|
-
platformId,
|
|
218
|
-
label: options.label as string,
|
|
219
|
-
hostname: options.hostname as string,
|
|
220
|
-
apiToken: options.apiToken as string,
|
|
221
|
-
sourceHostname: options.sourceHostname as string,
|
|
222
|
-
});
|
|
223
|
-
printEnvironmentDetails(created, { json: options.json });
|
|
224
|
-
} catch (error) {
|
|
225
|
-
handleCommandError(error);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
env
|
|
230
|
-
.command("inspect <idOrLabel>")
|
|
231
|
-
.description("Inspect an environment by id or label")
|
|
232
|
-
.option("--show-token", "Print the full API token", false)
|
|
233
|
-
.option("--json", "Machine-readable JSON output")
|
|
234
|
-
.action(async (idOrLabel: string, options: InspectEnvironmentOptions) => {
|
|
235
|
-
try {
|
|
236
|
-
const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
|
|
237
|
-
printEnvironmentDetails(resolved, options);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
handleCommandError(error);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
env
|
|
244
|
-
.command("update <idOrLabel>")
|
|
245
|
-
.description("Update an environment by id or label")
|
|
246
|
-
.option("--label <label>", "New environment label")
|
|
247
|
-
.option("--hostname <host>", "New Litmus Edge hostname or IP")
|
|
248
|
-
.option("--api-token <token>", "New Litmus Edge API token")
|
|
249
|
-
.option("--source-hostname <host>", "New hostname LE uses to reach Miravo")
|
|
250
|
-
.option("--platform <id>", "New platform id")
|
|
251
|
-
.option("--json", "Machine-readable JSON output")
|
|
252
|
-
.action(async (idOrLabel: string, options: UpdateEnvironmentOptions) => {
|
|
253
|
-
try {
|
|
254
|
-
const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
|
|
255
|
-
const hasUpdates =
|
|
256
|
-
options.label !== undefined ||
|
|
257
|
-
options.hostname !== undefined ||
|
|
258
|
-
options.apiToken !== undefined ||
|
|
259
|
-
options.sourceHostname !== undefined ||
|
|
260
|
-
options.platform !== undefined;
|
|
261
|
-
if (!hasUpdates) {
|
|
262
|
-
throw userError(
|
|
263
|
-
"No updates provided.",
|
|
264
|
-
"Pass at least one of --label, --hostname, --api-token, --source-hostname, or --platform.",
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
if (options.platform !== undefined) {
|
|
268
|
-
assertSupportedPlatform(options.platform);
|
|
269
|
-
}
|
|
270
|
-
if (options.label !== undefined) {
|
|
271
|
-
const byLabel = await getEnvironmentByLabel(options.label);
|
|
272
|
-
if (byLabel && byLabel.id !== resolved.id) {
|
|
273
|
-
throw userError(
|
|
274
|
-
`Environment label "${options.label}" already exists. Choose a different label.`,
|
|
275
|
-
"Choose a unique --label value.",
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const updated = await updateEnvironment(resolved.id, {
|
|
281
|
-
platformId: options.platform,
|
|
282
|
-
label: options.label,
|
|
283
|
-
hostname: options.hostname,
|
|
284
|
-
apiToken: options.apiToken,
|
|
285
|
-
sourceHostname: options.sourceHostname,
|
|
286
|
-
});
|
|
287
|
-
if (!updated) {
|
|
288
|
-
throw userError(
|
|
289
|
-
`Environment "${resolved.id}" not found.`,
|
|
290
|
-
"Run 'miravo platforms env list' and retry with an existing id.",
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
printEnvironmentDetails(updated, { json: options.json });
|
|
294
|
-
} catch (error) {
|
|
295
|
-
handleCommandError(error);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
env
|
|
300
|
-
.command("remove <idOrLabel>")
|
|
301
|
-
.description("Remove an environment by id or label")
|
|
302
|
-
.option("--yes", "Skip confirmation prompt", false)
|
|
303
|
-
.action(async (idOrLabel: string, options: RemoveEnvironmentOptions) => {
|
|
304
|
-
try {
|
|
305
|
-
const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
|
|
306
|
-
if (!options.yes) {
|
|
307
|
-
const confirmed = await confirm({
|
|
308
|
-
message: `Remove environment "${resolved.label}" (${resolved.id})?`,
|
|
309
|
-
});
|
|
310
|
-
if (isCancel(confirmed) || confirmed !== true) {
|
|
311
|
-
writeOutput("Canceled.");
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const removed = await removeEnvironment(resolved.id);
|
|
317
|
-
if (!removed) {
|
|
318
|
-
throw userError(
|
|
319
|
-
`Environment "${resolved.id}" not found.`,
|
|
320
|
-
"Run 'miravo platforms env list' and retry with an existing id.",
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
writeOutput(`Removed environment "${resolved.label}" (${resolved.id}).`);
|
|
324
|
-
} catch (error) {
|
|
325
|
-
handleCommandError(error);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
env
|
|
330
|
-
.command("test <idOrLabel>")
|
|
331
|
-
.description("Test connectivity for an environment by id or label")
|
|
332
|
-
.option("--json", "Machine-readable JSON output")
|
|
333
|
-
.action(async (idOrLabel: string, options: SharedOptions) => {
|
|
334
|
-
try {
|
|
335
|
-
const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
|
|
336
|
-
assertSupportedEnvironmentPlatform(resolved);
|
|
337
|
-
const result = await testLeConnection({
|
|
338
|
-
hostname: resolved.hostname,
|
|
339
|
-
apiToken: resolved.apiToken,
|
|
340
|
-
sourceHostname: resolved.sourceHostname,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
if (options.json) {
|
|
344
|
-
writeOutput(JSON.stringify(result, null, 2));
|
|
345
|
-
if (!result.connected) process.exitCode = 1;
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (result.connected) {
|
|
350
|
-
writeOutput(
|
|
351
|
-
formatKeyValue([
|
|
352
|
-
["Status", "connected"],
|
|
353
|
-
["Hostname", result.hostname],
|
|
354
|
-
["Source Host", result.sourceHostname],
|
|
355
|
-
]),
|
|
356
|
-
);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
writeOutput(
|
|
361
|
-
formatKeyValue([
|
|
362
|
-
["Status", "failed"],
|
|
363
|
-
["Error", result.error],
|
|
364
|
-
]),
|
|
365
|
-
);
|
|
366
|
-
process.exitCode = 1;
|
|
367
|
-
} catch (error) {
|
|
368
|
-
handleCommandError(error);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
package/src/commands/ps.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { EngineMetricsSnapshot } from "@miravo/shared";
|
|
2
|
-
import type { Command } from "commander";
|
|
3
|
-
import { isPidAlive, listEntries, sweepStaleEntries } from "../lib/daemon-registry.ts";
|
|
4
|
-
import { handleCommandError, writeOutput } from "../logger.ts";
|
|
5
|
-
import { formatTable } from "../output.ts";
|
|
6
|
-
|
|
7
|
-
interface PsOptions {
|
|
8
|
-
readonly json?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function registerPsCommand(program: Command): void {
|
|
12
|
-
program
|
|
13
|
-
.command("ps")
|
|
14
|
-
.description("List background simulations with live metrics")
|
|
15
|
-
.option("--json", "Machine-readable JSON output")
|
|
16
|
-
.addHelpText(
|
|
17
|
-
"after",
|
|
18
|
-
`
|
|
19
|
-
Examples:
|
|
20
|
-
$ miravo ps List background simulations
|
|
21
|
-
$ miravo ps --json Machine-readable JSON output`,
|
|
22
|
-
)
|
|
23
|
-
.action(async (options: PsOptions) => {
|
|
24
|
-
try {
|
|
25
|
-
await psAction(options);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
handleCommandError(error);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type PsRow = Record<string, string> & {
|
|
33
|
-
readonly name: string;
|
|
34
|
-
readonly status: string;
|
|
35
|
-
readonly uptime: string;
|
|
36
|
-
readonly ticks: string;
|
|
37
|
-
readonly instances: string;
|
|
38
|
-
readonly admin: string;
|
|
39
|
-
readonly mqtt: string;
|
|
40
|
-
readonly opcua: string;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
async function psAction(options: PsOptions): Promise<void> {
|
|
44
|
-
await sweepStaleEntries();
|
|
45
|
-
const entries = await listEntries();
|
|
46
|
-
|
|
47
|
-
if (entries.length === 0) {
|
|
48
|
-
writeOutput("No running simulations.");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const rows: PsRow[] = [];
|
|
53
|
-
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
const alive = isPidAlive(entry.pid);
|
|
56
|
-
let status = alive ? "running" : "dead";
|
|
57
|
-
let uptime = "-";
|
|
58
|
-
let ticks = "-";
|
|
59
|
-
let instances = "-";
|
|
60
|
-
|
|
61
|
-
if (alive) {
|
|
62
|
-
try {
|
|
63
|
-
const url = `http://${entry.adminHost}:${entry.adminPort}/metrics`;
|
|
64
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(2_000) });
|
|
65
|
-
if (response.ok) {
|
|
66
|
-
const metrics = (await response.json()) as EngineMetricsSnapshot;
|
|
67
|
-
status = metrics.core.state === "running" ? "running" : metrics.core.state;
|
|
68
|
-
uptime = formatUptime(metrics.core.uptimeMs);
|
|
69
|
-
ticks = String(metrics.core.tickCount);
|
|
70
|
-
instances = String(metrics.core.instanceCount);
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
status = "starting";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const mqttAdapter = entry.adapters["mqtt"];
|
|
78
|
-
const opcuaAdapter = entry.adapters["opcua"];
|
|
79
|
-
|
|
80
|
-
rows.push({
|
|
81
|
-
name: entry.name,
|
|
82
|
-
status,
|
|
83
|
-
uptime,
|
|
84
|
-
ticks,
|
|
85
|
-
instances,
|
|
86
|
-
admin: `:${entry.adminPort}`,
|
|
87
|
-
mqtt: mqttAdapter ? `:${mqttAdapter.port}` : "-",
|
|
88
|
-
opcua: opcuaAdapter ? `:${opcuaAdapter.port}` : "-",
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (options.json) {
|
|
93
|
-
writeOutput(JSON.stringify(rows, null, 2));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const headers: Record<string, string> = {
|
|
98
|
-
name: "NAME",
|
|
99
|
-
status: "STATUS",
|
|
100
|
-
uptime: "UPTIME",
|
|
101
|
-
ticks: "TICKS",
|
|
102
|
-
instances: "INSTANCES",
|
|
103
|
-
admin: "ADMIN",
|
|
104
|
-
mqtt: "MQTT",
|
|
105
|
-
opcua: "OPC UA",
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
writeOutput(formatTable(headers, rows));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function formatUptime(ms: number): string {
|
|
112
|
-
const totalSeconds = Math.floor(ms / 1_000);
|
|
113
|
-
const hours = Math.floor(totalSeconds / 3_600);
|
|
114
|
-
const minutes = Math.floor((totalSeconds % 3_600) / 60);
|
|
115
|
-
return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
116
|
-
}
|