@leanmcp/cli 0.2.14 → 0.3.0
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/dist/index.js +129 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import chalk from "chalk";
|
|
|
17
17
|
import ora from "ora";
|
|
18
18
|
import path3 from "path";
|
|
19
19
|
import fs3 from "fs-extra";
|
|
20
|
+
import crypto from "crypto";
|
|
20
21
|
import chokidar from "chokidar";
|
|
21
22
|
|
|
22
23
|
// src/vite/scanUIApp.ts
|
|
@@ -131,6 +132,29 @@ import react from "@vitejs/plugin-react";
|
|
|
131
132
|
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
132
133
|
import fs2 from "fs-extra";
|
|
133
134
|
import path2 from "path";
|
|
135
|
+
function resolveReactDependency(startDir, packageName) {
|
|
136
|
+
const localPath = path2.join(startDir, "node_modules", packageName);
|
|
137
|
+
if (fs2.existsSync(localPath)) {
|
|
138
|
+
return localPath;
|
|
139
|
+
}
|
|
140
|
+
let currentDir = path2.dirname(startDir);
|
|
141
|
+
const maxDepth = 10;
|
|
142
|
+
let depth = 0;
|
|
143
|
+
while (depth < maxDepth) {
|
|
144
|
+
const nodeModulesPath = path2.join(currentDir, "node_modules", packageName);
|
|
145
|
+
if (fs2.existsSync(nodeModulesPath)) {
|
|
146
|
+
return nodeModulesPath;
|
|
147
|
+
}
|
|
148
|
+
const parentDir = path2.dirname(currentDir);
|
|
149
|
+
if (parentDir === currentDir) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
currentDir = parentDir;
|
|
153
|
+
depth++;
|
|
154
|
+
}
|
|
155
|
+
return localPath;
|
|
156
|
+
}
|
|
157
|
+
__name(resolveReactDependency, "resolveReactDependency");
|
|
134
158
|
async function buildUIComponent(uiApp, projectDir, isDev = false) {
|
|
135
159
|
const { componentPath, componentName, resourceUri } = uiApp;
|
|
136
160
|
const safeFileName = resourceUri.replace("ui://", "").replace(/\//g, "-") + ".html";
|
|
@@ -318,6 +342,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
318
342
|
);
|
|
319
343
|
`);
|
|
320
344
|
try {
|
|
345
|
+
const reactPath = resolveReactDependency(projectDir, "react");
|
|
346
|
+
const reactDomPath = resolveReactDependency(projectDir, "react-dom");
|
|
321
347
|
await vite.build({
|
|
322
348
|
root: tempDir,
|
|
323
349
|
plugins: [
|
|
@@ -326,11 +352,11 @@ createRoot(document.getElementById('root')!).render(
|
|
|
326
352
|
],
|
|
327
353
|
resolve: {
|
|
328
354
|
alias: {
|
|
329
|
-
// Resolve React from
|
|
330
|
-
"react":
|
|
331
|
-
"react-dom":
|
|
332
|
-
"react/jsx-runtime": path2.join(
|
|
333
|
-
"react/jsx-dev-runtime": path2.join(
|
|
355
|
+
// Resolve React from project or workspace root node_modules
|
|
356
|
+
"react": reactPath,
|
|
357
|
+
"react-dom": reactDomPath,
|
|
358
|
+
"react/jsx-runtime": path2.join(reactPath, "jsx-runtime"),
|
|
359
|
+
"react/jsx-dev-runtime": path2.join(reactPath, "jsx-dev-runtime")
|
|
334
360
|
}
|
|
335
361
|
},
|
|
336
362
|
css: {
|
|
@@ -386,8 +412,49 @@ async function writeUIManifest(manifest, projectDir) {
|
|
|
386
412
|
});
|
|
387
413
|
}
|
|
388
414
|
__name(writeUIManifest, "writeUIManifest");
|
|
415
|
+
async function deleteUIComponent(uri, projectDir) {
|
|
416
|
+
const safeFileName = uri.replace("ui://", "").replace(/\//g, "-") + ".html";
|
|
417
|
+
const htmlPath = path2.join(projectDir, "dist", "ui", safeFileName);
|
|
418
|
+
if (await fs2.pathExists(htmlPath)) {
|
|
419
|
+
await fs2.remove(htmlPath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
__name(deleteUIComponent, "deleteUIComponent");
|
|
389
423
|
|
|
390
424
|
// src/commands/dev.ts
|
|
425
|
+
function computeHash(filePath) {
|
|
426
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
427
|
+
return crypto.createHash("md5").update(content).digest("hex");
|
|
428
|
+
}
|
|
429
|
+
__name(computeHash, "computeHash");
|
|
430
|
+
function computeDiff(previousUIApps, currentUIApps, hashCache) {
|
|
431
|
+
const previousURIs = new Set(previousUIApps.map((app) => app.resourceUri));
|
|
432
|
+
const currentURIs = new Set(currentUIApps.map((app) => app.resourceUri));
|
|
433
|
+
const removed = Array.from(previousURIs).filter((uri) => !currentURIs.has(uri));
|
|
434
|
+
const added = new Set(Array.from(currentURIs).filter((uri) => !previousURIs.has(uri)));
|
|
435
|
+
const addedOrChanged = [];
|
|
436
|
+
for (const app of currentUIApps) {
|
|
437
|
+
const isNew = added.has(app.resourceUri);
|
|
438
|
+
if (isNew) {
|
|
439
|
+
addedOrChanged.push(app);
|
|
440
|
+
} else {
|
|
441
|
+
const oldHash = hashCache.get(app.resourceUri);
|
|
442
|
+
let newHash;
|
|
443
|
+
if (fs3.existsSync(app.componentPath)) {
|
|
444
|
+
newHash = computeHash(app.componentPath);
|
|
445
|
+
}
|
|
446
|
+
if (oldHash !== newHash) {
|
|
447
|
+
addedOrChanged.push(app);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
removed,
|
|
453
|
+
added,
|
|
454
|
+
addedOrChanged
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
__name(computeDiff, "computeDiff");
|
|
391
458
|
async function devCommand() {
|
|
392
459
|
const cwd = process.cwd();
|
|
393
460
|
if (!await fs3.pathExists(path3.join(cwd, "main.ts"))) {
|
|
@@ -395,16 +462,13 @@ async function devCommand() {
|
|
|
395
462
|
console.error(chalk.gray("Run this command from your project root."));
|
|
396
463
|
process.exit(1);
|
|
397
464
|
}
|
|
398
|
-
console.log(chalk.cyan("\
|
|
465
|
+
console.log(chalk.cyan("\nLeanMCP Development Server\n"));
|
|
399
466
|
const scanSpinner = ora("Scanning for @UIApp components...").start();
|
|
400
467
|
const uiApps = await scanUIApp(cwd);
|
|
401
468
|
if (uiApps.length === 0) {
|
|
402
|
-
scanSpinner.
|
|
469
|
+
scanSpinner.info("No @UIApp components found");
|
|
403
470
|
} else {
|
|
404
|
-
scanSpinner.
|
|
405
|
-
for (const app of uiApps) {
|
|
406
|
-
console.log(chalk.gray(` \u2022 ${app.componentName} \u2192 ${app.resourceUri}`));
|
|
407
|
-
}
|
|
471
|
+
scanSpinner.info(`Found ${uiApps.length} @UIApp component(s)`);
|
|
408
472
|
}
|
|
409
473
|
const manifest = {};
|
|
410
474
|
if (uiApps.length > 0) {
|
|
@@ -422,10 +486,10 @@ async function devCommand() {
|
|
|
422
486
|
if (errors.length > 0) {
|
|
423
487
|
buildSpinner.warn("Built with warnings");
|
|
424
488
|
for (const error of errors) {
|
|
425
|
-
console.error(chalk.yellow(`
|
|
489
|
+
console.error(chalk.yellow(` ${error}`));
|
|
426
490
|
}
|
|
427
491
|
} else {
|
|
428
|
-
buildSpinner.
|
|
492
|
+
buildSpinner.info("UI components built");
|
|
429
493
|
}
|
|
430
494
|
}
|
|
431
495
|
console.log(chalk.cyan("\nStarting development server...\n"));
|
|
@@ -439,26 +503,59 @@ async function devCommand() {
|
|
|
439
503
|
shell: true
|
|
440
504
|
});
|
|
441
505
|
let watcher = null;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
watcher.on("change", async (changedPath) => {
|
|
448
|
-
const app = uiApps.find((a) => a.componentPath === changedPath);
|
|
449
|
-
if (!app) return;
|
|
450
|
-
console.log(chalk.cyan(`
|
|
451
|
-
[UI] Rebuilding ${app.componentName}...`));
|
|
452
|
-
const result = await buildUIComponent(app, cwd, true);
|
|
453
|
-
if (result.success) {
|
|
454
|
-
manifest[app.resourceUri] = result.htmlPath;
|
|
455
|
-
await writeUIManifest(manifest, cwd);
|
|
456
|
-
console.log(chalk.green(`[UI] ${app.componentName} rebuilt successfully`));
|
|
457
|
-
} else {
|
|
458
|
-
console.log(chalk.yellow(`[UI] ${app.componentName} build failed: ${result.error}`));
|
|
459
|
-
}
|
|
460
|
-
});
|
|
506
|
+
const componentHashCache = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const app of uiApps) {
|
|
508
|
+
if (await fs3.pathExists(app.componentPath)) {
|
|
509
|
+
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
510
|
+
}
|
|
461
511
|
}
|
|
512
|
+
let previousUIApps = uiApps;
|
|
513
|
+
const mcpPath = path3.join(cwd, "mcp");
|
|
514
|
+
watcher = chokidar.watch(mcpPath, {
|
|
515
|
+
ignoreInitial: true,
|
|
516
|
+
ignored: [
|
|
517
|
+
"**/node_modules/**",
|
|
518
|
+
"**/*.d.ts"
|
|
519
|
+
],
|
|
520
|
+
persistent: true,
|
|
521
|
+
awaitWriteFinish: {
|
|
522
|
+
stabilityThreshold: 100,
|
|
523
|
+
pollInterval: 50
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
let debounceTimer = null;
|
|
527
|
+
watcher.on("all", async (event, changedPath) => {
|
|
528
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
529
|
+
debounceTimer = setTimeout(async () => {
|
|
530
|
+
const currentUIApps = await scanUIApp(cwd);
|
|
531
|
+
const diff = computeDiff(previousUIApps, currentUIApps, componentHashCache);
|
|
532
|
+
if (diff.removed.length === 0 && diff.addedOrChanged.length === 0) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const uri of diff.removed) {
|
|
536
|
+
console.log(chalk.yellow(`Removing ${uri}...`));
|
|
537
|
+
await deleteUIComponent(uri, cwd);
|
|
538
|
+
delete manifest[uri];
|
|
539
|
+
componentHashCache.delete(uri);
|
|
540
|
+
}
|
|
541
|
+
for (const app of diff.addedOrChanged) {
|
|
542
|
+
const action = diff.added.has(app.resourceUri) ? "Building" : "Rebuilding";
|
|
543
|
+
console.log(chalk.cyan(`${action} ${app.componentName}...`));
|
|
544
|
+
const result = await buildUIComponent(app, cwd, true);
|
|
545
|
+
if (result.success) {
|
|
546
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
547
|
+
if (await fs3.pathExists(app.componentPath)) {
|
|
548
|
+
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
549
|
+
}
|
|
550
|
+
console.log(chalk.green(`${app.componentName} ${action.toLowerCase()} complete`));
|
|
551
|
+
} else {
|
|
552
|
+
console.log(chalk.yellow(`Build failed: ${result.error}`));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
await writeUIManifest(manifest, cwd);
|
|
556
|
+
previousUIApps = currentUIApps;
|
|
557
|
+
}, 150);
|
|
558
|
+
});
|
|
462
559
|
let isCleaningUp = false;
|
|
463
560
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
464
561
|
if (isCleaningUp) return;
|