miravo 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +14 -150
  2. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  3. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  4. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  5. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  6. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  7. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  8. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  9. package/console-dist/assets/index-C3XMYq26.css +2 -0
  10. package/console-dist/assets/index-hUJSpKtB.js +119 -0
  11. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  12. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  13. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  14. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  15. package/console-dist/favicon.svg +37 -0
  16. package/console-dist/index.html +14 -0
  17. package/console-dist/miravo-logo.png +0 -0
  18. package/package.json +12 -9
  19. package/src/__tests__/integration/executable-methods-runner.ts +53 -45
  20. package/src/__tests__/integration/executable-methods.integration.ts +648 -80
  21. package/src/commands/adapter.ts +76 -0
  22. package/src/commands/cli-test-setup.ts +1 -1
  23. package/src/commands/fork.ts +14 -0
  24. package/src/commands/inject.test.ts +0 -24
  25. package/src/commands/inject.ts +16 -8
  26. package/src/commands/load.ts +167 -0
  27. package/src/commands/logs.ts +175 -0
  28. package/src/commands/models.test.ts +23 -2
  29. package/src/commands/models.ts +20 -0
  30. package/src/commands/pause.ts +67 -0
  31. package/src/commands/ps.ts +116 -0
  32. package/src/commands/remove.ts +38 -0
  33. package/src/commands/reset.ts +44 -0
  34. package/src/commands/rm.ts +97 -0
  35. package/src/commands/spawn.ts +45 -0
  36. package/src/commands/speed.ts +40 -0
  37. package/src/commands/start.test.ts +93 -0
  38. package/src/commands/start.ts +1043 -193
  39. package/src/commands/status.test.ts +0 -24
  40. package/src/commands/status.ts +12 -6
  41. package/src/commands/stop.test.ts +84 -0
  42. package/src/commands/stop.ts +158 -0
  43. package/src/commands/templates.test.ts +1 -14
  44. package/src/commands/templates.ts +13 -0
  45. package/src/index.ts +49 -7
  46. package/src/lib/admin-client.test.ts +35 -0
  47. package/src/lib/admin-client.ts +13 -1
  48. package/src/lib/admin-server.test.ts +339 -53
  49. package/src/lib/admin-server.ts +413 -62
  50. package/src/lib/daemon-registry.test.ts +429 -0
  51. package/src/lib/daemon-registry.ts +345 -0
  52. package/src/lib/mqtt-client.ts +1 -1
  53. package/src/lib/parse.ts +18 -0
  54. package/src/lib/resolve-simulation.ts +1 -1
  55. package/src/lib/shutdown.test.ts +16 -4
  56. package/src/lib/shutdown.ts +3 -3
  57. package/src/lib/template-category.test.ts +1 -0
  58. package/src/lib/template-utils.ts +33 -2
  59. package/src/lib/ws-hub.ts +255 -0
  60. package/src/logger.test.ts +19 -19
  61. package/src/logger.ts +34 -6
@@ -0,0 +1,37 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <!-- Subtle mirror gradient -->
4
+ <linearGradient id="mirror" x1="0" y1="0" x2="0" y2="1">
5
+ <stop offset="0%" stop-color="#f6d28a"/>
6
+ <stop offset="50%" stop-color="#e5a218"/>
7
+ <stop offset="100%" stop-color="#c58514"/>
8
+ </linearGradient>
9
+ </defs>
10
+
11
+ <!-- Left M shape -->
12
+ <path d="M10 12 L30 12 L21 28 L10 28 Z"
13
+ fill="#e5a218"
14
+ stroke="#e5a218"
15
+ stroke-width="1.2"
16
+ stroke-linejoin="round"/>
17
+ <path d="M10 28 L21 28 L21 52 L10 48 Z"
18
+ fill="#e5a218"
19
+ stroke="#e5a218"
20
+ stroke-width="1.2"
21
+ stroke-linejoin="round"/>
22
+
23
+ <!-- Right mirrored M -->
24
+ <path d="M54 12 L34 12 L43 28 L54 28 Z"
25
+ fill="#e5a218"
26
+ stroke="#e5a218"
27
+ stroke-width="1.2"
28
+ stroke-linejoin="round"/>
29
+ <path d="M54 28 L43 28 L43 52 L54 48 Z"
30
+ fill="#e5a218"
31
+ stroke="#e5a218"
32
+ stroke-width="1.2"
33
+ stroke-linejoin="round"/>
34
+
35
+ <!-- Mirror axis (thin + subtle reflective feel) -->
36
+ <rect x="31.5" y="10" width="1" height="44" rx="0.5" fill="url(#mirror)" opacity="0.9"/>
37
+ </svg>
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Miravo</title>
8
+ <script type="module" crossorigin src="/assets/index-hUJSpKtB.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-C3XMYq26.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miravo",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Industrial environment platform with canonical asset models and MQTT + OPC UA protocol adapters",
6
6
  "license": "Apache-2.0",
@@ -27,21 +27,24 @@
27
27
  "miravo": "./src/index.ts"
28
28
  },
29
29
  "files": [
30
- "src/"
30
+ "src/",
31
+ "console-dist/"
31
32
  ],
32
33
  "dependencies": {
33
34
  "@clack/prompts": "^1.1.0",
34
- "@miravo/core": "0.2.3",
35
- "@miravo/mqtt": "0.2.3",
36
- "@miravo/opcua": "0.2.3",
37
- "@miravo/shared": "0.2.3",
35
+ "@miravo/core": "0.4.0",
36
+ "@miravo/mqtt": "0.4.0",
37
+ "@miravo/opcua": "0.4.0",
38
+ "@miravo/shared": "0.4.0",
38
39
  "commander": "^13.0.0",
39
40
  "consola": "^3.4.0",
40
- "mqtt": "^5.15.0"
41
+ "mqtt": "^5.15.0",
42
+ "yaml": "^2.8.1",
43
+ "zod": "^4.3.6"
41
44
  },
42
45
  "devDependencies": {
46
+ "@miravo/console": "0.0.0",
43
47
  "node-opcua-client": "^2.163.1",
44
- "node-opcua-variant": "^2.163.0",
45
- "yaml": "^2.8.1"
48
+ "node-opcua-variant": "^2.163.0"
46
49
  }
47
50
  }
@@ -9,6 +9,7 @@ import { MQTTAdapter } from "@miravo/mqtt";
9
9
  import { OPCUAAdapter } from "@miravo/opcua";
10
10
  import type { MiravoTemplate, TwinModel } from "@miravo/shared";
11
11
  import { startAdminServer } from "../../lib/admin-server.ts";
12
+ import { buildUNSBasePath, forEachTwin } from "../../lib/template-utils.ts";
12
13
 
13
14
  interface RunnerArgs {
14
15
  readonly template: string;
@@ -18,6 +19,10 @@ interface RunnerArgs {
18
19
  readonly rbeDeadband: number;
19
20
  }
20
21
 
22
+ interface ReadyHealthPayload {
23
+ readonly status?: unknown;
24
+ }
25
+
21
26
  function parseArgs(argv: readonly string[]): RunnerArgs {
22
27
  const values = new Map<string, string>();
23
28
  for (let index = 0; index < argv.length; index += 2) {
@@ -57,20 +62,6 @@ function resolveSimulationName(template: MiravoTemplate): string {
57
62
  return namespace.prefix.toLowerCase();
58
63
  }
59
64
 
60
- function buildUNSBasePath(template: MiravoTemplate, areaName: string, lineName: string): string {
61
- const segments: string[] = [];
62
- const namespace = template.namespace;
63
- if (namespace !== undefined) {
64
- if ("enterprise" in namespace) {
65
- segments.push(namespace.enterprise, namespace.site);
66
- } else {
67
- segments.push(namespace.prefix);
68
- }
69
- }
70
- segments.push(areaName, lineName);
71
- return segments.join("/");
72
- }
73
-
74
65
  async function loadModels(
75
66
  template: MiravoTemplate,
76
67
  modelsDir: string,
@@ -83,13 +74,9 @@ async function loadModels(
83
74
  }
84
75
 
85
76
  const referencedModelNames = new Set<string>();
86
- for (const area of template.areas) {
87
- for (const line of area.lines) {
88
- for (const twin of line.twins) {
89
- referencedModelNames.add(twin.model);
90
- }
91
- }
92
- }
77
+ forEachTwin(template, (_area, _line, twin) => {
78
+ referencedModelNames.add(twin.model);
79
+ });
93
80
 
94
81
  for (const modelName of referencedModelNames) {
95
82
  if (models.has(modelName)) {
@@ -103,6 +90,26 @@ async function loadModels(
103
90
  return models;
104
91
  }
105
92
 
93
+ async function waitForAdminReady(adminUrl: string, timeoutMs: number): Promise<void> {
94
+ const deadline = Date.now() + timeoutMs;
95
+ while (Date.now() < deadline) {
96
+ try {
97
+ const response = await fetch(new URL("/readyz", `${adminUrl}/`));
98
+ if (response.status === 200) {
99
+ const payload = (await response.json()) as ReadyHealthPayload;
100
+ if (payload.status === "ok") {
101
+ return;
102
+ }
103
+ }
104
+ } catch {
105
+ // Admin server can still be binding or the engine may not be running yet.
106
+ }
107
+ await Bun.sleep(25);
108
+ }
109
+
110
+ throw new Error(`Smoke runtime did not report ready within ${timeoutMs}ms.`);
111
+ }
112
+
106
113
  async function main(): Promise<void> {
107
114
  const args = parseArgs(process.argv.slice(2));
108
115
  const template = await loadTemplate(args.template);
@@ -127,17 +134,14 @@ async function main(): Promise<void> {
127
134
  engine.runtime.registerModel(model);
128
135
  }
129
136
 
130
- for (const area of template.areas) {
131
- for (const line of area.lines) {
132
- for (const twin of line.twins) {
133
- engine.runtime.spawn(twin.model, twin.count, {
134
- variation: twin.variation,
135
- unsBasePath: buildUNSBasePath(template, area.name, line.name),
136
- ...(twin.parameters !== undefined ? { parameterOverrides: twin.parameters } : {}),
137
- });
138
- }
139
- }
140
- }
137
+ forEachTwin(template, (area, line, twin) => {
138
+ engine.runtime.spawn(twin.model, twin.count, {
139
+ variation: twin.variation,
140
+ unsBasePath: buildUNSBasePath(template, area.name, line.name),
141
+ ...(twin.parameters !== undefined ? { parameterOverrides: twin.parameters } : {}),
142
+ ...(twin.starting_hours !== undefined ? { startingHours: twin.starting_hours } : {}),
143
+ });
144
+ });
141
145
 
142
146
  const mqttAdapter = new MQTTAdapter(engine.bus);
143
147
  await mqttAdapter.start({
@@ -184,19 +188,23 @@ async function main(): Promise<void> {
184
188
 
185
189
  const adminServer = startAdminServer({
186
190
  engine,
187
- host: "127.0.0.1",
188
- port: 0,
189
- tickIntervalMs,
190
- mqttEnabled: true,
191
- isMqttConnected: () => mqttAdapter.getMetrics().connected,
192
- opcuaEnabled: true,
193
- isOpcuaConnected: () => opcuaAdapter.getMetrics().connected,
194
- getLastTickProgressAt: () => lastTickProgressAt,
191
+ network: { host: "127.0.0.1", port: 0, tickIntervalMs },
192
+ protocols: {
193
+ getLastTickProgressAt: () => lastTickProgressAt,
194
+ },
195
+ ws: {
196
+ bus: engine.bus,
197
+ getEngineState: () => engine.getMetrics().core.state,
198
+ getTickInfo: () => ({
199
+ tick: engine.getMetrics().core.tickCount,
200
+ simulationTime: engine.getMetrics().core.simulationTimeSeconds,
201
+ instanceCount: engine.getMetrics().core.instanceCount,
202
+ }),
203
+ },
195
204
  });
196
205
 
197
- engine.start();
198
-
199
- await Bun.sleep(Math.max(250, tickIntervalMs * 3));
206
+ await engine.start();
207
+ await waitForAdminReady(adminServer.endpoint, Math.max(5_000, tickIntervalMs * 20));
200
208
 
201
209
  const [instanceId, instance] = [...engine.runtime.getInstances()][0] ?? [];
202
210
  if (!instanceId || !instance) {
@@ -237,10 +245,10 @@ async function main(): Promise<void> {
237
245
  process.exit(0);
238
246
  };
239
247
 
240
- process.on("SIGINT", () => {
248
+ process.once("SIGINT", () => {
241
249
  void shutdown();
242
250
  });
243
- process.on("SIGTERM", () => {
251
+ process.once("SIGTERM", () => {
244
252
  void shutdown();
245
253
  });
246
254