miravo 0.2.2 → 0.3.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 +116 -30
  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-BXtW73cF.css +2 -0
  10. package/console-dist/assets/index-ChSocZFW.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 +20 -0
  16. package/console-dist/index.html +14 -0
  17. package/console-dist/miravo-logo.png +0 -0
  18. package/package.json +10 -7
  19. package/src/__tests__/integration/executable-methods-runner.ts +52 -45
  20. package/src/__tests__/integration/executable-methods.integration.ts +609 -47
  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 +1 -1
  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 +22 -1
  29. package/src/commands/models.ts +14 -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 +976 -192
  39. package/src/commands/status.test.ts +3 -1
  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 -1
  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,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <defs>
3
+ <clipPath id="rounded">
4
+ <rect width="64" height="64" rx="14" ry="14" fill="white"/>
5
+ </clipPath>
6
+ </defs>
7
+ <g clip-path="url(#rounded)">
8
+ <!-- background -->
9
+ <rect width="64" height="64" fill="#0a0a0f"/>
10
+
11
+ <!-- left M half -->
12
+ <polygon points="12,52 12,18 28,32 28,52" fill="#1FA6A6"/>
13
+
14
+ <!-- right M half -->
15
+ <polygon points="52,52 52,18 36,32 36,52" fill="#2F4258"/>
16
+
17
+ <!-- mirror divider -->
18
+ <rect x="31" y="14" width="2" height="40" fill="#5A6B7F"/>
19
+ </g>
20
+ </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-ChSocZFW.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BXtW73cF.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.2",
3
+ "version": "0.3.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,19 +27,22 @@
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.1",
35
- "@miravo/mqtt": "0.2.1",
36
- "@miravo/opcua": "0.2.1",
37
- "@miravo/shared": "0.2.1",
35
+ "@miravo/core": "0.3.0",
36
+ "@miravo/mqtt": "0.3.0",
37
+ "@miravo/opcua": "0.3.0",
38
+ "@miravo/shared": "0.3.0",
38
39
  "commander": "^13.0.0",
39
40
  "consola": "^3.4.0",
40
- "mqtt": "^5.15.0"
41
+ "mqtt": "^5.15.0",
42
+ "zod": "^4.3.6"
41
43
  },
42
44
  "devDependencies": {
45
+ "@miravo/console": "0.0.0",
43
46
  "node-opcua-client": "^2.163.1",
44
47
  "node-opcua-variant": "^2.163.0",
45
48
  "yaml": "^2.8.1"
@@ -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,13 @@ 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
+ });
143
+ });
141
144
 
142
145
  const mqttAdapter = new MQTTAdapter(engine.bus);
143
146
  await mqttAdapter.start({
@@ -184,19 +187,23 @@ async function main(): Promise<void> {
184
187
 
185
188
  const adminServer = startAdminServer({
186
189
  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,
190
+ network: { host: "127.0.0.1", port: 0, tickIntervalMs },
191
+ protocols: {
192
+ getLastTickProgressAt: () => lastTickProgressAt,
193
+ },
194
+ ws: {
195
+ bus: engine.bus,
196
+ getEngineState: () => engine.getMetrics().core.state,
197
+ getTickInfo: () => ({
198
+ tick: engine.getMetrics().core.tickCount,
199
+ simulationTime: engine.getMetrics().core.simulationTimeSeconds,
200
+ instanceCount: engine.getMetrics().core.instanceCount,
201
+ }),
202
+ },
195
203
  });
196
204
 
197
- engine.start();
198
-
199
- await Bun.sleep(Math.max(250, tickIntervalMs * 3));
205
+ await engine.start();
206
+ await waitForAdminReady(adminServer.endpoint, Math.max(5_000, tickIntervalMs * 20));
200
207
 
201
208
  const [instanceId, instance] = [...engine.runtime.getInstances()][0] ?? [];
202
209
  if (!instanceId || !instance) {
@@ -237,10 +244,10 @@ async function main(): Promise<void> {
237
244
  process.exit(0);
238
245
  };
239
246
 
240
- process.on("SIGINT", () => {
247
+ process.once("SIGINT", () => {
241
248
  void shutdown();
242
249
  });
243
- process.on("SIGTERM", () => {
250
+ process.once("SIGTERM", () => {
244
251
  void shutdown();
245
252
  });
246
253