@leanmcp/cli 0.2.9 → 0.2.11
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 +280 -137
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -59,14 +59,33 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
59
59
|
while ((match = uiAppRegex.exec(content)) !== null) {
|
|
60
60
|
const decoratorBody = match[1];
|
|
61
61
|
const methodName = match[2];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
let componentPath;
|
|
63
|
+
let componentName;
|
|
64
|
+
const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
|
|
65
|
+
if (stringMatch) {
|
|
66
|
+
const relativePath = stringMatch[1];
|
|
67
|
+
const dir = path.dirname(filePath);
|
|
68
|
+
let resolvedPath = path.resolve(dir, relativePath);
|
|
69
|
+
if (!resolvedPath.endsWith(".tsx") && !resolvedPath.endsWith(".ts")) {
|
|
70
|
+
if (fs.existsSync(resolvedPath + ".tsx")) {
|
|
71
|
+
resolvedPath += ".tsx";
|
|
72
|
+
} else if (fs.existsSync(resolvedPath + ".ts")) {
|
|
73
|
+
resolvedPath += ".ts";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
componentPath = resolvedPath;
|
|
77
|
+
componentName = path.basename(relativePath).replace(/\.(tsx?|jsx?)$/, "");
|
|
78
|
+
} else {
|
|
79
|
+
const identifierMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
|
|
80
|
+
if (!identifierMatch) continue;
|
|
81
|
+
componentName = identifierMatch[1];
|
|
82
|
+
componentPath = importMap[componentName];
|
|
83
|
+
if (!componentPath) {
|
|
84
|
+
console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
69
87
|
}
|
|
88
|
+
if (!componentPath) continue;
|
|
70
89
|
const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
|
|
71
90
|
const resourceUri = `ui://${servicePrefix}/${methodName}`;
|
|
72
91
|
results.push({
|
|
@@ -134,11 +153,137 @@ async function buildUIComponent(uiApp, projectDir, isDev = false) {
|
|
|
134
153
|
<script type="module" src="./entry.tsx"></script>
|
|
135
154
|
</body>
|
|
136
155
|
</html>`);
|
|
156
|
+
const tailwindConfig = path2.join(tempDir, "tailwind.config.js");
|
|
157
|
+
await fs2.writeFile(tailwindConfig, `
|
|
158
|
+
/** @type {import('tailwindcss').Config} */
|
|
159
|
+
module.exports = {
|
|
160
|
+
content: [
|
|
161
|
+
'${path2.join(projectDir, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
|
|
162
|
+
'${path2.join(projectDir, "mcp/**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
|
|
163
|
+
'${path2.join(projectDir, "node_modules/@leanmcp/ui/**/*.{js,mjs}").replace(/\\/g, "/")}',
|
|
164
|
+
'${path2.join(projectDir, "../../packages/ui/src/**/*.{ts,tsx}").replace(/\\/g, "/")}',
|
|
165
|
+
],
|
|
166
|
+
darkMode: ['class'],
|
|
167
|
+
theme: {
|
|
168
|
+
container: {
|
|
169
|
+
center: true,
|
|
170
|
+
padding: '2rem',
|
|
171
|
+
screens: {
|
|
172
|
+
'2xl': '1400px',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
extend: {
|
|
176
|
+
colors: {
|
|
177
|
+
border: 'hsl(var(--border))',
|
|
178
|
+
input: 'hsl(var(--input))',
|
|
179
|
+
ring: 'hsl(var(--ring))',
|
|
180
|
+
background: 'hsl(var(--background))',
|
|
181
|
+
foreground: 'hsl(var(--foreground))',
|
|
182
|
+
primary: {
|
|
183
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
184
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
185
|
+
},
|
|
186
|
+
secondary: {
|
|
187
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
188
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
189
|
+
},
|
|
190
|
+
destructive: {
|
|
191
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
192
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
193
|
+
},
|
|
194
|
+
muted: {
|
|
195
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
196
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
197
|
+
},
|
|
198
|
+
accent: {
|
|
199
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
200
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
201
|
+
},
|
|
202
|
+
popover: {
|
|
203
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
204
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
205
|
+
},
|
|
206
|
+
card: {
|
|
207
|
+
DEFAULT: 'hsl(var(--card))',
|
|
208
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
borderRadius: {
|
|
212
|
+
lg: 'var(--radius)',
|
|
213
|
+
md: 'calc(var(--radius) - 2px)',
|
|
214
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
plugins: [],
|
|
219
|
+
}
|
|
220
|
+
`);
|
|
221
|
+
const stylesCss = path2.join(tempDir, "styles.css");
|
|
222
|
+
await fs2.writeFile(stylesCss, `
|
|
223
|
+
@tailwind base;
|
|
224
|
+
@tailwind components;
|
|
225
|
+
@tailwind utilities;
|
|
226
|
+
|
|
227
|
+
@layer base {
|
|
228
|
+
:root {
|
|
229
|
+
--background: 0 0% 100%;
|
|
230
|
+
--foreground: 222.2 84% 4.9%;
|
|
231
|
+
--card: 0 0% 100%;
|
|
232
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
233
|
+
--popover: 0 0% 100%;
|
|
234
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
235
|
+
--primary: 222.2 47.4% 11.2%;
|
|
236
|
+
--primary-foreground: 210 40% 98%;
|
|
237
|
+
--secondary: 210 40% 96.1%;
|
|
238
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
239
|
+
--muted: 210 40% 96.1%;
|
|
240
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
241
|
+
--accent: 210 40% 96.1%;
|
|
242
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
243
|
+
--destructive: 0 84.2% 60.2%;
|
|
244
|
+
--destructive-foreground: 210 40% 98%;
|
|
245
|
+
--border: 214.3 31.8% 91.4%;
|
|
246
|
+
--input: 214.3 31.8% 91.4%;
|
|
247
|
+
--ring: 222.2 84% 4.9%;
|
|
248
|
+
--radius: 0.5rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.dark {
|
|
252
|
+
--background: 222.2 84% 4.9%;
|
|
253
|
+
--foreground: 210 40% 98%;
|
|
254
|
+
--card: 222.2 84% 4.9%;
|
|
255
|
+
--card-foreground: 210 40% 98%;
|
|
256
|
+
--popover: 222.2 84% 4.9%;
|
|
257
|
+
--popover-foreground: 210 40% 98%;
|
|
258
|
+
--primary: 210 40% 98%;
|
|
259
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
260
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
261
|
+
--secondary-foreground: 210 40% 98%;
|
|
262
|
+
--muted: 217.2 32.6% 17.5%;
|
|
263
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
264
|
+
--accent: 217.2 32.6% 17.5%;
|
|
265
|
+
--accent-foreground: 210 40% 98%;
|
|
266
|
+
--destructive: 0 62.8% 30.6%;
|
|
267
|
+
--destructive-foreground: 210 40% 98%;
|
|
268
|
+
--border: 217.2 32.6% 17.5%;
|
|
269
|
+
--input: 217.2 32.6% 17.5%;
|
|
270
|
+
--ring: 212.7 26.8% 83.9%;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
body {
|
|
274
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
275
|
+
background-color: hsl(var(--background));
|
|
276
|
+
color: hsl(var(--foreground));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
`);
|
|
137
280
|
const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
|
|
138
281
|
await fs2.writeFile(entryJs, `
|
|
139
282
|
import React, { StrictMode } from 'react';
|
|
140
283
|
import { createRoot } from 'react-dom/client';
|
|
141
|
-
import { AppProvider } from '@leanmcp/ui';
|
|
284
|
+
import { AppProvider, Toaster } from '@leanmcp/ui';
|
|
285
|
+
import '@leanmcp/ui/styles.css';
|
|
286
|
+
import './styles.css';
|
|
142
287
|
import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
|
|
143
288
|
|
|
144
289
|
const APP_INFO = {
|
|
@@ -150,6 +295,7 @@ function App() {
|
|
|
150
295
|
return (
|
|
151
296
|
<AppProvider appInfo={APP_INFO}>
|
|
152
297
|
<${componentName} />
|
|
298
|
+
<Toaster />
|
|
153
299
|
</AppProvider>
|
|
154
300
|
);
|
|
155
301
|
}
|
|
@@ -167,6 +313,16 @@ createRoot(document.getElementById('root')!).render(
|
|
|
167
313
|
react(),
|
|
168
314
|
viteSingleFile()
|
|
169
315
|
],
|
|
316
|
+
css: {
|
|
317
|
+
postcss: {
|
|
318
|
+
plugins: [
|
|
319
|
+
(await import("tailwindcss")).default({
|
|
320
|
+
config: tailwindConfig
|
|
321
|
+
}),
|
|
322
|
+
(await import("autoprefixer")).default
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
},
|
|
170
326
|
build: {
|
|
171
327
|
outDir,
|
|
172
328
|
emptyOutDir: false,
|
|
@@ -400,7 +556,7 @@ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready
|
|
|
400
556
|
Examples:
|
|
401
557
|
$ leanmcp create my-app --allow-all # Scaffold without interactive prompts
|
|
402
558
|
`);
|
|
403
|
-
program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").action(async (projectName, options) => {
|
|
559
|
+
program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").action(async (projectName, options) => {
|
|
404
560
|
const spinner = ora3(`Creating project ${projectName}...`).start();
|
|
405
561
|
const targetDir = path5.join(process.cwd(), projectName);
|
|
406
562
|
if (fs5.existsSync(targetDir)) {
|
|
@@ -465,152 +621,139 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
465
621
|
await fs5.writeJSON(path5.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
466
622
|
spaces: 2
|
|
467
623
|
});
|
|
624
|
+
const dashboardLine = options.dashboard === false ? `
|
|
625
|
+
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
468
626
|
const mainTs = `import dotenv from "dotenv";
|
|
469
|
-
import { createHTTPServer
|
|
627
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
470
628
|
|
|
471
629
|
// Load environment variables
|
|
472
630
|
dotenv.config();
|
|
473
631
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
*/
|
|
480
|
-
const serverFactory = async () => {
|
|
481
|
-
const server = new MCPServer({
|
|
482
|
-
name: "${projectName}",
|
|
483
|
-
version: "1.0.0",
|
|
484
|
-
logging: true
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// Services are automatically discovered and registered from ./mcp
|
|
488
|
-
return server.getServer();
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
// Start the HTTP server
|
|
492
|
-
await createHTTPServer(serverFactory, {
|
|
493
|
-
port: PORT,
|
|
632
|
+
// Services are automatically discovered from ./mcp directory
|
|
633
|
+
await createHTTPServer({
|
|
634
|
+
name: "${projectName}",
|
|
635
|
+
version: "1.0.0",
|
|
636
|
+
port: 3001,
|
|
494
637
|
cors: true,
|
|
495
|
-
logging: true
|
|
638
|
+
logging: true${dashboardLine}
|
|
496
639
|
});
|
|
497
640
|
|
|
498
|
-
console.log(
|
|
641
|
+
console.log("\\n${projectName} MCP Server");
|
|
499
642
|
`;
|
|
500
643
|
await fs5.writeFile(path5.join(targetDir, "main.ts"), mainTs);
|
|
501
644
|
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
502
645
|
|
|
503
|
-
/**
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
// Input schema with validation decorators
|
|
510
|
-
class CalculateInput {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
class EchoInput {
|
|
527
|
-
@SchemaConstraint({
|
|
528
|
-
description: "Message to echo back",
|
|
529
|
-
minLength: 1
|
|
530
|
-
})
|
|
531
|
-
message!: string;
|
|
532
|
-
}
|
|
646
|
+
/**
|
|
647
|
+
* Example service demonstrating LeanMCP SDK decorators
|
|
648
|
+
*
|
|
649
|
+
* This is a simple example to get you started. Add your own tools, resources, and prompts here!
|
|
650
|
+
*/
|
|
651
|
+
|
|
652
|
+
// Input schema with validation decorators
|
|
653
|
+
class CalculateInput {
|
|
654
|
+
@SchemaConstraint({ description: "First number" })
|
|
655
|
+
a!: number;
|
|
656
|
+
|
|
657
|
+
@SchemaConstraint({ description: "Second number" })
|
|
658
|
+
b!: number;
|
|
659
|
+
|
|
660
|
+
@Optional()
|
|
661
|
+
@SchemaConstraint({
|
|
662
|
+
description: "Operation to perform",
|
|
663
|
+
enum: ["add", "subtract", "multiply", "divide"],
|
|
664
|
+
default: "add"
|
|
665
|
+
})
|
|
666
|
+
operation?: string;
|
|
667
|
+
}
|
|
533
668
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// Ensure numerical operations by explicitly converting to numbers
|
|
541
|
-
const a = Number(input.a);
|
|
542
|
-
const b = Number(input.b);
|
|
543
|
-
let result: number;
|
|
544
|
-
|
|
545
|
-
switch (input.operation || "add") {
|
|
546
|
-
case "add":
|
|
547
|
-
result = a + b;
|
|
548
|
-
break;
|
|
549
|
-
case "subtract":
|
|
550
|
-
result = a - b;
|
|
551
|
-
break;
|
|
552
|
-
case "multiply":
|
|
553
|
-
result = a * b;
|
|
554
|
-
break;
|
|
555
|
-
case "divide":
|
|
556
|
-
if (b === 0) throw new Error("Cannot divide by zero");
|
|
557
|
-
result = a / b;
|
|
558
|
-
break;
|
|
559
|
-
default:
|
|
560
|
-
throw new Error("Invalid operation");
|
|
669
|
+
class EchoInput {
|
|
670
|
+
@SchemaConstraint({
|
|
671
|
+
description: "Message to echo back",
|
|
672
|
+
minLength: 1
|
|
673
|
+
})
|
|
674
|
+
message!: string;
|
|
561
675
|
}
|
|
562
676
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
677
|
+
export class ExampleService {
|
|
678
|
+
@Tool({
|
|
679
|
+
description: "Perform arithmetic operations with automatic schema validation",
|
|
680
|
+
inputClass: CalculateInput
|
|
681
|
+
})
|
|
682
|
+
async calculate(input: CalculateInput) {
|
|
683
|
+
// Ensure numerical operations by explicitly converting to numbers
|
|
684
|
+
const a = Number(input.a);
|
|
685
|
+
const b = Number(input.b);
|
|
686
|
+
let result: number;
|
|
687
|
+
|
|
688
|
+
switch (input.operation || "add") {
|
|
689
|
+
case "add":
|
|
690
|
+
result = a + b;
|
|
691
|
+
break;
|
|
692
|
+
case "subtract":
|
|
693
|
+
result = a - b;
|
|
694
|
+
break;
|
|
695
|
+
case "multiply":
|
|
696
|
+
result = a * b;
|
|
697
|
+
break;
|
|
698
|
+
case "divide":
|
|
699
|
+
if (b === 0) throw new Error("Cannot divide by zero");
|
|
700
|
+
result = a / b;
|
|
701
|
+
break;
|
|
702
|
+
default:
|
|
703
|
+
throw new Error("Invalid operation");
|
|
704
|
+
}
|
|
574
705
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}, null, 2)
|
|
587
|
-
}]
|
|
588
|
-
};
|
|
589
|
-
}
|
|
706
|
+
return {
|
|
707
|
+
content: [{
|
|
708
|
+
type: "text" as const,
|
|
709
|
+
text: JSON.stringify({
|
|
710
|
+
operation: input.operation || "add",
|
|
711
|
+
operands: { a: input.a, b: input.b },
|
|
712
|
+
result
|
|
713
|
+
}, null, 2)
|
|
714
|
+
}]
|
|
715
|
+
};
|
|
716
|
+
}
|
|
590
717
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
718
|
+
@Tool({
|
|
719
|
+
description: "Echo a message back",
|
|
720
|
+
inputClass: EchoInput
|
|
721
|
+
})
|
|
722
|
+
async echo(input: EchoInput) {
|
|
723
|
+
return {
|
|
724
|
+
content: [{
|
|
725
|
+
type: "text" as const,
|
|
726
|
+
text: JSON.stringify({
|
|
727
|
+
echoed: input.message,
|
|
728
|
+
timestamp: new Date().toISOString()
|
|
729
|
+
}, null, 2)
|
|
730
|
+
}]
|
|
731
|
+
};
|
|
732
|
+
}
|
|
605
733
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
734
|
+
@Resource({ description: "Get server information" })
|
|
735
|
+
async serverInfo() {
|
|
736
|
+
return {
|
|
737
|
+
contents: [{
|
|
738
|
+
uri: "server://info",
|
|
739
|
+
mimeType: "application/json",
|
|
740
|
+
text: JSON.stringify({
|
|
741
|
+
name: "${projectName}",
|
|
742
|
+
version: "1.0.0",
|
|
743
|
+
uptime: process.uptime()
|
|
744
|
+
}, null, 2)
|
|
745
|
+
}]
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
@Prompt({ description: "Generate a greeting prompt" })
|
|
750
|
+
async greeting(args: { name?: string }) {
|
|
751
|
+
return {
|
|
752
|
+
messages: [{
|
|
753
|
+
role: "user" as const,
|
|
754
|
+
content: {
|
|
755
|
+
type: "text" as const,
|
|
756
|
+
text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
|
|
614
757
|
}
|
|
615
758
|
}]
|
|
616
759
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Command-line interface for scaffolding LeanMCP projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"leanmcp": "bin/leanmcp.js"
|
|
@@ -29,12 +29,15 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@inquirer/prompts": "^7.0.1",
|
|
31
31
|
"@vitejs/plugin-react": "^4.3.0",
|
|
32
|
+
"autoprefixer": "^10.4.16",
|
|
32
33
|
"chalk": "^5.3.0",
|
|
33
34
|
"chokidar": "^4.0.0",
|
|
34
35
|
"commander": "^12.0.0",
|
|
35
36
|
"fs-extra": "^11.2.0",
|
|
36
37
|
"glob": "^11.0.0",
|
|
37
38
|
"ora": "^8.1.0",
|
|
39
|
+
"postcss": "^8.4.32",
|
|
40
|
+
"tailwindcss": "^3.4.0",
|
|
38
41
|
"vite": "^5.4.0",
|
|
39
42
|
"vite-plugin-singlefile": "^2.3.0"
|
|
40
43
|
},
|