@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.15 → 0.1.2-dev.17
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/cli/src/build.mjs +13 -2
- package/cli/src/devBack.mjs +4 -6
- package/cli/src/pack.mjs +1 -4
- package/cli/src/update.mjs +114 -17
- package/cli/templates/common/manifest.json +0 -7
- package/cli/templates/common/tsconfig.json +1 -1
- package/cli/templates/common/vite.config.ts +1 -1
- package/package.json +1 -1
- /package/cli/templates/common/{main.py → py/main.py} +0 -0
- /package/cli/templates/common/{src → ui}/content.tsx +0 -0
package/cli/src/build.mjs
CHANGED
|
@@ -147,7 +147,7 @@ const HOST_PROVIDED_EXTERNALS = [
|
|
|
147
147
|
|
|
148
148
|
export async function buildPlugin({ root, minify = false } = {}) {
|
|
149
149
|
const pluginRoot = root ?? findPluginRoot();
|
|
150
|
-
const entry = resolve(pluginRoot, "
|
|
150
|
+
const entry = resolve(pluginRoot, "ui/content.tsx");
|
|
151
151
|
if (!existsSync(entry)) {
|
|
152
152
|
throw new Error(`missing entry: ${entry}`);
|
|
153
153
|
}
|
|
@@ -155,7 +155,7 @@ export async function buildPlugin({ root, minify = false } = {}) {
|
|
|
155
155
|
rmSync(distDir, { recursive: true, force: true });
|
|
156
156
|
mkdirSync(distDir, { recursive: true });
|
|
157
157
|
|
|
158
|
-
log.step("bundling
|
|
158
|
+
log.step("bundling ui/content.tsx → dist/content.js");
|
|
159
159
|
await buildWithFallback({
|
|
160
160
|
entryPoints: [entry],
|
|
161
161
|
outfile: resolve(distDir, "content.js"),
|
|
@@ -176,11 +176,22 @@ export async function buildPlugin({ root, minify = false } = {}) {
|
|
|
176
176
|
// Generate sandbox.html — plugin authors don't manage this file.
|
|
177
177
|
writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
|
|
178
178
|
log.step("generated dist/sandbox.html");
|
|
179
|
+
|
|
179
180
|
const assetsSrc = resolve(pluginRoot, "assets");
|
|
180
181
|
if (existsSync(assetsSrc)) {
|
|
181
182
|
cpSync(assetsSrc, resolve(distDir, "assets"), { recursive: true });
|
|
182
183
|
}
|
|
183
184
|
|
|
185
|
+
// Backend runtime files — copy entire py/ into dist/, then requirements.txt from root.
|
|
186
|
+
const pySrc = resolve(pluginRoot, "py");
|
|
187
|
+
if (existsSync(pySrc)) {
|
|
188
|
+
cpSync(pySrc, distDir, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
const rootReqs = resolve(pluginRoot, "requirements.txt");
|
|
191
|
+
if (existsSync(rootReqs)) {
|
|
192
|
+
copyFileSync(rootReqs, resolve(distDir, "requirements.txt"));
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
log.ok(`built → ${distDir}`);
|
|
185
196
|
return { pluginRoot, distDir };
|
|
186
197
|
}
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -31,6 +31,7 @@ function manifestRuntime(root) {
|
|
|
31
31
|
function runPython(root) {
|
|
32
32
|
const venv = resolve(root, ".venv");
|
|
33
33
|
const py = venvPython(venv);
|
|
34
|
+
const pyDir = resolve(root, "py");
|
|
34
35
|
|
|
35
36
|
if (!existsSync(py)) {
|
|
36
37
|
log.step("creating .venv…");
|
|
@@ -43,7 +44,7 @@ function runPython(root) {
|
|
|
43
44
|
if (!is312) log.warn("python3.12 not found; using fallback (robot targets 3.12).");
|
|
44
45
|
spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venv], { cwd: root, stdio: "inherit" });
|
|
45
46
|
} else {
|
|
46
|
-
throw new Error("Python not found (uv / conda / python3 none available). Install Python 3.12 to run the
|
|
47
|
+
throw new Error("Python not found (uv / conda / python3 none available). Install Python 3.12 to run the py.");
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -53,9 +54,6 @@ function runPython(root) {
|
|
|
53
54
|
spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], { cwd: root, stdio: "inherit" });
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
// watchfiles = uvicorn's reloader. --filter python watches every *.py in the
|
|
57
|
-
// project (and ignores .venv/__pycache__/node_modules), so interdependent
|
|
58
|
-
// modules trigger reloads — not just main.py.
|
|
59
57
|
log.step("pip install -q watchfiles (dev reloader)…");
|
|
60
58
|
spawnSync(py, ["-m", "pip", "install", "-q", "watchfiles"], { cwd: root, stdio: "inherit" });
|
|
61
59
|
|
|
@@ -63,10 +61,10 @@ function runPython(root) {
|
|
|
63
61
|
const backEnv = { ...process.env, ...(pluginId ? { RB_PLUGIN_ID: pluginId } : {}) };
|
|
64
62
|
if (pluginId) log.step(`RB_PLUGIN_ID=${pluginId}`);
|
|
65
63
|
|
|
66
|
-
log.step("watchfiles → python main.py (reload on any .py change)");
|
|
64
|
+
log.step("watchfiles → python py/main.py (reload on any .py change in py/)");
|
|
67
65
|
const child = spawn(
|
|
68
66
|
py,
|
|
69
|
-
["-m", "watchfiles", "--filter", "python", "--target-type", "command", "python main.py", "
|
|
67
|
+
["-m", "watchfiles", "--filter", "python", "--target-type", "command", "python py/main.py", "py"],
|
|
70
68
|
{ cwd: root, stdio: "inherit", env: backEnv },
|
|
71
69
|
);
|
|
72
70
|
const cleanup = () => { if (!child.killed) child.kill(); };
|
package/cli/src/pack.mjs
CHANGED
|
@@ -29,11 +29,8 @@ export async function runPack() {
|
|
|
29
29
|
const zipPath = resolve(pluginRoot, zipName);
|
|
30
30
|
rmSync(zipPath, { force: true });
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// dist/ already contains main.py, requirements.txt, manifest.json, assets/.
|
|
33
33
|
const entries = ["dist"];
|
|
34
|
-
for (const f of ["main.py", "requirements.txt"]) {
|
|
35
|
-
if (existsSync(resolve(pluginRoot, f))) entries.push(f);
|
|
36
|
-
}
|
|
37
34
|
if (existsSync(resolve(pluginRoot, "ros2_ws/install"))) {
|
|
38
35
|
entries.push("ros2_ws/install");
|
|
39
36
|
}
|
package/cli/src/update.mjs
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
// local : plugin project's node_modules (fallback)
|
|
9
9
|
// → <pm> add @rainbow-robotics/muscat-plugin-kit@<tag>
|
|
10
10
|
//
|
|
11
|
-
// After updating the CLI itself, also bumps the plugin project's local deps
|
|
11
|
+
// After updating the CLI itself, also bumps the plugin project's local deps
|
|
12
|
+
// and migrates the project structure if it's outdated (src/ → ui/, root main.py → py/).
|
|
13
|
+
import { createInterface } from "node:readline";
|
|
12
14
|
import { execSync, spawn } from "node:child_process";
|
|
13
|
-
import { chmodSync, copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
14
16
|
import { tmpdir } from "node:os";
|
|
15
17
|
import { join, resolve } from "node:path";
|
|
16
18
|
import { findPluginRoot, KIT_PUBLISHED_NAME, kitVersion, log, parseArgs } from "./util.mjs";
|
|
@@ -65,13 +67,13 @@ async function updateBinary(tag) {
|
|
|
65
67
|
const version = await resolveS3Version(tag);
|
|
66
68
|
const current = kitVersion();
|
|
67
69
|
if (version === current) {
|
|
68
|
-
log.ok(
|
|
70
|
+
log.ok(`Already up to date (v${current})`);
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
const binaryName = s3BinaryName();
|
|
73
75
|
const url = `${S3_BASE}/v${version}/${binaryName}`;
|
|
74
|
-
log.step(
|
|
76
|
+
log.step(`Downloading binary: v${current} → v${version}`);
|
|
75
77
|
|
|
76
78
|
const data = await fetchBinary(url);
|
|
77
79
|
const execPath = process.execPath;
|
|
@@ -88,14 +90,14 @@ async function updateBinary(tag) {
|
|
|
88
90
|
stdio: "ignore",
|
|
89
91
|
}).unref();
|
|
90
92
|
|
|
91
|
-
log.ok(`v${version}
|
|
93
|
+
log.ok(`v${version} downloaded. Will be applied on next run.`);
|
|
92
94
|
} else {
|
|
93
95
|
// macOS / Linux: can replace the file while it's mapped into memory
|
|
94
96
|
const tmp = join(tmpdir(), `muscat-pk-${version}`);
|
|
95
97
|
writeFileSync(tmp, data);
|
|
96
98
|
chmodSync(tmp, 0o755);
|
|
97
99
|
copyFileSync(tmp, execPath);
|
|
98
|
-
log.ok(`v${version}
|
|
100
|
+
log.ok(`Updated to v${version}.`);
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -104,7 +106,79 @@ async function updateBinary(tag) {
|
|
|
104
106
|
function updateNpmGlobal(tag) {
|
|
105
107
|
log.step(`npm install -g ${KIT_PUBLISHED_NAME}@${tag}`);
|
|
106
108
|
execSync(`npm install -g ${KIT_PUBLISHED_NAME}@${tag}`, { stdio: "inherit" });
|
|
107
|
-
log.ok("
|
|
109
|
+
log.ok("Global CLI updated.");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── project structure migration ───────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
function detectOldStructure(root) {
|
|
115
|
+
const issues = [];
|
|
116
|
+
if (existsSync(resolve(root, "src", "content.tsx")) && !existsSync(resolve(root, "ui"))) {
|
|
117
|
+
issues.push({ kind: "src→ui", desc: "src/content.tsx → ui/content.tsx" });
|
|
118
|
+
}
|
|
119
|
+
if (existsSync(resolve(root, "main.py")) && !existsSync(resolve(root, "py"))) {
|
|
120
|
+
issues.push({ kind: "main→py", desc: "main.py → py/main.py" });
|
|
121
|
+
}
|
|
122
|
+
if (existsSync(resolve(root, "backend"))) {
|
|
123
|
+
issues.push({ kind: "backend→py", desc: "backend/ → py/" });
|
|
124
|
+
}
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function prompt(question) {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
131
|
+
rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function migrateStructure(root, issues) {
|
|
136
|
+
log.warn("This plugin project's structure is outdated:");
|
|
137
|
+
for (const issue of issues) log.info(" • " + issue.desc);
|
|
138
|
+
log.warn("Files will be moved and modified. Back up or commit your work before proceeding.");
|
|
139
|
+
|
|
140
|
+
const answer = await prompt("Migrate project structure? [y/N] ");
|
|
141
|
+
if (answer.toLowerCase() !== "y") {
|
|
142
|
+
log.info("Structure migration skipped.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const issue of issues) {
|
|
147
|
+
if (issue.kind === "src→ui") {
|
|
148
|
+
renameSync(resolve(root, "src"), resolve(root, "ui"));
|
|
149
|
+
log.ok("src/ → ui/");
|
|
150
|
+
|
|
151
|
+
const vitePath = resolve(root, "vite.config.ts");
|
|
152
|
+
if (existsSync(vitePath)) {
|
|
153
|
+
writeFileSync(vitePath, readFileSync(vitePath, "utf8").replace(/["']src\/content\.tsx["']/g, '"ui/content.tsx"'));
|
|
154
|
+
log.ok("vite.config.ts entry updated");
|
|
155
|
+
}
|
|
156
|
+
const tscPath = resolve(root, "tsconfig.json");
|
|
157
|
+
if (existsSync(tscPath)) {
|
|
158
|
+
writeFileSync(tscPath, readFileSync(tscPath, "utf8").replace(/"src"/g, '"ui"'));
|
|
159
|
+
log.ok("tsconfig.json include updated");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (issue.kind === "main→py") {
|
|
164
|
+
mkdirSync(resolve(root, "py"), { recursive: true });
|
|
165
|
+
renameSync(resolve(root, "main.py"), resolve(root, "py", "main.py"));
|
|
166
|
+
log.ok("main.py → py/main.py");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (issue.kind === "backend→py") {
|
|
170
|
+
renameSync(resolve(root, "backend"), resolve(root, "py"));
|
|
171
|
+
log.ok("backend/ → py/");
|
|
172
|
+
const pyReqs = resolve(root, "py", "requirements.txt");
|
|
173
|
+
const rootReqs = resolve(root, "requirements.txt");
|
|
174
|
+
if (existsSync(pyReqs) && !existsSync(rootReqs)) {
|
|
175
|
+
renameSync(pyReqs, rootReqs);
|
|
176
|
+
log.ok("py/requirements.txt → requirements.txt (project root)");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
log.ok("Migration complete. Run `muscat-pk build` to verify.");
|
|
108
182
|
}
|
|
109
183
|
|
|
110
184
|
// ── plugin project local deps ─────────────────────────────────────────────────
|
|
@@ -117,31 +191,48 @@ function updateLocalDeps(root, tag) {
|
|
|
117
191
|
: pm === "yarn" ? `yarn add ${list.join(" ")}`
|
|
118
192
|
: `${pm} add ${list.join(" ")}`;
|
|
119
193
|
|
|
120
|
-
log.step(
|
|
194
|
+
log.step(`Updating plugin local deps (${pm}): ${list.join(", ")}`);
|
|
121
195
|
try {
|
|
122
196
|
execSync(cmd, { cwd: root, stdio: "inherit" });
|
|
123
|
-
log.ok("
|
|
197
|
+
log.ok("Local deps updated.");
|
|
124
198
|
} catch {
|
|
125
|
-
log.warn("
|
|
199
|
+
log.warn("Local deps update failed (offline?). Run manually:\n " + cmd);
|
|
126
200
|
}
|
|
127
201
|
}
|
|
128
202
|
|
|
129
203
|
// ── main ─────────────────────────────────────────────────────────────────────
|
|
130
204
|
|
|
205
|
+
async function resolveTag(explicitTag) {
|
|
206
|
+
if (explicitTag) return explicitTag;
|
|
207
|
+
|
|
208
|
+
const current = kitVersion();
|
|
209
|
+
const isDevVersion = current.includes("-dev.");
|
|
210
|
+
|
|
211
|
+
if (!isDevVersion) return "latest";
|
|
212
|
+
|
|
213
|
+
const answer = await prompt(
|
|
214
|
+
`You are on a dev version (v${current}).\n` +
|
|
215
|
+
` [1] Stay on dev — update to latest dev build\n` +
|
|
216
|
+
` [2] Switch to stable — update to latest stable release\n` +
|
|
217
|
+
`Choice [1/2, default: 1]: `
|
|
218
|
+
);
|
|
219
|
+
return answer.trim() === "2" ? "latest" : "dev";
|
|
220
|
+
}
|
|
221
|
+
|
|
131
222
|
export async function runUpdate(args = []) {
|
|
132
223
|
const opts = parseArgs(args);
|
|
133
|
-
const
|
|
224
|
+
const explicitTag = opts._[0] || opts.version || opts.tag || "";
|
|
225
|
+
const tag = await resolveTag(explicitTag);
|
|
134
226
|
|
|
135
227
|
// 1. Update the CLI itself
|
|
136
228
|
if (isBinaryMode) {
|
|
137
|
-
log.step("
|
|
229
|
+
log.step("Updating binary…");
|
|
138
230
|
try {
|
|
139
231
|
await updateBinary(tag);
|
|
140
232
|
} catch (err) {
|
|
141
|
-
log.warn(
|
|
233
|
+
log.warn(`Binary update failed: ${err.message}`);
|
|
142
234
|
}
|
|
143
235
|
} else {
|
|
144
|
-
// Check if globally installed via npm (execPath's npm prefix contains this package)
|
|
145
236
|
const isGlobal = (() => {
|
|
146
237
|
try {
|
|
147
238
|
const prefix = execSync("npm prefix -g", { encoding: "utf8" }).trim();
|
|
@@ -155,16 +246,22 @@ export async function runUpdate(args = []) {
|
|
|
155
246
|
try {
|
|
156
247
|
updateNpmGlobal(tag);
|
|
157
248
|
} catch (err) {
|
|
158
|
-
log.warn(
|
|
249
|
+
log.warn(`Global CLI update failed: ${err.message}`);
|
|
159
250
|
}
|
|
160
251
|
}
|
|
161
252
|
}
|
|
162
253
|
|
|
163
|
-
// 2.
|
|
254
|
+
// 2. Plugin project: structure migration + local deps
|
|
164
255
|
try {
|
|
165
256
|
const root = findPluginRoot();
|
|
257
|
+
|
|
258
|
+
const structureIssues = detectOldStructure(root);
|
|
259
|
+
if (structureIssues.length > 0) {
|
|
260
|
+
await migrateStructure(root, structureIssues);
|
|
261
|
+
}
|
|
262
|
+
|
|
166
263
|
updateLocalDeps(root, tag);
|
|
167
264
|
} catch {
|
|
168
|
-
// Not inside a plugin project — skip
|
|
265
|
+
// Not inside a plugin project — skip silently
|
|
169
266
|
}
|
|
170
267
|
}
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|