miravo 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. package/src/output.ts +0 -160
@@ -1,239 +0,0 @@
1
- import type { Engine } from "@miravo/core";
2
- import type { MiravoTemplate } from "@miravo/shared";
3
- import { MiravoError } from "@miravo/shared";
4
- import type { Command } from "commander";
5
- import { colors } from "consola/utils";
6
- import { modbusLog, mqttLog, opcuaLog } from "../../logger.ts";
7
- import {
8
- buildModbusDefaultConfig,
9
- buildMQTTDefaultConfig,
10
- buildOPCUADefaultConfig,
11
- } from "./adapter-config.ts";
12
- import type {
13
- MqttRunOverrides,
14
- ProtocolBootPlan,
15
- ProtocolOptionSource,
16
- StartOptions,
17
- } from "./types.ts";
18
-
19
- // ---------------------------------------------------------------------------
20
- // Protocol boot plans
21
- // ---------------------------------------------------------------------------
22
-
23
- interface ResolvedDetachAdapterConfigs {
24
- readonly mqtt: {
25
- readonly port: number | undefined;
26
- readonly host: string | undefined;
27
- readonly embedded: boolean;
28
- };
29
- readonly opcua: {
30
- readonly port: number | undefined;
31
- readonly host: string | undefined;
32
- };
33
- readonly modbus: {
34
- readonly port: number | undefined;
35
- readonly host: string | undefined;
36
- };
37
- }
38
-
39
- export function getCommandOptionSource(
40
- command: Command | undefined,
41
- optionName: ProtocolOptionSource,
42
- ): string | undefined {
43
- const maybeGetOptionValueSource = (
44
- command as Command & { getOptionValueSource?: (key: string) => string | undefined }
45
- )?.getOptionValueSource;
46
- return maybeGetOptionValueSource?.call(command, optionName);
47
- }
48
-
49
- export function resolveProtocolBootPlans(input: {
50
- readonly options: StartOptions & { readonly command?: Command | undefined };
51
- readonly optionSources: Partial<Record<ProtocolOptionSource, string | undefined>>;
52
- readonly restoredFromPersistence: boolean;
53
- readonly persistedDisabledAdapters?: ReadonlySet<string> | undefined;
54
- readonly templateProtocols?: MiravoTemplate["protocols"] | undefined;
55
- }): readonly ProtocolBootPlan[] {
56
- const mqttConfigExplicit = [
57
- "mqttPort",
58
- "mqttHost",
59
- "mqttMode",
60
- "mqttUrl",
61
- "mqttUsername",
62
- "mqttPassword",
63
- ].some((option) => input.optionSources[option as ProtocolOptionSource] === "cli");
64
-
65
- const opcuaConfigExplicit =
66
- input.options.opcua === true &&
67
- ["opcuaPort", "opcuaHost"].some(
68
- (option) => input.optionSources[option as ProtocolOptionSource] === "cli",
69
- );
70
-
71
- const modbusConfigExplicit =
72
- input.options.modbus === true &&
73
- ["modbusPort", "modbusHost"].some(
74
- (option) => input.optionSources[option as ProtocolOptionSource] === "cli",
75
- );
76
-
77
- const mqttOverrideRestore =
78
- !input.restoredFromPersistence || input.optionSources["mqtt"] === "cli" || mqttConfigExplicit;
79
- const opcuaOverrideRestore =
80
- !input.restoredFromPersistence || input.optionSources["opcua"] === "cli" || opcuaConfigExplicit;
81
- const modbusOverrideRestore =
82
- !input.restoredFromPersistence ||
83
- input.optionSources["modbus"] === "cli" ||
84
- modbusConfigExplicit;
85
-
86
- // When restoring without a CLI override, an adapter explicitly saved as disabled must
87
- // stay disabled — the user turned it off and that decision should survive restarts.
88
- const persistedDisabled = input.persistedDisabledAdapters ?? new Set<string>();
89
-
90
- return [
91
- {
92
- name: "mqtt",
93
- shouldEnable:
94
- input.options.mqtt !== false && (mqttOverrideRestore || !persistedDisabled.has("mqtt")),
95
- overrideRestore: mqttOverrideRestore,
96
- },
97
- {
98
- name: "opcua",
99
- shouldEnable:
100
- (input.options.opcua === true ||
101
- (input.options.opcua === undefined && input.templateProtocols?.opcua !== undefined)) &&
102
- (opcuaOverrideRestore || !persistedDisabled.has("opcua")),
103
- overrideRestore: opcuaOverrideRestore,
104
- },
105
- {
106
- name: "modbus",
107
- shouldEnable:
108
- (input.options.modbus === true ||
109
- (input.options.modbus === undefined && input.templateProtocols?.modbus !== undefined)) &&
110
- (modbusOverrideRestore || !persistedDisabled.has("modbus")),
111
- overrideRestore: modbusOverrideRestore,
112
- },
113
- ];
114
- }
115
-
116
- export function resolveDetachAdapterConfigs(input: {
117
- readonly template: MiravoTemplate;
118
- readonly simulationName: string;
119
- readonly mqtt: {
120
- readonly enabled: boolean;
121
- readonly portOverride: number | undefined;
122
- readonly overrides: MqttRunOverrides;
123
- };
124
- readonly opcua: {
125
- readonly enabled: boolean;
126
- readonly portOverride: number | undefined;
127
- readonly hostOverride: string | undefined;
128
- readonly writeBudgetOverride: number | undefined;
129
- };
130
- readonly modbus: {
131
- readonly enabled: boolean;
132
- readonly portOverride: number | undefined;
133
- readonly hostOverride: string | undefined;
134
- };
135
- }): ResolvedDetachAdapterConfigs {
136
- const mqttConfig = input.mqtt.enabled
137
- ? buildMQTTDefaultConfig(
138
- input.template,
139
- input.mqtt.portOverride,
140
- input.mqtt.overrides,
141
- input.simulationName,
142
- )
143
- : null;
144
- const opcuaConfig = input.opcua.enabled
145
- ? buildOPCUADefaultConfig(
146
- input.template,
147
- input.opcua.portOverride,
148
- input.opcua.hostOverride,
149
- input.simulationName,
150
- input.opcua.writeBudgetOverride,
151
- )
152
- : null;
153
- const modbusConfig = input.modbus.enabled
154
- ? buildModbusDefaultConfig(input.template, input.modbus.portOverride, input.modbus.hostOverride)
155
- : null;
156
-
157
- return {
158
- mqtt: {
159
- port: mqttConfig ? ((mqttConfig as { port?: number }).port ?? 1883) : undefined,
160
- host: mqttConfig ? ((mqttConfig as { host?: string }).host ?? "0.0.0.0") : undefined,
161
- embedded: mqttConfig ? ((mqttConfig as { embedded?: boolean }).embedded ?? true) : false,
162
- },
163
- opcua: {
164
- port: opcuaConfig ? ((opcuaConfig as { port?: number }).port ?? 4840) : undefined,
165
- host: opcuaConfig ? ((opcuaConfig as { host?: string }).host ?? "0.0.0.0") : undefined,
166
- },
167
- modbus: {
168
- port: modbusConfig ? ((modbusConfig as { port?: number }).port ?? 502) : undefined,
169
- host: modbusConfig ? ((modbusConfig as { host?: string }).host ?? "0.0.0.0") : undefined,
170
- },
171
- };
172
- }
173
-
174
- export async function reconcileProtocolBootPlan(
175
- engine: Engine,
176
- plan: ProtocolBootPlan,
177
- ): Promise<void> {
178
- const adapter = engine.getAvailableAdapters().find((candidate) => candidate.name === plan.name);
179
- const isEnabled = adapter?.enabled === true;
180
-
181
- if (!plan.shouldEnable) {
182
- if (!isEnabled || !plan.overrideRestore) {
183
- return;
184
- }
185
-
186
- const result = await engine.disableAdapter(plan.name);
187
- if (!result.success) {
188
- throw new MiravoError(
189
- result.message,
190
- result.error ?? `Disable adapter "${plan.name}" manually and retry startup.`,
191
- );
192
- }
193
- return;
194
- }
195
-
196
- if (plan.overrideRestore && isEnabled) {
197
- const disableResult = await engine.disableAdapter(plan.name);
198
- if (!disableResult.success) {
199
- throw new MiravoError(
200
- disableResult.message,
201
- disableResult.error ?? `Disable adapter "${plan.name}" manually and retry startup.`,
202
- );
203
- }
204
- if ((disableResult.data as { stopWarning?: boolean } | undefined)?.stopWarning === true) {
205
- throw new MiravoError(
206
- `Failed to safely reconfigure adapter "${plan.name}" during startup.`,
207
- `The previous ${plan.name.toUpperCase()} adapter instance did not stop cleanly. Free the port or clear the persisted adapter state before retrying.`,
208
- );
209
- }
210
- } else if (isEnabled) {
211
- return;
212
- }
213
-
214
- const result = await engine.enableAdapter(plan.name);
215
- if (!result.success) {
216
- throw new MiravoError(
217
- result.message,
218
- result.error ?? `Check ${plan.name.toUpperCase()} configuration.`,
219
- );
220
- }
221
-
222
- if (plan.name === "mqtt") {
223
- mqttLog.ready(
224
- `connected · broker: ${colors.cyan(String((result.data as Record<string, unknown>)?.["endpoint"] ?? ""))}`,
225
- );
226
- return;
227
- }
228
-
229
- if (plan.name === "modbus") {
230
- modbusLog.ready(
231
- `server ready · endpoint: ${colors.cyan(String((result.data as Record<string, unknown>)?.["endpoint"] ?? ""))}`,
232
- );
233
- return;
234
- }
235
-
236
- opcuaLog.ready(
237
- `server ready · endpoint: ${colors.cyan(String((result.data as Record<string, unknown>)?.["endpoint"] ?? ""))}`,
238
- );
239
- }
@@ -1,197 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { basename, resolve } from "node:path";
3
- import type { ContentCatalog, TwinRuntime } from "@miravo/core";
4
- import { discoverTemplates } from "@miravo/core";
5
- import type { CatalogTemplate, MiravoTemplate, TemplateRun } from "@miravo/shared";
6
- import {
7
- MiravoError,
8
- MiravoTemplateSchema,
9
- TWIN_FILE_EXTENSION,
10
- TwinModelSchema,
11
- } from "@miravo/shared";
12
- import { parse as parseYAML } from "yaml";
13
- import { isTemplatePathLike } from "../../lib/template-locator.ts";
14
- import {
15
- buildUNSBasePath,
16
- collectReferencedModels,
17
- forEachTwin,
18
- } from "../../lib/template-utils.ts";
19
-
20
- // ---------------------------------------------------------------------------
21
- // Runtime template loader (for loadTemplate command from console/API)
22
- // ---------------------------------------------------------------------------
23
-
24
- interface CachedTemplate {
25
- readonly id: string;
26
- readonly name: string;
27
- readonly path: string;
28
- }
29
-
30
- interface TemplateCacheResult {
31
- readonly pathCache: Map<string, CachedTemplate>;
32
- readonly catalog: readonly CatalogTemplate[];
33
- }
34
-
35
- /**
36
- * Discover templates once and build both the path lookup cache (for runtime
37
- * template loading) and the catalog (for the REST API) in a single pass.
38
- * All data comes from discovery metadata — no additional file I/O.
39
- */
40
- export async function buildTemplateCacheAndCatalog(
41
- contentCatalog: ContentCatalog,
42
- ): Promise<TemplateCacheResult> {
43
- const pathCache = new Map<string, CachedTemplate>();
44
- const catalogEntries: CatalogTemplate[] = [];
45
- const discovered = await discoverTemplates(contentCatalog.getBuiltinTemplatesDir());
46
-
47
- for (const t of discovered) {
48
- const entry: CachedTemplate = { id: t.id, name: t.name, path: t.path };
49
- pathCache.set(t.id, entry);
50
- pathCache.set(t.name, entry);
51
-
52
- catalogEntries.push({
53
- id: t.id,
54
- name: t.name,
55
- description: t.description ?? null,
56
- category: "Industrial",
57
- models: t.modelNames,
58
- instanceCount: t.totalTwinCount,
59
- memberCount: 0,
60
- source: "builtin",
61
- });
62
- }
63
-
64
- return { pathCache, catalog: catalogEntries };
65
- }
66
-
67
- export function createRuntimeTemplateLoader(
68
- catalog: ContentCatalog,
69
- templatePathCache: Map<string, CachedTemplate>,
70
- ): (templatePath: string, count: number, runId: number, runtime: TwinRuntime) => TemplateRun {
71
- return (templateIdentifier, count, runId, runtime) => {
72
- // Resolve template path from pre-built cache or as a file path
73
- let templatePath: string;
74
- let slugName: string;
75
- if (isTemplatePathLike(templateIdentifier)) {
76
- templatePath = resolve(templateIdentifier);
77
- slugName = basename(templatePath).replace(/\.miravo\.yaml$/, "");
78
- } else {
79
- const cached = templatePathCache.get(templateIdentifier);
80
- if (!cached) {
81
- const ids = [...new Set([...templatePathCache.values()].map((e) => e.id))];
82
- throw new MiravoError(
83
- `Template "${templateIdentifier}" not found.`,
84
- `Available templates: ${ids.join(", ") || "(none)"}`,
85
- );
86
- }
87
- templatePath = cached.path;
88
- slugName = cached.id;
89
- }
90
-
91
- const template = loadTemplateSync(templatePath);
92
- const referencedModels = registerTemplateModels(catalog, templatePath, template, runtime);
93
-
94
- // Spawn instances with auto-prefix using kebab-case slug
95
- const prefix = `${slugName}-${runId}`;
96
- const allInstanceIds: string[] = [];
97
-
98
- for (let c = 0; c < count; c++) {
99
- const runPrefix = count > 1 ? `${prefix}-${c + 1}` : prefix;
100
- forEachTwin(template, (area, line, twin) => {
101
- const unsBasePath = `${runPrefix}/${buildUNSBasePath(template, area.name, line.name)}`;
102
- const spawned = runtime.spawn(twin.model, twin.count, {
103
- variation: twin.variation,
104
- unsBasePath,
105
- ...(twin.parameters !== undefined ? { parameterOverrides: twin.parameters } : {}),
106
- ...(twin.starting_hours !== undefined ? { startingHours: twin.starting_hours } : {}),
107
- templateRunId: runId,
108
- });
109
- for (const node of spawned) {
110
- allInstanceIds.push(node.id);
111
- }
112
- });
113
- }
114
-
115
- return {
116
- templateRunId: runId,
117
- templateName: template.name,
118
- templatePath,
119
- prefix,
120
- instanceIds: allInstanceIds,
121
- models: [...referencedModels],
122
- };
123
- };
124
- }
125
-
126
- function loadTemplateSync(templatePath: string): MiravoTemplate {
127
- try {
128
- return MiravoTemplateSchema.parse(parseYAML(readFileSync(templatePath, "utf-8")));
129
- } catch (err) {
130
- throw new MiravoError(
131
- `Failed to load template "${templatePath}".`,
132
- err instanceof Error ? err.message : "Check that the file is valid YAML.",
133
- );
134
- }
135
- }
136
-
137
- export function registerModelFromSearchPaths(
138
- runtime: TwinRuntime,
139
- modelName: string,
140
- searchPaths: readonly string[],
141
- ): void {
142
- if (runtime.getModel(modelName)) {
143
- return;
144
- }
145
-
146
- const { path: modelPath, content: modelContent } = loadModelSync(modelName, searchPaths);
147
- try {
148
- const model = TwinModelSchema.parse(parseYAML(modelContent));
149
- runtime.registerModel(model);
150
- } catch (err) {
151
- throw new MiravoError(
152
- `Failed to load model "${modelName}" from "${modelPath}".`,
153
- err instanceof Error ? err.message : "Check that the model file is valid YAML.",
154
- );
155
- }
156
- }
157
-
158
- export function loadTemplateModels(
159
- catalog: ContentCatalog,
160
- templatePath: string,
161
- runtime: TwinRuntime,
162
- ): readonly string[] {
163
- const template = loadTemplateSync(templatePath);
164
- return registerTemplateModels(catalog, templatePath, template, runtime);
165
- }
166
-
167
- function registerTemplateModels(
168
- catalog: ContentCatalog,
169
- templatePath: string,
170
- template: MiravoTemplate,
171
- runtime: TwinRuntime,
172
- ): readonly string[] {
173
- const referencedModels = collectReferencedModels(template);
174
- const searchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
175
- for (const modelName of referencedModels) {
176
- registerModelFromSearchPaths(runtime, modelName, searchPaths);
177
- }
178
- return [...referencedModels];
179
- }
180
-
181
- function loadModelSync(
182
- modelName: string,
183
- searchPaths: readonly string[],
184
- ): { path: string; content: string } {
185
- for (const dir of searchPaths) {
186
- const candidate = resolve(dir, `${modelName}${TWIN_FILE_EXTENSION}`);
187
- try {
188
- return { path: candidate, content: readFileSync(candidate, "utf-8") };
189
- } catch {
190
- // File doesn't exist or can't be read — try next path
191
- }
192
- }
193
- throw new MiravoError(
194
- `Model "${modelName}" not found in search paths.`,
195
- `Searched: ${searchPaths.join(", ")}. Ensure the model file exists as "${modelName}${TWIN_FILE_EXTENSION}".`,
196
- );
197
- }
@@ -1,185 +0,0 @@
1
- import { basename } from "node:path";
2
- import type { Option as ClackOption } from "@clack/prompts";
3
- import { isCancel, select } from "@clack/prompts";
4
- import type { ContentCatalog } from "@miravo/core";
5
- import { discoverTemplates, resolveMiravoHome } from "@miravo/core";
6
- import type { DiscoveredTemplate } from "@miravo/shared";
7
- import { ConfigNotFoundError, MiravoError, PersistedStateSchema } from "@miravo/shared";
8
- import { Glob } from "bun";
9
- import {
10
- groupTemplatesByCategory,
11
- isStressBenchmarkTemplate,
12
- sortTemplatesByCategoryAndName,
13
- } from "../../lib/template-category.ts";
14
- import {
15
- discoverPreferredLocalTemplates,
16
- resolveTemplateIdentifier,
17
- } from "../../lib/template-locator.ts";
18
- import { configLog } from "../../logger.ts";
19
-
20
- // ---------------------------------------------------------------------------
21
- // Step 1: Template resolution
22
- // ---------------------------------------------------------------------------
23
-
24
- export async function peekStateTemplatePath(simulationName: string): Promise<string | undefined> {
25
- const stateDir = `${resolveMiravoHome()}/state`;
26
- const file = Bun.file(`${stateDir}/${simulationName}.json`);
27
- if (!(await file.exists())) return undefined;
28
- try {
29
- const parsed = JSON.parse(await file.text()) as unknown;
30
- const result = PersistedStateSchema.safeParse(parsed);
31
- if (!result.success) return undefined;
32
- return result.data.templateRuns.find((r) => r.templatePath.trim().length > 0)?.templatePath;
33
- } catch {
34
- return undefined;
35
- }
36
- }
37
-
38
- export async function peekSingleStateTemplatePath(): Promise<string | undefined> {
39
- const stateDir = `${resolveMiravoHome()}/state`;
40
- let bestTemplatePath: string | undefined;
41
- let bestSavedAt = "";
42
- try {
43
- const glob = new Glob("*.json");
44
- for await (const file of glob.scan({ cwd: stateDir, onlyFiles: true })) {
45
- try {
46
- const parsed = JSON.parse(await Bun.file(`${stateDir}/${file}`).text()) as unknown;
47
- const result = PersistedStateSchema.safeParse(parsed);
48
- if (!result.success) continue;
49
- const templatePath = result.data.templateRuns.find(
50
- (r) => r.templatePath.trim().length > 0,
51
- )?.templatePath;
52
- if (!templatePath) continue;
53
- if (result.data.savedAt > bestSavedAt) {
54
- bestSavedAt = result.data.savedAt;
55
- bestTemplatePath = templatePath;
56
- }
57
- } catch {}
58
- }
59
- } catch {
60
- return undefined; // state dir doesn't exist yet (first-ever run)
61
- }
62
- return bestTemplatePath;
63
- }
64
-
65
- export async function resolveTemplatePath(
66
- templateOption: string | undefined,
67
- catalog: ContentCatalog,
68
- ): Promise<string> {
69
- // 1. Explicit --template identifier or path
70
- if (templateOption) {
71
- return resolveTemplateIdentifier(templateOption, catalog);
72
- }
73
-
74
- // 2. Auto-discover local templates from content dir (if configured) and CWD
75
- const discovered = await discoverPreferredLocalTemplates(catalog);
76
- if (discovered.length === 1) {
77
- const first = discovered[0];
78
- if (first !== undefined) {
79
- configLog.debug(`Found template: ${first.name} (${first.path})`);
80
- return first.path;
81
- }
82
- }
83
- if (discovered.length > 1) {
84
- if (!process.stdout.isTTY || !process.stdin.isTTY) {
85
- const names = discovered.map((t) => t.name).join(", ");
86
- throw new MiravoError(
87
- `Found ${discovered.length} templates: ${names}.`,
88
- "Use '--template <name|path>' to specify which template to use.",
89
- );
90
- }
91
- const first = discovered[0];
92
- if (first !== undefined) {
93
- configLog.warn(
94
- `Found ${discovered.length} templates: ${discovered.map((t) => t.name).join(", ")}. Using ${first.name}. Use '--template <name|path>' to specify.`,
95
- );
96
- return first.path;
97
- }
98
- }
99
-
100
- // 3. Fall back to built-in templates
101
- const builtinTemplateDir = catalog.getBuiltinTemplatesDir();
102
- let builtinTemplates: readonly DiscoveredTemplate[] = [];
103
- try {
104
- const discovered = await discoverTemplates(builtinTemplateDir);
105
- // Exclude stress/benchmark templates from built-in list (same as standalone binaries).
106
- builtinTemplates = discovered.filter((t) => !isStressBenchmarkTemplate(t.id));
107
- } catch {
108
- // Builtin dir may not exist in edge cases (e.g. standalone binary extraction failure)
109
- }
110
- if (builtinTemplates.length > 0) {
111
- if (process.stdout.isTTY && process.stdin.isTTY) {
112
- return pickBuiltinTemplate(builtinTemplates);
113
- }
114
- const selected = selectDefaultBuiltinTemplate(builtinTemplates);
115
- if (selected !== undefined) {
116
- configLog.debug(`No template specified. Using built-in "${selected.name}".`);
117
- configLog.debug(
118
- "Run 'miravo templates list' to see all options, or use --template <name|path>.",
119
- );
120
- return selected.path;
121
- }
122
- }
123
-
124
- // 4. Nothing available
125
- throw new ConfigNotFoundError(
126
- "No template found. Use 'miravo templates list' to find built-ins or pass 'miravo start --template <name|path>'.",
127
- );
128
- }
129
-
130
- async function pickBuiltinTemplate(templates: readonly DiscoveredTemplate[]): Promise<string> {
131
- configLog.info("No template found in current directory.\n");
132
-
133
- const choice = await select({
134
- message: "Choose a built-in template to get started:",
135
- options: buildBuiltinTemplateChoices(templates),
136
- maxItems: 9,
137
- });
138
-
139
- if (isCancel(choice)) {
140
- process.exit(0);
141
- }
142
-
143
- configLog.info("Tip: use --template <id> to skip this prompt next time.");
144
-
145
- return choice as string;
146
- }
147
-
148
- export function buildBuiltinTemplateChoices(
149
- templates: readonly DiscoveredTemplate[],
150
- ): Array<ClackOption<string>> {
151
- const groupedTemplates = groupTemplatesByCategory(sortTemplatesByCategoryAndName(templates));
152
- const choices: ClackOption<string>[] = [];
153
-
154
- for (const group of groupedTemplates) {
155
- choices.push({
156
- value: `__header__${group.category}`,
157
- label: group.category,
158
- hint: `${group.templates.length} templates`,
159
- disabled: true,
160
- });
161
-
162
- for (const [index, template] of group.templates.entries()) {
163
- const isLast = index === group.templates.length - 1;
164
- const branch = isLast ? "\u2514\u2500" : "\u251C\u2500";
165
- const label = ` ${branch} ${template.name}`;
166
- const hintPrefix = template.description ?? template.id;
167
- choices.push({
168
- value: template.path,
169
- label,
170
- hint: `${hintPrefix} \u00B7 ${template.uniqueModelCount} models \u00B7 ${template.totalTwinCount} twins`,
171
- });
172
- }
173
- }
174
-
175
- return choices;
176
- }
177
-
178
- function selectDefaultBuiltinTemplate(
179
- templates: readonly DiscoveredTemplate[],
180
- ): DiscoveredTemplate | undefined {
181
- const preferredFilename = "smart-factory.miravo.yaml";
182
- return (
183
- templates.find((template) => basename(template.path) === preferredFilename) ?? templates[0]
184
- );
185
- }
@@ -1,73 +0,0 @@
1
- import type { Command } from "commander";
2
-
3
- export interface StartOptions {
4
- readonly name?: string;
5
- readonly template?: string;
6
- readonly modelsDir?: string;
7
- readonly speed?: number;
8
- readonly seed?: number;
9
- readonly tick?: number;
10
- readonly mqttPort?: number;
11
- readonly mqttHost?: string;
12
- readonly mqttMode?: string;
13
- readonly mqttUrl?: string;
14
- readonly mqttUsername?: string;
15
- readonly mqttPassword?: string;
16
- readonly mqtt?: boolean;
17
- readonly opcuaPort?: number;
18
- readonly opcuaHost?: string;
19
- readonly opcua?: boolean;
20
- readonly modbusPort?: number;
21
- readonly modbusHost?: string;
22
- readonly modbus?: boolean;
23
- readonly adminHost?: string;
24
- readonly adminPort?: number;
25
- readonly dryRun?: boolean;
26
- readonly summaryInterval?: number;
27
- readonly ui?: boolean;
28
- readonly auth?: boolean;
29
- readonly resetState?: boolean;
30
- readonly detach?: boolean;
31
- readonly command?: Command;
32
- }
33
-
34
- export interface RestoreSummary {
35
- readonly instanceCount: number;
36
- readonly templateRunCount: number;
37
- readonly adapterCount: number;
38
- readonly stateFilePath: string;
39
- }
40
-
41
- export interface ProtocolBootPlan {
42
- readonly name: "mqtt" | "opcua" | "modbus";
43
- readonly shouldEnable: boolean;
44
- readonly overrideRestore: boolean;
45
- }
46
-
47
- export type ProtocolOptionSource =
48
- | "mqtt"
49
- | "opcua"
50
- | "modbus"
51
- | "mqttPort"
52
- | "mqttHost"
53
- | "mqttMode"
54
- | "mqttUrl"
55
- | "mqttUsername"
56
- | "mqttPassword"
57
- | "opcuaPort"
58
- | "opcuaHost"
59
- | "modbusPort"
60
- | "modbusHost";
61
-
62
- export type MqttMode = "embedded" | "external";
63
-
64
- export interface MqttRunOverrides {
65
- readonly mode?: MqttMode;
66
- readonly host?: string;
67
- readonly url?: string;
68
- readonly username?: string;
69
- readonly password?: string;
70
- readonly drainBudget?: number;
71
- readonly batchSize?: number;
72
- readonly brokerConcurrency?: number;
73
- }