@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 +44 -0
- package/build.mjs +51 -0
- package/dist/index.html +28 -0
- package/package.json +39 -0
- package/serve.mjs +140 -0
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);
|
package/dist/index.html
ADDED
|
@@ -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
|
+
});
|