miravo 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +199 -0
- package/README.md +29 -59
- package/bin/miravo.js +84 -0
- package/package.json +21 -37
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- package/src/output.ts +0 -160
package/src/commands/stop.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import { isPidAlive, probeEntryIdentity, readEntry, removeEntry } from "../lib/daemon-registry.ts";
|
|
3
|
-
import { parseTimeoutSeconds } from "../lib/parse.ts";
|
|
4
|
-
import { handleCommandError, writeOutput } from "../logger.ts";
|
|
5
|
-
|
|
6
|
-
interface StopOptions {
|
|
7
|
-
readonly json?: boolean;
|
|
8
|
-
readonly timeout?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function registerStopCommand(program: Command): void {
|
|
12
|
-
program
|
|
13
|
-
.command("stop <name>")
|
|
14
|
-
.description("Stop a running background simulation")
|
|
15
|
-
.option("--json", "Machine-readable JSON output")
|
|
16
|
-
.option("--timeout <seconds>", "Shutdown timeout in seconds (default: 10)", parseTimeoutSeconds)
|
|
17
|
-
.addHelpText(
|
|
18
|
-
"after",
|
|
19
|
-
`
|
|
20
|
-
Examples:
|
|
21
|
-
$ miravo stop my-sim Graceful shutdown
|
|
22
|
-
$ miravo stop my-sim --timeout 30 Wait up to 30s before force kill`,
|
|
23
|
-
)
|
|
24
|
-
.action(async (name: string, options: StopOptions) => {
|
|
25
|
-
try {
|
|
26
|
-
await stopAction(name, options);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
handleCommandError(error);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function jsonOut(obj: Record<string, unknown>): void {
|
|
34
|
-
console.log(JSON.stringify(obj, null, 2));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function stopAction(name: string, options: StopOptions): Promise<void> {
|
|
38
|
-
const entry = await readEntry(name);
|
|
39
|
-
if (!entry) {
|
|
40
|
-
const msg = `Simulation "${name}" not found. Use 'miravo ps' to list running simulations.`;
|
|
41
|
-
if (options.json) {
|
|
42
|
-
jsonOut({ success: false, name, message: msg });
|
|
43
|
-
} else {
|
|
44
|
-
writeOutput(msg);
|
|
45
|
-
}
|
|
46
|
-
process.exitCode = 1;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Verify identity before taking any action — guards against PID reuse
|
|
51
|
-
const identity = await probeEntryIdentity(entry);
|
|
52
|
-
if (identity.status === "dead" || identity.status === "mismatch") {
|
|
53
|
-
await removeEntry(name);
|
|
54
|
-
const msg = `Simulation "${name}" was not running (stale registry entry removed).`;
|
|
55
|
-
if (options.json) {
|
|
56
|
-
jsonOut({ success: true, name, message: msg });
|
|
57
|
-
} else {
|
|
58
|
-
writeOutput(msg);
|
|
59
|
-
}
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (identity.status === "unreachable") {
|
|
63
|
-
const msg = `Simulation "${name}" could not be safely verified because its admin server is unreachable while PID ${entry.pid} is still alive.`;
|
|
64
|
-
if (options.json) {
|
|
65
|
-
jsonOut({
|
|
66
|
-
success: false,
|
|
67
|
-
name,
|
|
68
|
-
message: msg,
|
|
69
|
-
error: "Retry later, or inspect the process manually before removing the registry entry.",
|
|
70
|
-
});
|
|
71
|
-
} else {
|
|
72
|
-
writeOutput(
|
|
73
|
-
`${msg} Retry later, or inspect the process manually before removing the registry entry.`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
process.exitCode = 1;
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Try graceful shutdown via admin server (identity-safe — admin server belongs to this process)
|
|
81
|
-
let shutdownSent = false;
|
|
82
|
-
let canForceKill = true;
|
|
83
|
-
try {
|
|
84
|
-
const adminBase = `http://${entry.adminHost}:${entry.adminPort}`;
|
|
85
|
-
// Authenticate with session-based login
|
|
86
|
-
// Resolve password: env > default.
|
|
87
|
-
// If credential file exists, we can't reverse the hash — use "admin" as default.
|
|
88
|
-
// The server will verify against the stored hash. If the user changed their password,
|
|
89
|
-
// they need to set MIRAVO_ADMIN_PASSWORD env var for CLI access.
|
|
90
|
-
const envPassword = Bun.env["MIRAVO_ADMIN_PASSWORD"];
|
|
91
|
-
const password = envPassword && envPassword.trim().length > 0 ? envPassword : "admin";
|
|
92
|
-
const loginResponse = await fetch(`${adminBase}/auth/login`, {
|
|
93
|
-
method: "POST",
|
|
94
|
-
headers: { "content-type": "application/json" },
|
|
95
|
-
body: JSON.stringify({ username: "admin", password }),
|
|
96
|
-
signal: AbortSignal.timeout(2_000),
|
|
97
|
-
});
|
|
98
|
-
const setCookie = loginResponse.ok ? loginResponse.headers.get("set-cookie") : null;
|
|
99
|
-
const cookie = setCookie ? (setCookie.split(";")[0] ?? "") : "";
|
|
100
|
-
|
|
101
|
-
const response = await fetch(`${adminBase}/shutdown`, {
|
|
102
|
-
method: "POST",
|
|
103
|
-
headers: { "content-type": "application/json", ...(cookie ? { cookie } : {}) },
|
|
104
|
-
body: JSON.stringify({ reason: "miravo stop" }),
|
|
105
|
-
signal: AbortSignal.timeout(2_000),
|
|
106
|
-
});
|
|
107
|
-
shutdownSent = response.ok;
|
|
108
|
-
} catch {
|
|
109
|
-
// Admin server unreachable — fall back to SIGTERM only if we can re-verify identity
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!shutdownSent) {
|
|
113
|
-
try {
|
|
114
|
-
process.kill(entry.pid, "SIGTERM");
|
|
115
|
-
} catch {
|
|
116
|
-
// Process may have already exited
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Poll for process exit
|
|
121
|
-
const timeoutMs = options.timeout ?? 10_000;
|
|
122
|
-
const deadline = Date.now() + timeoutMs;
|
|
123
|
-
|
|
124
|
-
while (isPidAlive(entry.pid) && Date.now() < deadline) {
|
|
125
|
-
await Bun.sleep(500);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// After /shutdown succeeds the admin server may exit before engine.stop() finishes,
|
|
129
|
-
// so keep the verified identity from the successful request for the SIGKILL fallback.
|
|
130
|
-
if (shutdownSent) {
|
|
131
|
-
canForceKill = true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Force kill only if still alive and we already verified identity earlier.
|
|
135
|
-
if (isPidAlive(entry.pid) && canForceKill) {
|
|
136
|
-
try {
|
|
137
|
-
process.kill(entry.pid, "SIGKILL");
|
|
138
|
-
} catch {
|
|
139
|
-
// Ignore — process may have just exited
|
|
140
|
-
}
|
|
141
|
-
await Bun.sleep(200);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (isPidAlive(entry.pid)) {
|
|
145
|
-
const msg = `Simulation "${name}" did not exit within the timeout.`;
|
|
146
|
-
if (options.json) {
|
|
147
|
-
jsonOut({
|
|
148
|
-
success: false,
|
|
149
|
-
name,
|
|
150
|
-
message: msg,
|
|
151
|
-
error: "Inspect the process manually and retry with a longer --timeout if needed.",
|
|
152
|
-
});
|
|
153
|
-
} else {
|
|
154
|
-
writeOutput(
|
|
155
|
-
`${msg} Inspect the process manually and retry with a longer --timeout if needed.`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
process.exitCode = 1;
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
await removeEntry(name);
|
|
163
|
-
const msg = `Simulation "${name}" stopped.`;
|
|
164
|
-
if (options.json) {
|
|
165
|
-
jsonOut({ success: true, name, message: msg });
|
|
166
|
-
} else {
|
|
167
|
-
writeOutput(msg);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { loadTemplate } from "@miravo/core";
|
|
4
|
-
import { createTmpFileUtils } from "@miravo/core/test-tmp-utils";
|
|
5
|
-
import { stripAnsi } from "consola/utils";
|
|
6
|
-
import { configureLogger } from "../logger.ts";
|
|
7
|
-
import { runCliCommand } from "./cli-test-setup.ts";
|
|
8
|
-
import { formatTemplateInspect } from "./templates.ts";
|
|
9
|
-
|
|
10
|
-
configureLogger({ quiet: true });
|
|
11
|
-
|
|
12
|
-
const templatesDir = resolve(import.meta.dir, "..", "..", "..", "content", "templates");
|
|
13
|
-
const { cleanupTmpDir } = createTmpFileUtils(resolve(import.meta.dir, ".tmp-test-templates"));
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
process.exitCode = 0;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
process.exitCode = 0;
|
|
21
|
-
await cleanupTmpDir();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("formatTemplateInspect", () => {
|
|
25
|
-
test("includes topology summary and source details", async () => {
|
|
26
|
-
const templatePath = resolve(templatesDir, "smart-factory.miravo.yaml");
|
|
27
|
-
const template = await loadTemplate(templatePath);
|
|
28
|
-
|
|
29
|
-
const raw = formatTemplateInspect(template, templatePath, "built-in");
|
|
30
|
-
const output = stripAnsi(raw);
|
|
31
|
-
|
|
32
|
-
expect(output).toContain("Smart Factory Production Line");
|
|
33
|
-
expect(output).toContain("built-in");
|
|
34
|
-
expect(output).toContain("Template ID:");
|
|
35
|
-
expect(output).toContain("smart-factory");
|
|
36
|
-
expect(output).toContain("Namespace:");
|
|
37
|
-
expect(output).toContain("Twin assignments:");
|
|
38
|
-
expect(output).toContain("Areas:");
|
|
39
|
-
expect(output).toContain("machining");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("miravo templates", () => {
|
|
44
|
-
test("templates inspect nonexistent template sets exit code 1", async () => {
|
|
45
|
-
await runCliCommand(["templates", "inspect", "nonexistent-template"]);
|
|
46
|
-
expect(process.exitCode).toBe(1);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("templates inspect supports template ID", async () => {
|
|
50
|
-
await runCliCommand(["templates", "inspect", "smart-factory"]);
|
|
51
|
-
expect(process.exitCode).toBe(0);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("templates list with bad directory sets exit code 1", async () => {
|
|
55
|
-
await runCliCommand(["templates", "list", "--dir", "/nonexistent/templates-dir"]);
|
|
56
|
-
expect(process.exitCode).toBe(1);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import { basename, relative, resolve } from "node:path";
|
|
2
|
-
import { type DiscoveredTemplateWithSource, discoverTemplates, loadTemplate } from "@miravo/core";
|
|
3
|
-
import { type DiscoveredTemplate, MiravoError, TEMPLATE_FILE_EXTENSION } from "@miravo/shared";
|
|
4
|
-
import type { Command } from "commander";
|
|
5
|
-
import { colors } from "consola/utils";
|
|
6
|
-
import {
|
|
7
|
-
groupTemplatesByCategory,
|
|
8
|
-
isStressBenchmarkTemplate,
|
|
9
|
-
sortTemplatesByCategoryAndName,
|
|
10
|
-
} from "../lib/template-category.ts";
|
|
11
|
-
import { createCatalog, resolveTemplateIdentifier } from "../lib/template-locator.ts";
|
|
12
|
-
import { collectReferencedModels } from "../lib/template-utils.ts";
|
|
13
|
-
import { configLog, handleCommandError, writeOutput } from "../logger.ts";
|
|
14
|
-
import { formatKeyValue, formatTable } from "../output.ts";
|
|
15
|
-
|
|
16
|
-
export function registerTemplatesCommand(program: Command): void {
|
|
17
|
-
const templates = program.command("templates").description("List and inspect Miravo templates");
|
|
18
|
-
|
|
19
|
-
templates
|
|
20
|
-
.command("list")
|
|
21
|
-
.description("Discover and list available templates")
|
|
22
|
-
.option("--dir <path>", "Additional directory to search for .miravo.yaml templates")
|
|
23
|
-
.addHelpText(
|
|
24
|
-
"after",
|
|
25
|
-
`
|
|
26
|
-
Examples:
|
|
27
|
-
$ miravo templates list List all discoverable templates`,
|
|
28
|
-
)
|
|
29
|
-
.action(async (options: { readonly dir?: string }) => {
|
|
30
|
-
await templatesListAction(options);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
templates
|
|
34
|
-
.command("inspect <idNameOrPath>")
|
|
35
|
-
.description("Show detailed information about a template by id, name, or path")
|
|
36
|
-
.option("--raw", "Dump the raw YAML file to stdout")
|
|
37
|
-
.addHelpText(
|
|
38
|
-
"after",
|
|
39
|
-
`
|
|
40
|
-
Examples:
|
|
41
|
-
$ miravo templates inspect water-treatment Show template details
|
|
42
|
-
$ miravo templates inspect water-treatment --raw Dump raw YAML`,
|
|
43
|
-
)
|
|
44
|
-
.action(
|
|
45
|
-
async (
|
|
46
|
-
idNameOrPath: string,
|
|
47
|
-
options: {
|
|
48
|
-
readonly raw?: boolean;
|
|
49
|
-
},
|
|
50
|
-
) => {
|
|
51
|
-
await templatesInspectAction(idNameOrPath, options);
|
|
52
|
-
},
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface TemplateListOptions {
|
|
57
|
-
readonly dir?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface TemplateInspectOptions {
|
|
61
|
-
readonly raw?: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function templatesListAction(options: TemplateListOptions): Promise<void> {
|
|
65
|
-
try {
|
|
66
|
-
const catalog = createCatalog();
|
|
67
|
-
const all = await catalog.listTemplates();
|
|
68
|
-
const builtinTemplates = all.filter(
|
|
69
|
-
(t) => t.source.kind === "builtin" && !isStressBenchmarkTemplate(t.id),
|
|
70
|
-
);
|
|
71
|
-
const localTemplates: DiscoveredTemplateWithSource[] = all.filter(
|
|
72
|
-
(t) => t.source.kind !== "builtin",
|
|
73
|
-
);
|
|
74
|
-
const localPaths = new Set(localTemplates.map((t) => t.path));
|
|
75
|
-
|
|
76
|
-
if (options.dir) {
|
|
77
|
-
const resolvedDir = resolve(options.dir);
|
|
78
|
-
const { existsSync, statSync } = await import("node:fs");
|
|
79
|
-
if (!existsSync(resolvedDir) || !statSync(resolvedDir).isDirectory()) {
|
|
80
|
-
throw new MiravoError(
|
|
81
|
-
`Templates directory not found: "${resolvedDir}"`,
|
|
82
|
-
"Check the path passed to --dir. Ensure the directory exists.",
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
const extraTemplates = await discoverTemplates(resolvedDir);
|
|
86
|
-
for (const extra of extraTemplates) {
|
|
87
|
-
if (!localPaths.has(extra.path)) {
|
|
88
|
-
localPaths.add(extra.path);
|
|
89
|
-
localTemplates.push({ ...extra, source: { kind: "cwd", path: resolvedDir } });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (builtinTemplates.length === 0 && localTemplates.length === 0) {
|
|
95
|
-
configLog.info("No templates found.");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const headers = {
|
|
100
|
-
id: colors.dim("ID"),
|
|
101
|
-
name: colors.dim("Name"),
|
|
102
|
-
areas: colors.dim("Areas"),
|
|
103
|
-
assignments: colors.dim("Assignments"),
|
|
104
|
-
models: colors.dim("Models"),
|
|
105
|
-
inline: colors.dim("Inline"),
|
|
106
|
-
twins: colors.dim("Twins"),
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const renderTemplateTable = (templates: readonly DiscoveredTemplate[]): string =>
|
|
110
|
-
formatTable(
|
|
111
|
-
headers,
|
|
112
|
-
templates.map((template) => ({
|
|
113
|
-
id: colors.bold(template.id),
|
|
114
|
-
name: colors.cyan(template.name),
|
|
115
|
-
areas: String(template.areaCount),
|
|
116
|
-
assignments: String(template.twinAssignmentCount),
|
|
117
|
-
models: String(template.uniqueModelCount),
|
|
118
|
-
inline: String(template.inlineModelCount),
|
|
119
|
-
twins: String(template.totalTwinCount),
|
|
120
|
-
})),
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const renderSection = (label: string, templates: readonly DiscoveredTemplate[]): void => {
|
|
124
|
-
if (templates.length === 0) return;
|
|
125
|
-
writeOutput();
|
|
126
|
-
writeOutput(` ${colors.bold(`${label}:`)}`);
|
|
127
|
-
writeOutput(renderTemplateTable(templates));
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const sortedBuiltinTemplates = sortTemplatesByCategoryAndName(builtinTemplates);
|
|
131
|
-
const groupedBuiltinTemplates = groupTemplatesByCategory(sortedBuiltinTemplates);
|
|
132
|
-
if (groupedBuiltinTemplates.length > 0) {
|
|
133
|
-
writeOutput();
|
|
134
|
-
writeOutput(` ${colors.bold("Built-in:")}`);
|
|
135
|
-
groupedBuiltinTemplates.forEach((group, idx) => {
|
|
136
|
-
writeOutput(` ${colors.dim(group.category)}`);
|
|
137
|
-
writeOutput(renderTemplateTable(group.templates));
|
|
138
|
-
if (idx < groupedBuiltinTemplates.length - 1) {
|
|
139
|
-
writeOutput();
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const sortedLocalTemplates = [...localTemplates].sort((a, b) => a.name.localeCompare(b.name));
|
|
145
|
-
renderSection("Local", sortedLocalTemplates);
|
|
146
|
-
|
|
147
|
-
writeOutput();
|
|
148
|
-
} catch (error) {
|
|
149
|
-
handleCommandError(error);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function templatesInspectAction(
|
|
154
|
-
idNameOrPath: string,
|
|
155
|
-
options: TemplateInspectOptions,
|
|
156
|
-
): Promise<void> {
|
|
157
|
-
try {
|
|
158
|
-
const catalog = createCatalog();
|
|
159
|
-
const resolvedPath = await resolveTemplateIdentifier(idNameOrPath, catalog);
|
|
160
|
-
const sourceLabel = catalog.getSourceLabel(resolvedPath);
|
|
161
|
-
|
|
162
|
-
if (options.raw) {
|
|
163
|
-
const rawText = await Bun.file(resolvedPath).text();
|
|
164
|
-
process.stdout.write(rawText);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const template = await loadTemplate(resolvedPath);
|
|
169
|
-
writeOutput(formatTemplateInspect(template, resolvedPath, sourceLabel));
|
|
170
|
-
} catch (error) {
|
|
171
|
-
handleCommandError(error);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function formatTemplateInspect(
|
|
176
|
-
template: import("@miravo/shared").MiravoTemplate,
|
|
177
|
-
sourcePath: string,
|
|
178
|
-
sourceLabel: string,
|
|
179
|
-
): string {
|
|
180
|
-
const lines: string[] = [];
|
|
181
|
-
|
|
182
|
-
if (template.description) {
|
|
183
|
-
lines.push(
|
|
184
|
-
` ${colors.cyan(colors.bold(template.name))} ${colors.dim("\u2014")} ${template.description}`,
|
|
185
|
-
);
|
|
186
|
-
} else {
|
|
187
|
-
lines.push(` ${colors.cyan(colors.bold(template.name))}`);
|
|
188
|
-
}
|
|
189
|
-
lines.push("");
|
|
190
|
-
|
|
191
|
-
const totalLineCount = template.areas.reduce((sum, area) => sum + area.lines.length, 0);
|
|
192
|
-
const twinAssignmentCount = template.areas.reduce(
|
|
193
|
-
(sum, area) => sum + area.lines.reduce((lineSum, line) => lineSum + line.twins.length, 0),
|
|
194
|
-
0,
|
|
195
|
-
);
|
|
196
|
-
const totalTwinCount = template.areas.reduce(
|
|
197
|
-
(sum, area) =>
|
|
198
|
-
sum +
|
|
199
|
-
area.lines.reduce(
|
|
200
|
-
(lineSum, line) => lineSum + line.twins.reduce((t, twin) => t + twin.count, 0),
|
|
201
|
-
0,
|
|
202
|
-
),
|
|
203
|
-
0,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const referencedModels = collectReferencedModels(template);
|
|
207
|
-
|
|
208
|
-
const namespace = template.namespace
|
|
209
|
-
? "enterprise" in template.namespace
|
|
210
|
-
? `${template.namespace.enterprise}/${template.namespace.site}`
|
|
211
|
-
: template.namespace.prefix
|
|
212
|
-
: colors.dim("none");
|
|
213
|
-
|
|
214
|
-
const protocols = template.protocols
|
|
215
|
-
? Object.keys(template.protocols).join(", ")
|
|
216
|
-
: colors.dim("none");
|
|
217
|
-
const relPath = relative(process.cwd(), sourcePath);
|
|
218
|
-
const templateId = basename(sourcePath, TEMPLATE_FILE_EXTENSION);
|
|
219
|
-
|
|
220
|
-
const settings = template.settings;
|
|
221
|
-
const settingsSummary = settings
|
|
222
|
-
? `tick ${settings.tick_interval_ms}ms · speed ${settings.time_multiplier}x${settings.seed !== undefined ? ` · seed ${settings.seed}` : ""}`
|
|
223
|
-
: colors.dim("defaults");
|
|
224
|
-
|
|
225
|
-
lines.push(
|
|
226
|
-
formatKeyValue([
|
|
227
|
-
[colors.dim("Source"), `${sourceLabel} ${colors.gray(`(${relPath})`)}`],
|
|
228
|
-
[colors.dim("Template ID"), colors.bold(templateId)],
|
|
229
|
-
[colors.dim("Namespace"), namespace],
|
|
230
|
-
[colors.dim("Areas"), String(template.areas.length)],
|
|
231
|
-
[colors.dim("Lines"), String(totalLineCount)],
|
|
232
|
-
[colors.dim("Twin assignments"), String(twinAssignmentCount)],
|
|
233
|
-
[colors.dim("Total twins"), String(totalTwinCount)],
|
|
234
|
-
[colors.dim("Referenced models"), String(referencedModels.size)],
|
|
235
|
-
[colors.dim("Inline models"), String(template.models?.length ?? 0)],
|
|
236
|
-
[colors.dim("Protocols"), protocols],
|
|
237
|
-
[colors.dim("Settings"), settingsSummary],
|
|
238
|
-
]),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
lines.push("");
|
|
242
|
-
lines.push(` ${colors.bold("Areas:")}`);
|
|
243
|
-
lines.push(
|
|
244
|
-
formatTable(
|
|
245
|
-
{
|
|
246
|
-
area: colors.dim("Area"),
|
|
247
|
-
lines: colors.dim("Lines"),
|
|
248
|
-
assignments: colors.dim("Assignments"),
|
|
249
|
-
twins: colors.dim("Twins"),
|
|
250
|
-
models: colors.dim("Models"),
|
|
251
|
-
},
|
|
252
|
-
template.areas.map((area) => {
|
|
253
|
-
const assignments = area.lines.reduce((sum, line) => sum + line.twins.length, 0);
|
|
254
|
-
const twins = area.lines.reduce(
|
|
255
|
-
(sum, line) => sum + line.twins.reduce((acc, twin) => acc + twin.count, 0),
|
|
256
|
-
0,
|
|
257
|
-
);
|
|
258
|
-
const modelNames = new Set<string>();
|
|
259
|
-
for (const line of area.lines) {
|
|
260
|
-
for (const twin of line.twins) {
|
|
261
|
-
modelNames.add(twin.model);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
area: area.name,
|
|
267
|
-
lines: String(area.lines.length),
|
|
268
|
-
assignments: String(assignments),
|
|
269
|
-
twins: String(twins),
|
|
270
|
-
models: [...modelNames].join(", "),
|
|
271
|
-
};
|
|
272
|
-
}),
|
|
273
|
-
),
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
return lines.join("\n");
|
|
277
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { MIRAVO_VERSION } from "@miravo/shared";
|
|
3
|
-
import { Command, InvalidOptionArgumentError } from "commander";
|
|
4
|
-
import { registerAdapterCommand } from "./commands/adapter.ts";
|
|
5
|
-
import { registerApplyCommand } from "./commands/apply.ts";
|
|
6
|
-
import { registerCleanupCommand } from "./commands/cleanup.ts";
|
|
7
|
-
import { registerExportCommand } from "./commands/export.ts";
|
|
8
|
-
import { registerForkCommand } from "./commands/fork.ts";
|
|
9
|
-
import { registerInjectCommand } from "./commands/inject.ts";
|
|
10
|
-
import { registerLoadCommand } from "./commands/load.ts";
|
|
11
|
-
import { registerLogsCommand } from "./commands/logs.ts";
|
|
12
|
-
import { registerModelsCommand } from "./commands/models.ts";
|
|
13
|
-
import { registerPauseCommand, registerResumeCommand } from "./commands/pause.ts";
|
|
14
|
-
import { registerEnvironmentsCommand } from "./commands/platforms.ts";
|
|
15
|
-
import { registerPsCommand } from "./commands/ps.ts";
|
|
16
|
-
import { registerRemoveCommand } from "./commands/remove.ts";
|
|
17
|
-
import { registerResetCommand } from "./commands/reset.ts";
|
|
18
|
-
import { registerRmCommand } from "./commands/rm.ts";
|
|
19
|
-
import { registerSpawnCommand } from "./commands/spawn.ts";
|
|
20
|
-
import { registerSpeedCommand } from "./commands/speed.ts";
|
|
21
|
-
import { registerStartCommand } from "./commands/start/index.ts";
|
|
22
|
-
import { registerStatusCommand } from "./commands/status.ts";
|
|
23
|
-
import { registerStopCommand } from "./commands/stop.ts";
|
|
24
|
-
import { registerTemplatesCommand } from "./commands/templates.ts";
|
|
25
|
-
import { configureLogger, parseLogFormat, parseLogLevel, writeOutput } from "./logger.ts";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Create and configure the root Commander.js program.
|
|
29
|
-
* Exported for testing — tests call createProgram() without triggering parse().
|
|
30
|
-
*/
|
|
31
|
-
export function createProgram(): Command {
|
|
32
|
-
const program = new Command();
|
|
33
|
-
|
|
34
|
-
program
|
|
35
|
-
.name("miravo")
|
|
36
|
-
.description("Industrial environment platform for realistic software-defined plants")
|
|
37
|
-
.version(MIRAVO_VERSION, "-V, --version", "Print version")
|
|
38
|
-
.helpOption("-h, --help", "Show help for command")
|
|
39
|
-
.addHelpCommand("help [command]", "Show help for command")
|
|
40
|
-
.option("-v, --verbose", "Shorthand for --log-level debug")
|
|
41
|
-
.option("-q, --quiet", "Suppress all output except errors")
|
|
42
|
-
.option("--plain", "Use stable plain-text output (no ANSI styling)")
|
|
43
|
-
.option("--log-level <level>", "Log level: debug | info | warn | error", (value: string) => {
|
|
44
|
-
try {
|
|
45
|
-
return parseLogLevel(value);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
-
throw new InvalidOptionArgumentError(message);
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
.option(
|
|
52
|
-
"--log-format <format>",
|
|
53
|
-
"Log format: pretty | json",
|
|
54
|
-
(value: string) => {
|
|
55
|
-
try {
|
|
56
|
-
return parseLogFormat(value);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
-
throw new InvalidOptionArgumentError(message);
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"pretty",
|
|
63
|
-
)
|
|
64
|
-
.addHelpText("afterAll", "\nDocs: https://miravo.dev/docs")
|
|
65
|
-
.action(() => {
|
|
66
|
-
writeOutput(`miravo v${MIRAVO_VERSION} — Industrial environment platform
|
|
67
|
-
|
|
68
|
-
Quick start:
|
|
69
|
-
$ miravo start Start with auto-detected template
|
|
70
|
-
$ miravo status View simulation dashboard
|
|
71
|
-
$ miravo ps List background simulations
|
|
72
|
-
|
|
73
|
-
Run miravo --help for all commands.
|
|
74
|
-
Docs: https://miravo.dev/docs`);
|
|
75
|
-
})
|
|
76
|
-
.hook("preAction", (_thisCommand, actionCommand) => {
|
|
77
|
-
const rootOpts = program.opts<{
|
|
78
|
-
verbose?: boolean;
|
|
79
|
-
quiet?: boolean;
|
|
80
|
-
plain?: boolean;
|
|
81
|
-
logLevel?: "debug" | "info" | "warn" | "error";
|
|
82
|
-
logFormat?: "pretty" | "json";
|
|
83
|
-
}>();
|
|
84
|
-
const cmdOpts = actionCommand.opts<{
|
|
85
|
-
verbose?: boolean;
|
|
86
|
-
quiet?: boolean;
|
|
87
|
-
plain?: boolean;
|
|
88
|
-
logLevel?: "debug" | "info" | "warn" | "error";
|
|
89
|
-
logFormat?: "pretty" | "json";
|
|
90
|
-
}>();
|
|
91
|
-
configureLogger({
|
|
92
|
-
verbose: rootOpts.verbose ?? cmdOpts.verbose,
|
|
93
|
-
quiet: rootOpts.quiet ?? cmdOpts.quiet,
|
|
94
|
-
plain: rootOpts.plain ?? cmdOpts.plain,
|
|
95
|
-
logLevel: rootOpts.logLevel ?? cmdOpts.logLevel,
|
|
96
|
-
logFormat: rootOpts.logFormat ?? cmdOpts.logFormat,
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Simulation lifecycle
|
|
101
|
-
registerStartCommand(program);
|
|
102
|
-
registerStatusCommand(program);
|
|
103
|
-
registerPsCommand(program);
|
|
104
|
-
registerStopCommand(program);
|
|
105
|
-
registerLogsCommand(program);
|
|
106
|
-
registerRmCommand(program);
|
|
107
|
-
|
|
108
|
-
// Runtime control
|
|
109
|
-
registerSpawnCommand(program);
|
|
110
|
-
registerRemoveCommand(program);
|
|
111
|
-
registerPauseCommand(program);
|
|
112
|
-
registerResumeCommand(program);
|
|
113
|
-
registerSpeedCommand(program);
|
|
114
|
-
registerLoadCommand(program);
|
|
115
|
-
registerResetCommand(program);
|
|
116
|
-
registerInjectCommand(program);
|
|
117
|
-
registerAdapterCommand(program);
|
|
118
|
-
registerExportCommand(program);
|
|
119
|
-
registerApplyCommand(program);
|
|
120
|
-
registerCleanupCommand(program);
|
|
121
|
-
registerEnvironmentsCommand(program);
|
|
122
|
-
|
|
123
|
-
// Content discovery
|
|
124
|
-
registerModelsCommand(program);
|
|
125
|
-
registerTemplatesCommand(program);
|
|
126
|
-
registerForkCommand(program);
|
|
127
|
-
|
|
128
|
-
return program;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Only run when executed directly, not when imported in tests
|
|
132
|
-
if (import.meta.path === Bun.main) {
|
|
133
|
-
// In compiled binaries, extract embedded content to a temp directory so
|
|
134
|
-
// ContentCatalog's filesystem-based discovery can find built-in models/templates.
|
|
135
|
-
// The manifest only exists during `bun run build:standalone`; the catch is a no-op in dev.
|
|
136
|
-
try {
|
|
137
|
-
// @ts-expect-error — file is generated at build time and does not exist in source tree
|
|
138
|
-
const manifest = await import("./_embedded-manifest.ts");
|
|
139
|
-
const { modelPaths, templatePaths } = manifest as {
|
|
140
|
-
modelPaths: string[];
|
|
141
|
-
templatePaths: string[];
|
|
142
|
-
};
|
|
143
|
-
const { basename, join } = await import("node:path");
|
|
144
|
-
const { tmpdir } = await import("node:os");
|
|
145
|
-
const { mkdirSync } = await import("node:fs");
|
|
146
|
-
const { registerEmbeddedContentDir } = await import("@miravo/core");
|
|
147
|
-
|
|
148
|
-
const contentDir = join(tmpdir(), "miravo-embedded");
|
|
149
|
-
const modelsDir = join(contentDir, "models");
|
|
150
|
-
const templatesDir = join(contentDir, "templates");
|
|
151
|
-
mkdirSync(modelsDir, { recursive: true });
|
|
152
|
-
mkdirSync(templatesDir, { recursive: true });
|
|
153
|
-
|
|
154
|
-
for (const p of modelPaths) {
|
|
155
|
-
await Bun.write(join(modelsDir, basename(p)), Bun.file(p));
|
|
156
|
-
}
|
|
157
|
-
for (const p of templatePaths) {
|
|
158
|
-
await Bun.write(join(templatesDir, basename(p)), Bun.file(p));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
registerEmbeddedContentDir(contentDir);
|
|
162
|
-
} catch {
|
|
163
|
-
// Not a compiled binary or manifest not present — expected in dev mode
|
|
164
|
-
}
|
|
165
|
-
createProgram().parse();
|
|
166
|
-
}
|