@rainbow-robotics/preview-host 0.1.1

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/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @repo/preview-host
2
+
3
+ Standalone **mini-Muscat** (nexus) shell for previewing Muscat plugins with **no
4
+ robot and no back end**. Built from `apps/web/nexus` in `plugin-preview` mode.
5
+
6
+ The shell renders the real Header / LNB / panels and the real `rb-components`,
7
+ loads the developer's plugin via its manifest, and mocks every robot/back-end
8
+ dependency (API-failure toasts suppressed, query retries off, rb static assets
9
+ served locally).
10
+
11
+ This package is consumed by `muscat-plugin-kit` (`muscat-pk dev:ui`) so external
12
+ plugin developers get a faithful preview without the web-rainbow-ui source.
13
+
14
+ ## Build
15
+
16
+ ```bash
17
+ node build.mjs # builds nexus (plugin-preview) and assembles ./dist
18
+ node build.mjs --skip-build # only re-assemble (nexus already built)
19
+ ```
20
+
21
+ `dist/` (git-ignored) is self-contained: the nexus bundle + rb assets
22
+ (`/imgs`, `/fonts`, …) served at their runtime paths.
23
+
24
+ ## Serve
25
+
26
+ ```bash
27
+ rb-preview-host --plugin <built-plugin-dir> [--port 4180] [--dist <dir>]
28
+ ```
29
+
30
+ The plugin dir is the output of `muscat-pk build` and must contain
31
+ `manifest.json`, `sandbox.html`, `content.js` and any `assets/`. It is mounted at
32
+ `/plugin-dev/`; the shell reads it via these globals (injected by `serve.mjs`):
33
+
34
+ | global | meaning |
35
+ |---|---|
36
+ | `window.__RB_PLUGIN_PREVIEW_HOST__` | enable preview-host mode (mock back end) |
37
+ | `window.__RB_PLUGIN_PREVIEW_BASE__` | base URL of the plugin (default `/plugin-dev`) |
38
+ | `window.__RB_ASSETS_BASE_URL__` | origin for rb static assets |
39
+
40
+ ## How it relates to dev mode
41
+
42
+ `muscat-pk dev:ui` runs nexus's `vite --mode plugin-preview` directly (HMR), which
43
+ is the same preview-host runtime. This packaged static bundle is the
44
+ distributable form for developers who don't have the monorepo source.
package/build.mjs ADDED
@@ -0,0 +1,51 @@
1
+ // Builds the standalone preview-host bundle.
2
+ //
3
+ // 1. Builds nexus in `plugin-preview` mode (standalone mini-Muscat shell with
4
+ // the preview-host runtime flag baked into index.html).
5
+ // 2. Assembles a self-contained `dist/` here: the nexus bundle plus the rb
6
+ // static assets (imgs/fonts/...) served at their runtime paths.
7
+ //
8
+ // Run: node libraries/preview-host/build.mjs [--skip-build]
9
+ import { execSync } from "node:child_process";
10
+ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
11
+ import { dirname, resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ // This file lives at <repo>/libraries/preview-host/build.mjs
15
+ const here = dirname(fileURLToPath(import.meta.url));
16
+ const repoRoot = resolve(here, "../..");
17
+ const nexusDir = resolve(repoRoot, "apps/web/nexus");
18
+ const nexusDist = resolve(nexusDir, "dist-preview");
19
+ const assetsPublic = resolve(repoRoot, "apps/web/assets/public");
20
+ const outDir = resolve(here, "dist");
21
+
22
+ const skipBuild = process.argv.includes("--skip-build");
23
+
24
+ if (!skipBuild) {
25
+ console.log("[preview-host] building nexus (plugin-preview)…");
26
+ execSync("yarn workspace @app/nexus build:preview", {
27
+ cwd: repoRoot,
28
+ stdio: "inherit",
29
+ });
30
+ }
31
+
32
+ if (!existsSync(nexusDist)) {
33
+ throw new Error(
34
+ `nexus preview build not found at ${nexusDist}. Run without --skip-build.`,
35
+ );
36
+ }
37
+
38
+ console.log("[preview-host] assembling dist…");
39
+ rmSync(outDir, { recursive: true, force: true });
40
+ mkdirSync(outDir, { recursive: true });
41
+
42
+ // nexus bundle (index.html + assets/)
43
+ cpSync(nexusDist, outDir, { recursive: true });
44
+
45
+ // rb static assets served at runtime (/imgs, /fonts, ...)
46
+ for (const dir of ["imgs", "fonts", "models", "sounds", "data"]) {
47
+ const src = resolve(assetsPublic, dir);
48
+ if (existsSync(src)) cpSync(src, resolve(outDir, dir), { recursive: true });
49
+ }
50
+
51
+ console.log("[preview-host] dist ready →", outDir);
@@ -0,0 +1,28 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/rainbow-robotics-favicon.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Rainbow Robotics System</title>
8
+ <script type="module" crossorigin src="/assets/index.C3X9hmTL.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/__federation_fn_import.Drmbxo5T.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/index.ChsGqxH_.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/jsx-runtime.CKZTQYsb.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/emotion-styled-base.browser.esm.JngGwqgu.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers.Cpj98o6Y.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/FactoryMap.CBpf9N92.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/index.CHuCXVak.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/index.ayADrA9_.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/__federation_expose_App.B-7yegU_.js">
18
+
19
+
20
+ <script>
21
+ window.__RB_PLUGIN_PREVIEW_HOST__ = true;
22
+ window.__RB_ASSETS_BASE_URL__ = window.location.origin;
23
+ </script>
24
+ </head>
25
+ <body>
26
+ <div id="root"></div>
27
+ </body>
28
+ </html>
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@rainbow-robotics/preview-host",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=20"
7
+ },
8
+ "description": "Mini-Muscat preview host for Muscat plugin development (devDependency)",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "bin": {
13
+ "rb-preview-host": "./serve.mjs"
14
+ },
15
+ "exports": {
16
+ ".": "./serve.mjs",
17
+ "./serve": "./serve.mjs",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "dist/assets",
22
+ "dist/data",
23
+ "dist/fonts",
24
+ "dist/imgs",
25
+ "dist/index.html",
26
+ "dist/plugin-demo",
27
+ "dist/plugin-runtime",
28
+ "dist/sounds",
29
+ "*.mjs",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "node build.mjs",
34
+ "prepack": "node build.mjs"
35
+ },
36
+ "dependencies": {
37
+ "@rainbow-robotics/plugin-ui": "*"
38
+ }
39
+ }
package/serve.mjs ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ // Serves the standalone preview-host bundle: the mini-Muscat shell (dist/) plus
3
+ // the developer's built plugin mounted at /plugin-dev/. No robot/back end.
4
+ //
5
+ // Usage:
6
+ // rb-preview-host --plugin <built-plugin-dir> [--port 4180] [--dist <dir>]
7
+ //
8
+ // The plugin dir must contain manifest.json, sandbox.html, content.js and any
9
+ // assets/ — i.e. the output of `muscat-pk build`.
10
+ import { createServer } from "node:http";
11
+ import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
12
+ import { createRequire } from "node:module";
13
+ import { dirname, extname, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const here = dirname(fileURLToPath(import.meta.url));
17
+
18
+ function arg(name, fallback) {
19
+ const i = process.argv.indexOf(`--${name}`);
20
+ return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
21
+ }
22
+
23
+ function resolvePluginUiDistBrowser() {
24
+ try {
25
+ const req = createRequire(import.meta.url);
26
+ const pkgPath = req.resolve("@rainbow-robotics/plugin-ui/package.json");
27
+ return resolve(dirname(pkgPath), "dist-browser");
28
+ } catch {
29
+ // fallback for unusual install layouts
30
+ return resolve(here, "../plugin-ui/dist-browser");
31
+ }
32
+ }
33
+
34
+ const port = Number(arg("port", "4180"));
35
+ const distDir = resolve(arg("dist", join(here, "dist")));
36
+ const runtimeDir = resolve(arg("runtime", resolvePluginUiDistBrowser()));
37
+ const pluginDir = arg("plugin", "")
38
+ ? resolve(arg("plugin", ""))
39
+ : "";
40
+ const PLUGIN_BASE = "/plugin-dev";
41
+ const RUNTIME_BASE = "/plugin-runtime";
42
+
43
+ const CONTENT_TYPE = {
44
+ ".html": "text/html; charset=utf-8",
45
+ ".js": "text/javascript; charset=utf-8",
46
+ ".mjs": "text/javascript; charset=utf-8",
47
+ ".css": "text/css; charset=utf-8",
48
+ ".json": "application/json; charset=utf-8",
49
+ ".svg": "image/svg+xml",
50
+ ".png": "image/png",
51
+ ".jpg": "image/jpeg",
52
+ ".jpeg": "image/jpeg",
53
+ ".webp": "image/webp",
54
+ ".gif": "image/gif",
55
+ ".woff": "font/woff",
56
+ ".woff2": "font/woff2",
57
+ ".glb": "model/gltf-binary",
58
+ ".mp3": "audio/mpeg",
59
+ ".wasm": "application/wasm",
60
+ };
61
+
62
+ if (!existsSync(distDir)) {
63
+ console.error(
64
+ `[preview-host] dist not found at ${distDir}. Run \`node build.mjs\` first.`,
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ // index.html with the preview-host globals injected (host flag + plugin base).
70
+ const indexHtml = readFileSync(join(distDir, "index.html"), "utf8").replace(
71
+ "</head>",
72
+ [
73
+ " <script>",
74
+ " window.__RB_PLUGIN_PREVIEW_HOST__ = true;",
75
+ " window.__RB_ASSETS_BASE_URL__ = window.location.origin;",
76
+ ` window.__RB_PLUGIN_PREVIEW_BASE__ = ${JSON.stringify(PLUGIN_BASE)};`,
77
+ " </script>",
78
+ " </head>",
79
+ ].join("\n"),
80
+ );
81
+
82
+ function sendFile(res, filePath) {
83
+ res.setHeader("Access-Control-Allow-Origin", "*");
84
+ const type = CONTENT_TYPE[extname(filePath)];
85
+ if (type) res.setHeader("Content-Type", type);
86
+ createReadStream(filePath).pipe(res);
87
+ }
88
+
89
+ function tryServe(res, baseDir, relPath) {
90
+ const filePath = resolve(baseDir, `.${relPath}`);
91
+ if (!filePath.startsWith(baseDir)) return false;
92
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
93
+ sendFile(res, filePath);
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+
99
+ const server = createServer((req, res) => {
100
+ const url = (req.url || "/").split("?")[0];
101
+
102
+ // Developer's plugin (sandbox.html, content.js, manifest.json, assets/)
103
+ if (url.startsWith(PLUGIN_BASE)) {
104
+ if (!pluginDir) {
105
+ res.statusCode = 404;
106
+ res.end("no --plugin dir configured");
107
+ return;
108
+ }
109
+ const rel = url.slice(PLUGIN_BASE.length) || "/";
110
+ if (tryServe(res, pluginDir, rel)) return;
111
+ res.statusCode = 404;
112
+ res.end("plugin asset not found");
113
+ return;
114
+ }
115
+
116
+ // Host-provided plugin runtime (@rainbow-robotics/plugin-ui, React).
117
+ if (url.startsWith(RUNTIME_BASE)) {
118
+ const rel = url.slice(RUNTIME_BASE.length) || "/";
119
+ if (tryServe(res, runtimeDir, rel)) return;
120
+ res.statusCode = 404;
121
+ res.end("plugin runtime asset not found");
122
+ return;
123
+ }
124
+
125
+ // Static bundle + rb assets
126
+ if (url !== "/" && tryServe(res, distDir, url)) return;
127
+
128
+ // SPA fallback → injected index.html
129
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
130
+ res.end(indexHtml);
131
+ });
132
+
133
+ server.listen(port, () => {
134
+ console.log(`[preview-host] mini-Muscat → http://localhost:${port}`);
135
+ console.log(`[preview-host] dist: ${distDir}`);
136
+ console.log(`[preview-host] runtime: ${runtimeDir} → ${RUNTIME_BASE}`);
137
+ console.log(
138
+ `[preview-host] plugin: ${pluginDir || "(none — pass --plugin <dir>)"} → ${PLUGIN_BASE}`,
139
+ );
140
+ });