@voltx/cli 0.3.4 → 0.3.6
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 +54 -22
- package/dist/build.d.mts +4 -3
- package/dist/build.d.ts +4 -3
- package/dist/build.js +56 -27
- package/dist/build.mjs +1 -2
- package/dist/chunk-2LHDOMF2.mjs +680 -0
- package/dist/chunk-5DVBIJXU.mjs +84 -0
- package/dist/chunk-65PVXS4D.mjs +894 -0
- package/dist/chunk-7JVIEGSA.mjs +141 -0
- package/dist/chunk-A4NA4AJN.mjs +786 -0
- package/dist/chunk-AAAHANST.mjs +839 -0
- package/dist/chunk-AD3WMFZF.mjs +205 -0
- package/dist/chunk-CSSHLVYS.mjs +83 -0
- package/dist/chunk-CWOSNV5O.mjs +150 -0
- package/dist/chunk-EI6XBYKB.mjs +84 -0
- package/dist/chunk-FI2W4L4S.mjs +205 -0
- package/dist/chunk-G2INQCCJ.mjs +907 -0
- package/dist/chunk-H2DTIOEO.mjs +150 -0
- package/dist/chunk-IS2WTE3C.mjs +138 -0
- package/dist/chunk-JECCDBYI.mjs +730 -0
- package/dist/chunk-KX2MRJUO.mjs +795 -0
- package/dist/chunk-LTGMHAZS.mjs +147 -0
- package/dist/chunk-OPO6RUFP.mjs +698 -0
- package/dist/chunk-PWQSKYAM.mjs +682 -0
- package/dist/chunk-Q5XCFN7L.mjs +1026 -0
- package/dist/chunk-QSU6FZC7.mjs +497 -0
- package/dist/chunk-RYWRFHEC.mjs +83 -0
- package/dist/chunk-SU4Q3PTH.mjs +201 -0
- package/dist/chunk-TFVNHM7S.mjs +1028 -0
- package/dist/chunk-UXI3QSDN.mjs +121 -0
- package/dist/chunk-VD3CNPNP.mjs +123 -0
- package/dist/chunk-X6VOAPRJ.mjs +756 -0
- package/dist/cli.js +935 -308
- package/dist/cli.mjs +7 -6
- package/dist/create.d.mts +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +726 -192
- package/dist/create.mjs +1 -2
- package/dist/dev.d.mts +6 -4
- package/dist/dev.d.ts +6 -4
- package/dist/dev.js +119 -46
- package/dist/dev.mjs +1 -2
- package/dist/generate.js +13 -13
- package/dist/generate.mjs +1 -2
- package/dist/index.js +922 -296
- package/dist/index.mjs +5 -6
- package/dist/start.js +7 -17
- package/dist/start.mjs +1 -2
- package/dist/welcome.mjs +0 -1
- package/package.json +11 -3
package/dist/cli.js
CHANGED
|
@@ -145,8 +145,11 @@ var create_exports = {};
|
|
|
145
145
|
__export(create_exports, {
|
|
146
146
|
createProject: () => createProject
|
|
147
147
|
});
|
|
148
|
+
function v(pkg) {
|
|
149
|
+
return VV[pkg] ?? "^0.3.0";
|
|
150
|
+
}
|
|
148
151
|
async function createProject(options) {
|
|
149
|
-
const { name, template = "blank", auth = "none" } = options;
|
|
152
|
+
const { name, template = "blank", auth = "none", shadcn = false } = options;
|
|
150
153
|
const targetDir = path.resolve(process.cwd(), name);
|
|
151
154
|
if (fs.existsSync(targetDir)) {
|
|
152
155
|
console.error(`[voltx] Directory "${name}" already exists.`);
|
|
@@ -156,21 +159,41 @@ async function createProject(options) {
|
|
|
156
159
|
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
157
160
|
const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
|
|
158
161
|
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
159
|
-
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli":
|
|
162
|
+
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": v("@voltx/cli") };
|
|
160
163
|
if (auth === "better-auth") {
|
|
161
|
-
deps["@voltx/auth"] =
|
|
164
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
162
165
|
deps["better-auth"] = "^1.5.0";
|
|
163
166
|
} else if (auth === "jwt") {
|
|
164
|
-
deps["@voltx/auth"] =
|
|
167
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
165
168
|
deps["jose"] = "^6.0.0";
|
|
166
169
|
}
|
|
170
|
+
const devDeps = { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" };
|
|
171
|
+
deps["hono"] = "^4.7.0";
|
|
172
|
+
deps["@hono/node-server"] = "^1.14.0";
|
|
173
|
+
deps["react"] = "^19.0.0";
|
|
174
|
+
deps["react-dom"] = "^19.0.0";
|
|
175
|
+
deps["tailwindcss"] = "^4.0.0";
|
|
176
|
+
devDeps["vite"] = "^6.0.0";
|
|
177
|
+
devDeps["@hono/vite-dev-server"] = "^0.7.0";
|
|
178
|
+
devDeps["@vitejs/plugin-react"] = "^4.3.0";
|
|
179
|
+
devDeps["@tailwindcss/vite"] = "^4.0.0";
|
|
180
|
+
devDeps["@types/react"] = "^19.0.0";
|
|
181
|
+
devDeps["@types/react-dom"] = "^19.0.0";
|
|
182
|
+
if (shadcn) {
|
|
183
|
+
deps["class-variance-authority"] = "^0.7.0";
|
|
184
|
+
deps["clsx"] = "^2.1.0";
|
|
185
|
+
deps["tailwind-merge"] = "^3.0.0";
|
|
186
|
+
deps["lucide-react"] = "^0.468.0";
|
|
187
|
+
}
|
|
188
|
+
devDeps["@voltx/cli"] = deps["@voltx/cli"] ?? v("@voltx/cli");
|
|
189
|
+
delete deps["@voltx/cli"];
|
|
167
190
|
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify({
|
|
168
191
|
name,
|
|
169
192
|
version: "0.1.0",
|
|
170
193
|
private: true,
|
|
171
|
-
scripts: { dev: "voltx dev", build: "voltx build", start: "voltx start" },
|
|
194
|
+
scripts: { dev: "npx voltx dev", build: "npx voltx build", start: "npx voltx start" },
|
|
172
195
|
dependencies: deps,
|
|
173
|
-
devDependencies:
|
|
196
|
+
devDependencies: devDeps
|
|
174
197
|
}, null, 2));
|
|
175
198
|
let config = `import { defineConfig } from "@voltx/core";
|
|
176
199
|
|
|
@@ -191,31 +214,53 @@ export default defineConfig({
|
|
|
191
214
|
},`;
|
|
192
215
|
config += `
|
|
193
216
|
server: {
|
|
194
|
-
routesDir: "
|
|
217
|
+
routesDir: "api",
|
|
195
218
|
staticDir: "public",
|
|
196
219
|
cors: true,
|
|
197
220
|
},
|
|
198
221
|
});
|
|
199
222
|
`;
|
|
200
223
|
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), config);
|
|
201
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
224
|
+
fs.mkdirSync(path.join(targetDir, "api"), { recursive: true });
|
|
202
225
|
fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
|
|
203
|
-
fs.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
fs.writeFileSync(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
|
|
227
|
+
fs.mkdirSync(path.join(targetDir, "src", "components"), { recursive: true });
|
|
228
|
+
fs.mkdirSync(path.join(targetDir, "src", "hooks"), { recursive: true });
|
|
229
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
230
|
+
fs.writeFileSync(path.join(targetDir, "public", "favicon.svg"), generateFaviconSVG());
|
|
231
|
+
fs.writeFileSync(path.join(targetDir, "public", "robots.txt"), generateRobotsTxt());
|
|
232
|
+
fs.writeFileSync(path.join(targetDir, "public", "site.webmanifest"), generateWebManifest(name));
|
|
233
|
+
const tsconfig = {
|
|
234
|
+
compilerOptions: {
|
|
235
|
+
target: "ES2022",
|
|
236
|
+
module: "ESNext",
|
|
237
|
+
moduleResolution: "bundler",
|
|
238
|
+
strict: true,
|
|
239
|
+
esModuleInterop: true,
|
|
240
|
+
skipLibCheck: true,
|
|
241
|
+
outDir: "dist",
|
|
242
|
+
baseUrl: ".",
|
|
243
|
+
paths: { "@/*": ["./src/*"] },
|
|
244
|
+
jsx: "react-jsx"
|
|
245
|
+
},
|
|
246
|
+
include: ["src", "api", "server.ts", "voltx.config.ts"]
|
|
247
|
+
};
|
|
248
|
+
if (template === "agent-app") tsconfig.include.push("agents", "tools");
|
|
249
|
+
fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
250
|
+
fs.writeFileSync(path.join(targetDir, "server.ts"), generateServerEntry(name, template));
|
|
251
|
+
fs.writeFileSync(path.join(targetDir, "vite.config.ts"), generateViteConfigFile("server.ts"));
|
|
252
|
+
fs.writeFileSync(path.join(targetDir, "src", "entry-client.tsx"), generateEntryClient());
|
|
253
|
+
fs.writeFileSync(path.join(targetDir, "src", "entry-server.tsx"), generateEntryServer());
|
|
254
|
+
fs.writeFileSync(path.join(targetDir, "src", "layout.tsx"), generateLayoutComponent(name));
|
|
255
|
+
fs.writeFileSync(path.join(targetDir, "src", "globals.css"), generateGlobalCSS(shadcn));
|
|
256
|
+
if (shadcn) {
|
|
257
|
+
fs.writeFileSync(path.join(targetDir, "src", "lib", "utils.ts"), generateCnUtil());
|
|
258
|
+
fs.writeFileSync(path.join(targetDir, "components.json"), generateComponentsJson());
|
|
259
|
+
}
|
|
260
|
+
fs.writeFileSync(path.join(targetDir, "src", "app.tsx"), generateAppComponent(name, template));
|
|
216
261
|
fs.writeFileSync(
|
|
217
|
-
path.join(targetDir, "
|
|
218
|
-
`// GET / \u2014 Health check
|
|
262
|
+
path.join(targetDir, "api", "index.ts"),
|
|
263
|
+
`// GET /api \u2014 Health check
|
|
219
264
|
import type { Context } from "@voltx/server";
|
|
220
265
|
|
|
221
266
|
export function GET(c: Context) {
|
|
@@ -225,7 +270,7 @@ export function GET(c: Context) {
|
|
|
225
270
|
);
|
|
226
271
|
if (template === "chatbot" || template === "agent-app") {
|
|
227
272
|
fs.writeFileSync(
|
|
228
|
-
path.join(targetDir, "
|
|
273
|
+
path.join(targetDir, "api", "chat.ts"),
|
|
229
274
|
`// POST /api/chat \u2014 Streaming chat with conversation memory
|
|
230
275
|
import type { Context } from "@voltx/server";
|
|
231
276
|
import { streamText } from "@voltx/ai";
|
|
@@ -259,9 +304,9 @@ export async function POST(c: Context) {
|
|
|
259
304
|
);
|
|
260
305
|
}
|
|
261
306
|
if (template === "agent-app") {
|
|
262
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
263
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
264
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
307
|
+
fs.mkdirSync(path.join(targetDir, "agents"), { recursive: true });
|
|
308
|
+
fs.mkdirSync(path.join(targetDir, "tools"), { recursive: true });
|
|
309
|
+
fs.writeFileSync(path.join(targetDir, "tools", "calculator.ts"), `// Calculator tool \u2014 evaluates math expressions (no API key needed)
|
|
265
310
|
import type { Tool } from "@voltx/agents";
|
|
266
311
|
|
|
267
312
|
export const calculatorTool: Tool = {
|
|
@@ -286,7 +331,7 @@ export const calculatorTool: Tool = {
|
|
|
286
331
|
},
|
|
287
332
|
};
|
|
288
333
|
`);
|
|
289
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
334
|
+
fs.writeFileSync(path.join(targetDir, "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
|
|
290
335
|
import type { Tool } from "@voltx/agents";
|
|
291
336
|
|
|
292
337
|
export const datetimeTool: Tool = {
|
|
@@ -307,7 +352,7 @@ export const datetimeTool: Tool = {
|
|
|
307
352
|
},
|
|
308
353
|
};
|
|
309
354
|
`);
|
|
310
|
-
fs.writeFileSync(path.join(targetDir, "
|
|
355
|
+
fs.writeFileSync(path.join(targetDir, "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
|
|
311
356
|
import { createAgent } from "@voltx/agents";
|
|
312
357
|
import { calculatorTool } from "../tools/calculator";
|
|
313
358
|
import { datetimeTool } from "../tools/datetime";
|
|
@@ -321,10 +366,10 @@ export const assistant = createAgent({
|
|
|
321
366
|
});
|
|
322
367
|
`);
|
|
323
368
|
fs.writeFileSync(
|
|
324
|
-
path.join(targetDir, "
|
|
369
|
+
path.join(targetDir, "api", "agent.ts"),
|
|
325
370
|
`// POST /api/agent \u2014 Run the AI agent
|
|
326
371
|
import type { Context } from "@voltx/server";
|
|
327
|
-
import { assistant } from "
|
|
372
|
+
import { assistant } from "../agents/assistant";
|
|
328
373
|
|
|
329
374
|
export async function POST(c: Context) {
|
|
330
375
|
const { input } = await c.req.json();
|
|
@@ -337,9 +382,9 @@ export async function POST(c: Context) {
|
|
|
337
382
|
}
|
|
338
383
|
if (template === "rag-app") {
|
|
339
384
|
const embedModel = "openai:text-embedding-3-small";
|
|
340
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
385
|
+
fs.mkdirSync(path.join(targetDir, "api", "rag"), { recursive: true });
|
|
341
386
|
fs.writeFileSync(
|
|
342
|
-
path.join(targetDir, "
|
|
387
|
+
path.join(targetDir, "api", "rag", "query.ts"),
|
|
343
388
|
`// POST /api/rag/query \u2014 Query documents with RAG
|
|
344
389
|
import type { Context } from "@voltx/server";
|
|
345
390
|
import { streamText } from "@voltx/ai";
|
|
@@ -363,7 +408,7 @@ export async function POST(c: Context) {
|
|
|
363
408
|
`
|
|
364
409
|
);
|
|
365
410
|
fs.writeFileSync(
|
|
366
|
-
path.join(targetDir, "
|
|
411
|
+
path.join(targetDir, "api", "rag", "ingest.ts"),
|
|
367
412
|
`// POST /api/rag/ingest \u2014 Ingest documents into the vector store
|
|
368
413
|
import type { Context } from "@voltx/server";
|
|
369
414
|
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
@@ -383,12 +428,12 @@ export async function POST(c: Context) {
|
|
|
383
428
|
);
|
|
384
429
|
}
|
|
385
430
|
if (auth === "better-auth") {
|
|
386
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
431
|
+
fs.mkdirSync(path.join(targetDir, "api", "auth"), { recursive: true });
|
|
387
432
|
fs.writeFileSync(
|
|
388
|
-
path.join(targetDir, "
|
|
433
|
+
path.join(targetDir, "api", "auth", "[...path].ts"),
|
|
389
434
|
`// ALL /api/auth/* \u2014 Better Auth handler
|
|
390
435
|
import type { Context } from "@voltx/server";
|
|
391
|
-
import { auth } from "
|
|
436
|
+
import { auth } from "../../src/lib/auth";
|
|
392
437
|
import { createAuthHandler } from "@voltx/auth";
|
|
393
438
|
|
|
394
439
|
const handler = createAuthHandler(auth);
|
|
@@ -397,7 +442,6 @@ export const GET = (c: Context) => handler(c);
|
|
|
397
442
|
export const POST = (c: Context) => handler(c);
|
|
398
443
|
`
|
|
399
444
|
);
|
|
400
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
401
445
|
fs.writeFileSync(
|
|
402
446
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
403
447
|
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
@@ -414,7 +458,6 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
414
458
|
`
|
|
415
459
|
);
|
|
416
460
|
} else if (auth === "jwt") {
|
|
417
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
418
461
|
fs.writeFileSync(
|
|
419
462
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
420
463
|
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
@@ -431,9 +474,9 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
431
474
|
`
|
|
432
475
|
);
|
|
433
476
|
fs.writeFileSync(
|
|
434
|
-
path.join(targetDir, "
|
|
477
|
+
path.join(targetDir, "api", "auth.ts"),
|
|
435
478
|
`import type { Context } from "@voltx/server";
|
|
436
|
-
import { jwt } from "
|
|
479
|
+
import { jwt } from "../src/lib/auth";
|
|
437
480
|
|
|
438
481
|
export async function POST(c: Context) {
|
|
439
482
|
const { email, password } = await c.req.json();
|
|
@@ -450,14 +493,6 @@ export async function POST(c: Context) {
|
|
|
450
493
|
envContent += "# \u2500\u2500\u2500 Database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
451
494
|
} else if (template === "chatbot" || template === "agent-app") {
|
|
452
495
|
envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCEREBRAS_API_KEY=csk-...\n\n";
|
|
453
|
-
if (template === "agent-app") {
|
|
454
|
-
envContent += "# \u2500\u2500\u2500 Database (optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
455
|
-
envContent += "# \u2500\u2500\u2500 Tool API Keys (add keys for tools you use) \u2500\u2500\n";
|
|
456
|
-
envContent += "# TAVILY_API_KEY=tvly-... (Web Search \u2014 https://tavily.com)\n";
|
|
457
|
-
envContent += "# SERPER_API_KEY= (Google Search \u2014 https://serper.dev)\n";
|
|
458
|
-
envContent += "# OPENWEATHER_API_KEY= (Weather \u2014 https://openweathermap.org/api)\n";
|
|
459
|
-
envContent += "# NEWS_API_KEY= (News \u2014 https://newsapi.org)\n\n";
|
|
460
|
-
}
|
|
461
496
|
} else {
|
|
462
497
|
envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# OPENAI_API_KEY=sk-...\n# CEREBRAS_API_KEY=csk-...\n\n";
|
|
463
498
|
}
|
|
@@ -468,173 +503,672 @@ export async function POST(c: Context) {
|
|
|
468
503
|
}
|
|
469
504
|
envContent += "# \u2500\u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPORT=3000\nNODE_ENV=development\n";
|
|
470
505
|
fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
|
|
471
|
-
|
|
472
|
-
fs.writeFileSync(path.join(targetDir, "public", "index.html"), generateFrontendHTML(name, template));
|
|
473
|
-
}
|
|
474
|
-
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n");
|
|
506
|
+
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n.env.local\n.env.*.local\nvite.config.voltx.ts\n");
|
|
475
507
|
printWelcomeBanner(name);
|
|
476
508
|
}
|
|
477
|
-
function
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
.
|
|
493
|
-
.
|
|
494
|
-
.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
509
|
+
function generateServerEntry(projectName, template) {
|
|
510
|
+
const imports = [];
|
|
511
|
+
const mounts = [];
|
|
512
|
+
imports.push('import { GET as healthGET } from "./api/index";');
|
|
513
|
+
mounts.push('app.get("/api", healthGET);');
|
|
514
|
+
if (template === "chatbot" || template === "agent-app") {
|
|
515
|
+
imports.push('import { POST as chatPOST } from "./api/chat";');
|
|
516
|
+
mounts.push('app.post("/api/chat", chatPOST);');
|
|
517
|
+
}
|
|
518
|
+
if (template === "agent-app") {
|
|
519
|
+
imports.push('import { POST as agentPOST } from "./api/agent";');
|
|
520
|
+
mounts.push('app.post("/api/agent", agentPOST);');
|
|
521
|
+
}
|
|
522
|
+
if (template === "rag-app") {
|
|
523
|
+
imports.push('import { POST as ragQueryPOST } from "./api/rag/query";');
|
|
524
|
+
imports.push('import { POST as ragIngestPOST } from "./api/rag/ingest";');
|
|
525
|
+
mounts.push('app.post("/api/rag/query", ragQueryPOST);');
|
|
526
|
+
mounts.push('app.post("/api/rag/ingest", ragIngestPOST);');
|
|
527
|
+
}
|
|
528
|
+
return `import { Hono } from "hono";
|
|
529
|
+
import { serve } from "@hono/node-server";
|
|
530
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
531
|
+
import { registerSSR } from "@voltx/server";
|
|
532
|
+
import { loadEnv } from "@voltx/core";
|
|
533
|
+
${imports.join("\n")}
|
|
534
|
+
|
|
535
|
+
loadEnv(process.env.NODE_ENV ?? "development");
|
|
536
|
+
|
|
537
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
538
|
+
const app = new Hono();
|
|
539
|
+
|
|
540
|
+
// \u2500\u2500 API Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
541
|
+
${mounts.join("\n")}
|
|
542
|
+
|
|
543
|
+
// \u2500\u2500 Static assets (production) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
544
|
+
if (isProd) {
|
|
545
|
+
app.use("/assets/*", serveStatic({ root: "./dist/client/" }));
|
|
546
|
+
app.use("/favicon.svg", serveStatic({ root: "./public/" }));
|
|
547
|
+
app.use("/robots.txt", serveStatic({ root: "./public/" }));
|
|
548
|
+
app.use("/site.webmanifest", serveStatic({ root: "./public/" }));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// \u2500\u2500 SSR catch-all \u2014 renders React on the server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
552
|
+
registerSSR(app, null, {
|
|
553
|
+
title: "${projectName}",
|
|
554
|
+
entryServer: "src/entry-server.tsx",
|
|
555
|
+
entryClient: "src/entry-client.tsx",
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
export default app;
|
|
559
|
+
|
|
560
|
+
if (isProd) {
|
|
561
|
+
const port = Number(process.env.PORT) || 3000;
|
|
562
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
563
|
+
console.log(\`\\n \u26A1 ${projectName} running at http://localhost:\${info.port}\\n\`);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
`;
|
|
567
|
+
}
|
|
568
|
+
function generateViteConfigFile(entry) {
|
|
569
|
+
return `import { defineConfig } from "vite";
|
|
570
|
+
import devServer from "@hono/vite-dev-server";
|
|
571
|
+
import react from "@vitejs/plugin-react";
|
|
572
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
573
|
+
|
|
574
|
+
export default defineConfig({
|
|
575
|
+
resolve: {
|
|
576
|
+
alias: {
|
|
577
|
+
"@": "/src",
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
plugins: [
|
|
581
|
+
react(),
|
|
582
|
+
tailwindcss(),
|
|
583
|
+
devServer({
|
|
584
|
+
entry: "${entry}",
|
|
585
|
+
exclude: [
|
|
586
|
+
/.*\\.tsx?($|\\?)/,
|
|
587
|
+
/.*\\.(s?css|less)($|\\?)/,
|
|
588
|
+
/.*\\.(svg|png|jpg|jpeg|gif|webp|ico)($|\\?)/,
|
|
589
|
+
/^\\/@.+$/,
|
|
590
|
+
/^\\/favicon\\.svg$/,
|
|
591
|
+
/^\\/node_modules\\/.*/,
|
|
592
|
+
/^\\/src\\/.*/,
|
|
593
|
+
],
|
|
594
|
+
injectClientScript: false,
|
|
595
|
+
}),
|
|
596
|
+
],
|
|
597
|
+
});
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
function generateEntryClient() {
|
|
601
|
+
return `import React from "react";
|
|
602
|
+
import { hydrateRoot } from "react-dom/client";
|
|
603
|
+
import Layout from "./layout";
|
|
604
|
+
import App from "./app";
|
|
605
|
+
import "./globals.css";
|
|
606
|
+
|
|
607
|
+
hydrateRoot(
|
|
608
|
+
document.getElementById("root")!,
|
|
609
|
+
<React.StrictMode>
|
|
610
|
+
<Layout>
|
|
611
|
+
<App />
|
|
612
|
+
</Layout>
|
|
613
|
+
</React.StrictMode>
|
|
614
|
+
);
|
|
615
|
+
`;
|
|
616
|
+
}
|
|
617
|
+
function generateEntryServer() {
|
|
618
|
+
return `import React from "react";
|
|
619
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
620
|
+
import Layout from "./layout";
|
|
621
|
+
import App from "./app";
|
|
622
|
+
|
|
623
|
+
export async function render(_url: string): Promise<ReadableStream> {
|
|
624
|
+
const stream = await renderToReadableStream(
|
|
625
|
+
<React.StrictMode>
|
|
626
|
+
<Layout>
|
|
627
|
+
<App />
|
|
628
|
+
</Layout>
|
|
629
|
+
</React.StrictMode>,
|
|
630
|
+
{
|
|
631
|
+
onError(error: unknown) {
|
|
632
|
+
console.error("[voltx] SSR render error:", error);
|
|
633
|
+
},
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
return stream;
|
|
637
|
+
}
|
|
638
|
+
`;
|
|
639
|
+
}
|
|
640
|
+
function generateLayoutComponent(projectName) {
|
|
641
|
+
return `import React from "react";
|
|
642
|
+
|
|
643
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
644
|
+
return (
|
|
645
|
+
<div className="min-h-screen bg-background text-foreground font-sans">
|
|
646
|
+
<header className="border-b border-border px-6 py-3 flex items-center justify-between">
|
|
647
|
+
<div className="flex items-center gap-2">
|
|
648
|
+
<span className="text-xl">\u26A1</span>
|
|
649
|
+
<span className="font-semibold">${projectName}</span>
|
|
534
650
|
</div>
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
651
|
+
<a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-muted text-sm hover:text-foreground transition-colors">
|
|
652
|
+
Built with VoltX
|
|
653
|
+
</a>
|
|
654
|
+
</header>
|
|
655
|
+
<main>{children}</main>
|
|
656
|
+
</div>
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
`;
|
|
660
|
+
}
|
|
661
|
+
function generateGlobalCSS(useShadcn = false) {
|
|
662
|
+
if (useShadcn) {
|
|
663
|
+
return `@import "tailwindcss";
|
|
664
|
+
|
|
665
|
+
@theme inline {
|
|
666
|
+
--radius-sm: 0.25rem;
|
|
667
|
+
--radius-md: 0.375rem;
|
|
668
|
+
--radius-lg: 0.5rem;
|
|
669
|
+
--radius-xl: 0.75rem;
|
|
670
|
+
--color-background: hsl(var(--background));
|
|
671
|
+
--color-foreground: hsl(var(--foreground));
|
|
672
|
+
--color-card: hsl(var(--card));
|
|
673
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
674
|
+
--color-popover: hsl(var(--popover));
|
|
675
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
676
|
+
--color-primary: hsl(var(--primary));
|
|
677
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
678
|
+
--color-secondary: hsl(var(--secondary));
|
|
679
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
680
|
+
--color-muted: hsl(var(--muted));
|
|
681
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
682
|
+
--color-accent: hsl(var(--accent));
|
|
683
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
684
|
+
--color-destructive: hsl(var(--destructive));
|
|
685
|
+
--color-border: hsl(var(--border));
|
|
686
|
+
--color-input: hsl(var(--input));
|
|
687
|
+
--color-ring: hsl(var(--ring));
|
|
688
|
+
--color-chart-1: hsl(var(--chart-1));
|
|
689
|
+
--color-chart-2: hsl(var(--chart-2));
|
|
690
|
+
--color-chart-3: hsl(var(--chart-3));
|
|
691
|
+
--color-chart-4: hsl(var(--chart-4));
|
|
692
|
+
--color-chart-5: hsl(var(--chart-5));
|
|
693
|
+
--color-sidebar: hsl(var(--sidebar));
|
|
694
|
+
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
|
|
695
|
+
--color-sidebar-primary: hsl(var(--sidebar-primary));
|
|
696
|
+
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
|
|
697
|
+
--color-sidebar-accent: hsl(var(--sidebar-accent));
|
|
698
|
+
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
|
|
699
|
+
--color-sidebar-border: hsl(var(--sidebar-border));
|
|
700
|
+
--color-sidebar-ring: hsl(var(--sidebar-ring));
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
:root {
|
|
704
|
+
--background: 0 0% 4%;
|
|
705
|
+
--foreground: 0 0% 93%;
|
|
706
|
+
--card: 0 0% 6%;
|
|
707
|
+
--card-foreground: 0 0% 93%;
|
|
708
|
+
--popover: 0 0% 6%;
|
|
709
|
+
--popover-foreground: 0 0% 93%;
|
|
710
|
+
--primary: 0 0% 93%;
|
|
711
|
+
--primary-foreground: 0 0% 6%;
|
|
712
|
+
--secondary: 0 0% 12%;
|
|
713
|
+
--secondary-foreground: 0 0% 93%;
|
|
714
|
+
--muted: 0 0% 12%;
|
|
715
|
+
--muted-foreground: 0 0% 55%;
|
|
716
|
+
--accent: 0 0% 12%;
|
|
717
|
+
--accent-foreground: 0 0% 93%;
|
|
718
|
+
--destructive: 0 62% 50%;
|
|
719
|
+
--border: 0 0% 14%;
|
|
720
|
+
--input: 0 0% 14%;
|
|
721
|
+
--ring: 0 0% 83%;
|
|
722
|
+
--chart-1: 220 70% 50%;
|
|
723
|
+
--chart-2: 160 60% 45%;
|
|
724
|
+
--chart-3: 30 80% 55%;
|
|
725
|
+
--chart-4: 280 65% 60%;
|
|
726
|
+
--chart-5: 340 75% 55%;
|
|
727
|
+
--sidebar: 0 0% 5%;
|
|
728
|
+
--sidebar-foreground: 0 0% 93%;
|
|
729
|
+
--sidebar-primary: 0 0% 93%;
|
|
730
|
+
--sidebar-primary-foreground: 0 0% 6%;
|
|
731
|
+
--sidebar-accent: 0 0% 12%;
|
|
732
|
+
--sidebar-accent-foreground: 0 0% 93%;
|
|
733
|
+
--sidebar-border: 0 0% 14%;
|
|
734
|
+
--sidebar-ring: 0 0% 83%;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
*,
|
|
738
|
+
*::before,
|
|
739
|
+
*::after {
|
|
740
|
+
box-sizing: border-box;
|
|
741
|
+
margin: 0;
|
|
742
|
+
padding: 0;
|
|
743
|
+
border-color: var(--color-border);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
html,
|
|
747
|
+
body {
|
|
748
|
+
height: 100%;
|
|
749
|
+
background: var(--color-background);
|
|
750
|
+
color: var(--color-foreground);
|
|
751
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
752
|
+
-webkit-font-smoothing: antialiased;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
#root {
|
|
756
|
+
height: 100%;
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
return `@import "tailwindcss";
|
|
761
|
+
|
|
762
|
+
@theme {
|
|
763
|
+
--color-background: #0a0a0a;
|
|
764
|
+
--color-foreground: #ededed;
|
|
765
|
+
--color-muted: #888888;
|
|
766
|
+
--color-border: #222222;
|
|
767
|
+
--color-primary: #2563eb;
|
|
768
|
+
--color-accent: #a78bfa;
|
|
769
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
*,
|
|
773
|
+
*::before,
|
|
774
|
+
*::after {
|
|
775
|
+
box-sizing: border-box;
|
|
776
|
+
margin: 0;
|
|
777
|
+
padding: 0;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
html,
|
|
781
|
+
body {
|
|
782
|
+
height: 100%;
|
|
783
|
+
background: var(--color-background);
|
|
784
|
+
color: var(--color-foreground);
|
|
785
|
+
font-family: var(--font-sans);
|
|
786
|
+
-webkit-font-smoothing: antialiased;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
#root {
|
|
790
|
+
height: 100%;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
a {
|
|
794
|
+
color: inherit;
|
|
795
|
+
text-decoration: none;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
a:hover {
|
|
799
|
+
text-decoration: underline;
|
|
800
|
+
}
|
|
801
|
+
`;
|
|
802
|
+
}
|
|
803
|
+
function generateCnUtil() {
|
|
804
|
+
return `import { type ClassValue, clsx } from "clsx";
|
|
805
|
+
import { twMerge } from "tailwind-merge";
|
|
806
|
+
|
|
807
|
+
export function cn(...inputs: ClassValue[]) {
|
|
808
|
+
return twMerge(clsx(inputs));
|
|
809
|
+
}
|
|
810
|
+
`;
|
|
811
|
+
}
|
|
812
|
+
function generateComponentsJson() {
|
|
813
|
+
return JSON.stringify({
|
|
814
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
815
|
+
style: "new-york",
|
|
816
|
+
rsc: false,
|
|
817
|
+
tsx: true,
|
|
818
|
+
tailwind: {
|
|
819
|
+
config: "",
|
|
820
|
+
css: "src/globals.css",
|
|
821
|
+
baseColor: "neutral",
|
|
822
|
+
cssVariables: true
|
|
823
|
+
},
|
|
824
|
+
aliases: {
|
|
825
|
+
components: "@/components",
|
|
826
|
+
utils: "@/lib/utils",
|
|
827
|
+
ui: "@/components/ui",
|
|
828
|
+
lib: "@/lib",
|
|
829
|
+
hooks: "@/hooks"
|
|
830
|
+
}
|
|
831
|
+
}, null, 2) + "\n";
|
|
832
|
+
}
|
|
833
|
+
function generateAppComponent(projectName, template) {
|
|
834
|
+
if (template === "blank") {
|
|
835
|
+
return `import React, { useState, useEffect } from "react";
|
|
836
|
+
|
|
837
|
+
export default function App() {
|
|
838
|
+
const [status, setStatus] = useState<string>("checking...");
|
|
839
|
+
|
|
840
|
+
useEffect(() => {
|
|
841
|
+
fetch("/api")
|
|
842
|
+
.then((res) => res.json())
|
|
843
|
+
.then((data) => setStatus(data.status || "ok"))
|
|
844
|
+
.catch(() => setStatus("error"));
|
|
845
|
+
}, []);
|
|
846
|
+
|
|
847
|
+
return (
|
|
848
|
+
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)] px-6 py-12">
|
|
849
|
+
<div className="text-center max-w-2xl w-full">
|
|
850
|
+
{/* Hero */}
|
|
851
|
+
<div className="relative mb-8">
|
|
852
|
+
<div className="absolute inset-0 blur-3xl opacity-20 bg-gradient-to-r from-blue-500 via-purple-500 to-blue-500 rounded-full" />
|
|
853
|
+
<div className="relative text-7xl mb-4">\u26A1</div>
|
|
540
854
|
</div>
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
<textarea id="ingestText" rows="6" placeholder="Paste text to ingest..." class="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-emerald-500 resize-none"></textarea>
|
|
555
|
-
<button id="ingestBtn" onclick="ingestDoc()" class="w-full py-2.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-lg transition-colors">Ingest</button>
|
|
556
|
-
<div id="ingestStatus" class="text-xs text-gray-500 hidden"></div>
|
|
557
|
-
<div class="mt-auto pt-3 border-t border-gray-800"><p class="text-xs text-gray-600">Documents are chunked, embedded, and stored for retrieval.</p></div>
|
|
855
|
+
<h1 className="text-5xl font-bold tracking-tight mb-3 bg-gradient-to-r from-white to-white/60 bg-clip-text text-transparent">
|
|
856
|
+
\${"\${projectName}"}
|
|
857
|
+
</h1>
|
|
858
|
+
<p className="text-muted text-lg mb-10">The AI-first full-stack framework</p>
|
|
859
|
+
|
|
860
|
+
{/* Status cards */}
|
|
861
|
+
<div className="flex gap-4 justify-center mb-10">
|
|
862
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
863
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">Server</div>
|
|
864
|
+
<div className={\`text-sm font-medium \${status === "ok" ? "text-emerald-400" : "text-red-400"}\`}>
|
|
865
|
+
<span className={\`inline-block w-2 h-2 rounded-full mr-2 \${status === "ok" ? "bg-emerald-400 animate-pulse" : "bg-red-400"}\`} />
|
|
866
|
+
{status}
|
|
867
|
+
</div>
|
|
558
868
|
</div>
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
<h2 class="text-xl font-semibold text-white mb-2">Ask your documents</h2>
|
|
565
|
-
<p class="text-gray-500 text-sm">Ingest documents on the left, then ask questions here.</p>
|
|
869
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
870
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">Frontend</div>
|
|
871
|
+
<div className="text-sm font-medium text-emerald-400">
|
|
872
|
+
<span className="inline-block w-2 h-2 rounded-full mr-2 bg-emerald-400 animate-pulse" />
|
|
873
|
+
React + Vite
|
|
566
874
|
</div>
|
|
567
875
|
</div>
|
|
568
|
-
<div
|
|
569
|
-
<
|
|
570
|
-
|
|
571
|
-
<button type="submit" id="ragSendBtn" class="px-5 py-3 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-xl transition-colors">Ask</button>
|
|
572
|
-
</form>
|
|
876
|
+
<div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
|
|
877
|
+
<div className="text-xs text-muted mb-1 uppercase tracking-wider">CSS</div>
|
|
878
|
+
<div className="text-sm font-medium text-sky-400">Tailwind v4</div>
|
|
573
879
|
</div>
|
|
574
880
|
</div>
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
881
|
+
|
|
882
|
+
{/* Get started */}
|
|
883
|
+
<div className="bg-white/[0.03] border border-border rounded-2xl p-8 text-left backdrop-blur-sm">
|
|
884
|
+
<h2 className="text-sm font-medium text-muted mb-6 uppercase tracking-wider">Get started</h2>
|
|
885
|
+
<div className="space-y-4">
|
|
886
|
+
<div className="flex items-start gap-4">
|
|
887
|
+
<div className="w-8 h-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400 text-sm shrink-0">1</div>
|
|
888
|
+
<div>
|
|
889
|
+
<code className="text-purple-400 text-sm">src/app.tsx</code>
|
|
890
|
+
<p className="text-muted text-sm mt-1">Edit this file to build your UI</p>
|
|
891
|
+
</div>
|
|
892
|
+
</div>
|
|
893
|
+
<div className="flex items-start gap-4">
|
|
894
|
+
<div className="w-8 h-8 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400 text-sm shrink-0">2</div>
|
|
895
|
+
<div>
|
|
896
|
+
<code className="text-blue-400 text-sm">api/</code>
|
|
897
|
+
<p className="text-muted text-sm mt-1">Add API routes here (file-based routing)</p>
|
|
898
|
+
</div>
|
|
899
|
+
</div>
|
|
900
|
+
<div className="flex items-start gap-4">
|
|
901
|
+
<div className="w-8 h-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 text-sm shrink-0">3</div>
|
|
902
|
+
<div>
|
|
903
|
+
<code className="text-emerald-400 text-sm">src/components/</code>
|
|
904
|
+
<p className="text-muted text-sm mt-1">Create React components with Tailwind CSS</p>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
591
907
|
</div>
|
|
592
908
|
</div>
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
909
|
+
|
|
910
|
+
{/* Links */}
|
|
911
|
+
<div className="flex gap-6 justify-center mt-8">
|
|
912
|
+
<a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
|
|
913
|
+
GitHub \u2192
|
|
914
|
+
</a>
|
|
915
|
+
<a href="https://voltx.co.in" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
|
|
916
|
+
Docs \u2192
|
|
917
|
+
</a>
|
|
598
918
|
</div>
|
|
599
919
|
</div>
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
920
|
+
</div>
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
`;
|
|
924
|
+
}
|
|
925
|
+
const apiEndpoint = template === "agent-app" ? "/api/agent" : template === "rag-app" ? "/api/rag/query" : "/api/chat";
|
|
926
|
+
const isAgent = template === "agent-app";
|
|
927
|
+
const isRag = template === "rag-app";
|
|
928
|
+
let emptyStateTitle = "Start a conversation";
|
|
929
|
+
let emptyStateHint = "Type a message below to chat with AI";
|
|
930
|
+
let accentColor = "blue";
|
|
931
|
+
let inputPlaceholder = "Type a message...";
|
|
932
|
+
if (isAgent) {
|
|
933
|
+
emptyStateTitle = "Talk to your AI agent";
|
|
934
|
+
emptyStateHint = "The agent can use tools like Calculator and Date/Time";
|
|
935
|
+
accentColor = "purple";
|
|
936
|
+
inputPlaceholder = "Ask the agent anything...";
|
|
937
|
+
} else if (isRag) {
|
|
938
|
+
emptyStateTitle = "Ask your documents";
|
|
939
|
+
emptyStateHint = "Query your knowledge base \u2014 ingest docs via POST /api/rag/ingest";
|
|
940
|
+
accentColor = "emerald";
|
|
941
|
+
inputPlaceholder = "Ask a question about your documents...";
|
|
942
|
+
}
|
|
943
|
+
return `import React, { useState, useRef, useEffect, useCallback } from "react";
|
|
944
|
+
|
|
945
|
+
interface Message {
|
|
946
|
+
id: string;
|
|
947
|
+
role: "user" | "assistant";
|
|
948
|
+
content: string;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export default function App() {
|
|
952
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
953
|
+
const [input, setInput] = useState("");
|
|
954
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
955
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
956
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
957
|
+
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
960
|
+
}, [messages]);
|
|
961
|
+
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
inputRef.current?.focus();
|
|
964
|
+
}, []);
|
|
965
|
+
|
|
966
|
+
const sendMessage = useCallback(async () => {
|
|
967
|
+
const text = input.trim();
|
|
968
|
+
if (!text || isLoading) return;
|
|
969
|
+
|
|
970
|
+
const userMsg: Message = { id: crypto.randomUUID(), role: "user", content: text };
|
|
971
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
972
|
+
setInput("");
|
|
973
|
+
setIsLoading(true);
|
|
974
|
+
|
|
975
|
+
const assistantMsg: Message = { id: crypto.randomUUID(), role: "assistant", content: "" };
|
|
976
|
+
setMessages((prev) => [...prev, assistantMsg]);
|
|
977
|
+
|
|
978
|
+
try {${isAgent ? `
|
|
979
|
+
const res = await fetch("${apiEndpoint}", {
|
|
980
|
+
method: "POST",
|
|
981
|
+
headers: { "Content-Type": "application/json" },
|
|
982
|
+
body: JSON.stringify({ input: text }),
|
|
983
|
+
});
|
|
984
|
+
const data = await res.json();
|
|
985
|
+
setMessages((prev) =>
|
|
986
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: data.content || "No response" } : m)
|
|
987
|
+
);` : `
|
|
988
|
+
const res = await fetch("${apiEndpoint}", {
|
|
989
|
+
method: "POST",
|
|
990
|
+
headers: { "Content-Type": "application/json" },
|
|
991
|
+
body: JSON.stringify({${isRag ? ` question: text ` : ` messages: [...messages, { role: "user", content: text }] `}}),
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
if (!res.body) throw new Error("No response body");
|
|
995
|
+
|
|
996
|
+
const reader = res.body.getReader();
|
|
997
|
+
const decoder = new TextDecoder();
|
|
998
|
+
let buffer = "";
|
|
999
|
+
let fullContent = "";
|
|
1000
|
+
|
|
1001
|
+
while (true) {
|
|
1002
|
+
const { done, value } = await reader.read();
|
|
1003
|
+
if (done) break;
|
|
1004
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1005
|
+
const lines = buffer.split("\\n");
|
|
1006
|
+
buffer = lines.pop() ?? "";
|
|
1007
|
+
|
|
1008
|
+
for (const line of lines) {
|
|
1009
|
+
if (!line.startsWith("data: ")) continue;
|
|
1010
|
+
const data = line.slice(6);
|
|
1011
|
+
if (data === "[DONE]") break;
|
|
1012
|
+
try {
|
|
1013
|
+
const parsed = JSON.parse(data);
|
|
1014
|
+
const chunk = parsed.textDelta ?? parsed.content ?? parsed.choices?.[0]?.delta?.content ?? "";
|
|
1015
|
+
if (chunk) {
|
|
1016
|
+
fullContent += chunk;
|
|
1017
|
+
setMessages((prev) =>
|
|
1018
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: fullContent } : m)
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
} catch {}
|
|
1022
|
+
}
|
|
1023
|
+
}`}
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
setMessages((prev) =>
|
|
1026
|
+
prev.map((m) => m.id === assistantMsg.id ? { ...m, content: "Error: " + (err instanceof Error ? err.message : String(err)) } : m)
|
|
1027
|
+
);
|
|
1028
|
+
} finally {
|
|
1029
|
+
setIsLoading(false);
|
|
1030
|
+
}
|
|
1031
|
+
}, [input, isLoading, messages]);
|
|
1032
|
+
|
|
1033
|
+
return (
|
|
1034
|
+
<div className="h-[calc(100vh-60px)] flex flex-col">
|
|
1035
|
+
<main className="flex-1 overflow-y-auto px-4 py-6">
|
|
1036
|
+
<div className="max-w-3xl mx-auto">
|
|
1037
|
+
{messages.length === 0 && (
|
|
1038
|
+
<div className="flex flex-col items-center justify-center h-[60vh] text-center">
|
|
1039
|
+
<div className="relative mb-6">
|
|
1040
|
+
<div className="absolute inset-0 blur-2xl opacity-20 bg-${accentColor}-500 rounded-full" />
|
|
1041
|
+
<div className="relative text-5xl">\u26A1</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
<h2 className="text-2xl font-semibold mb-2">${emptyStateTitle}</h2>
|
|
1044
|
+
<p className="text-muted text-sm max-w-md">${emptyStateHint}</p>
|
|
1045
|
+
<div className="flex gap-2 mt-6">
|
|
1046
|
+
${isAgent ? `<button onClick={() => { setInput("What is 42 * 17?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1047
|
+
What is 42 \xD7 17?
|
|
1048
|
+
</button>
|
|
1049
|
+
<button onClick={() => { setInput("What day is it today?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1050
|
+
What day is it?
|
|
1051
|
+
</button>` : isRag ? `<button onClick={() => { setInput("Summarize the main topics"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1052
|
+
Summarize main topics
|
|
1053
|
+
</button>
|
|
1054
|
+
<button onClick={() => { setInput("What are the key findings?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1055
|
+
Key findings
|
|
1056
|
+
</button>` : `<button onClick={() => { setInput("Hello! What can you do?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1057
|
+
Hello! What can you do?
|
|
1058
|
+
</button>
|
|
1059
|
+
<button onClick={() => { setInput("Tell me a fun fact"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
|
|
1060
|
+
Tell me a fun fact
|
|
1061
|
+
</button>`}
|
|
1062
|
+
</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
)}
|
|
1065
|
+
{messages.map((msg) => (
|
|
1066
|
+
<div key={msg.id} className={\`mb-6 flex \${msg.role === "user" ? "justify-end" : "justify-start"}\`}>
|
|
1067
|
+
<div className={\`flex items-start gap-3 max-w-[80%] \${msg.role === "user" ? "flex-row-reverse" : ""}\`}>
|
|
1068
|
+
<div className={\`w-8 h-8 rounded-full flex items-center justify-center text-sm shrink-0 \${
|
|
1069
|
+
msg.role === "user" ? "bg-${accentColor}-500 text-white" : "bg-white/10 text-muted"
|
|
1070
|
+
}\`}>
|
|
1071
|
+
{msg.role === "user" ? "Y" : "\u26A1"}
|
|
1072
|
+
</div>
|
|
1073
|
+
<div className={\`px-4 py-3 rounded-2xl whitespace-pre-wrap leading-relaxed text-sm \${
|
|
1074
|
+
msg.role === "user"
|
|
1075
|
+
? "bg-${accentColor}-500 text-white rounded-br-md"
|
|
1076
|
+
: "bg-white/[0.05] border border-border rounded-bl-md"
|
|
1077
|
+
}\`}>
|
|
1078
|
+
{msg.content || (isLoading && msg.role === "assistant" ? (
|
|
1079
|
+
<span className="flex gap-1">
|
|
1080
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:0ms]" />
|
|
1081
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:150ms]" />
|
|
1082
|
+
<span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:300ms]" />
|
|
1083
|
+
</span>
|
|
1084
|
+
) : "")}
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
))}
|
|
1089
|
+
<div ref={messagesEndRef} />
|
|
1090
|
+
</div>
|
|
1091
|
+
</main>
|
|
1092
|
+
<footer className="border-t border-border px-4 py-4">
|
|
1093
|
+
<form onSubmit={(e) => { e.preventDefault(); sendMessage(); }} className="max-w-3xl mx-auto flex gap-3">
|
|
1094
|
+
<input
|
|
1095
|
+
ref={inputRef}
|
|
1096
|
+
value={input}
|
|
1097
|
+
onChange={(e) => setInput(e.target.value)}
|
|
1098
|
+
placeholder="${inputPlaceholder}"
|
|
1099
|
+
disabled={isLoading}
|
|
1100
|
+
className="flex-1 px-4 py-3 rounded-xl bg-white/[0.05] border border-border text-foreground placeholder:text-muted/50 outline-none focus:border-${accentColor}-500/50 focus:ring-1 focus:ring-${accentColor}-500/25 transition-all disabled:opacity-50"
|
|
1101
|
+
/>
|
|
1102
|
+
<button
|
|
1103
|
+
type="submit"
|
|
1104
|
+
disabled={isLoading || !input.trim()}
|
|
1105
|
+
className="px-6 py-3 rounded-xl font-medium text-sm transition-all disabled:opacity-30 disabled:cursor-not-allowed bg-${accentColor}-500 hover:bg-${accentColor}-400 text-white cursor-pointer"
|
|
1106
|
+
>
|
|
1107
|
+
{isLoading ? (
|
|
1108
|
+
<svg className="w-5 h-5 animate-spin" viewBox="0 0 24 24" fill="none">
|
|
1109
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
1110
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
1111
|
+
</svg>
|
|
1112
|
+
) : "Send"}
|
|
1113
|
+
</button>
|
|
1114
|
+
</form>
|
|
1115
|
+
</footer>
|
|
1116
|
+
</div>
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
`;
|
|
1120
|
+
}
|
|
1121
|
+
function generateFaviconSVG() {
|
|
1122
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1123
|
+
<rect width="32" height="32" rx="6" fill="#0a0a0a"/>
|
|
1124
|
+
<path d="M18.5 4L8 18h7l-1.5 10L24 14h-7l1.5-10z" fill="#facc15" stroke="#facc15" stroke-width="0.5" stroke-linejoin="round"/>
|
|
1125
|
+
</svg>
|
|
1126
|
+
`;
|
|
1127
|
+
}
|
|
1128
|
+
function generateRobotsTxt() {
|
|
1129
|
+
return `# https://www.robotstxt.org/robotstxt.html
|
|
1130
|
+
User-agent: *
|
|
1131
|
+
Allow: /
|
|
1132
|
+
|
|
1133
|
+
Sitemap: /sitemap.xml
|
|
1134
|
+
`;
|
|
1135
|
+
}
|
|
1136
|
+
function generateWebManifest(projectName) {
|
|
1137
|
+
return JSON.stringify({
|
|
1138
|
+
name: projectName,
|
|
1139
|
+
short_name: projectName,
|
|
1140
|
+
icons: [
|
|
1141
|
+
{ src: "/favicon.svg", sizes: "any", type: "image/svg+xml" }
|
|
1142
|
+
],
|
|
1143
|
+
theme_color: "#0a0a0a",
|
|
1144
|
+
background_color: "#0a0a0a",
|
|
1145
|
+
display: "standalone"
|
|
1146
|
+
}, null, 2) + "\n";
|
|
1147
|
+
}
|
|
1148
|
+
var fs, path, VV, TEMPLATE_DEPS;
|
|
610
1149
|
var init_create = __esm({
|
|
611
1150
|
"src/create.ts"() {
|
|
612
1151
|
"use strict";
|
|
613
1152
|
fs = __toESM(require("fs"));
|
|
614
1153
|
path = __toESM(require("path"));
|
|
615
1154
|
init_welcome();
|
|
616
|
-
|
|
1155
|
+
VV = {
|
|
1156
|
+
"@voltx/core": "^0.3.1",
|
|
1157
|
+
"@voltx/server": "^0.3.1",
|
|
1158
|
+
"@voltx/cli": "^0.3.5",
|
|
1159
|
+
"@voltx/ai": "^0.3.0",
|
|
1160
|
+
"@voltx/agents": "^0.3.1",
|
|
1161
|
+
"@voltx/memory": "^0.3.0",
|
|
1162
|
+
"@voltx/db": "^0.3.0",
|
|
1163
|
+
"@voltx/rag": "^0.3.1",
|
|
1164
|
+
"@voltx/auth": "^0.3.0"
|
|
1165
|
+
};
|
|
617
1166
|
TEMPLATE_DEPS = {
|
|
618
|
-
blank: { "@voltx/core":
|
|
619
|
-
chatbot: { "@voltx/core":
|
|
620
|
-
"rag-app": { "@voltx/core":
|
|
621
|
-
"agent-app": { "@voltx/core":
|
|
1167
|
+
blank: { "@voltx/core": v("@voltx/core"), "@voltx/server": v("@voltx/server") },
|
|
1168
|
+
chatbot: { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/memory": v("@voltx/memory") },
|
|
1169
|
+
"rag-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/rag": v("@voltx/rag"), "@voltx/db": v("@voltx/db") },
|
|
1170
|
+
"agent-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/agents": v("@voltx/agents"), "@voltx/memory": v("@voltx/memory") }
|
|
622
1171
|
};
|
|
623
|
-
if (typeof require !== "undefined" && require.main === module && process.argv[1]?.includes("create")) {
|
|
624
|
-
const projectName = process.argv[2];
|
|
625
|
-
if (!projectName) {
|
|
626
|
-
console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
|
|
627
|
-
process.exit(1);
|
|
628
|
-
}
|
|
629
|
-
const templateFlag = process.argv.indexOf("--template");
|
|
630
|
-
const template = templateFlag !== -1 ? process.argv[templateFlag + 1] : "blank";
|
|
631
|
-
const authFlag = process.argv.indexOf("--auth");
|
|
632
|
-
const auth = authFlag !== -1 ? process.argv[authFlag + 1] : "none";
|
|
633
|
-
createProject({ name: projectName, template, auth }).catch((err) => {
|
|
634
|
-
console.error("[voltx] Error:", err);
|
|
635
|
-
process.exit(1);
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
1172
|
}
|
|
639
1173
|
});
|
|
640
1174
|
|
|
@@ -651,7 +1185,7 @@ async function runDev(options = {}) {
|
|
|
651
1185
|
clearScreen = true
|
|
652
1186
|
} = options;
|
|
653
1187
|
if (!entry) {
|
|
654
|
-
console.error("[voltx] Could not find entry point. Expected
|
|
1188
|
+
console.error("[voltx] Could not find entry point. Expected server.ts or src/index.ts");
|
|
655
1189
|
process.exit(1);
|
|
656
1190
|
}
|
|
657
1191
|
const entryPath = (0, import_node_path.resolve)(cwd, entry);
|
|
@@ -659,56 +1193,124 @@ async function runDev(options = {}) {
|
|
|
659
1193
|
console.error(`[voltx] Entry file not found: ${entry}`);
|
|
660
1194
|
process.exit(1);
|
|
661
1195
|
}
|
|
662
|
-
|
|
1196
|
+
(0, import_core.loadEnv)("development", cwd);
|
|
1197
|
+
const hasViteConfig = (0, import_node_fs.existsSync)((0, import_node_path.resolve)(cwd, "vite.config.ts"));
|
|
1198
|
+
const hasServerEntry = (0, import_node_fs.existsSync)((0, import_node_path.resolve)(cwd, "server.ts"));
|
|
1199
|
+
const isFullStack = hasViteConfig || hasServerEntry;
|
|
1200
|
+
if (isFullStack) {
|
|
1201
|
+
await startViteDevServer(cwd, port, entry, options);
|
|
1202
|
+
} else {
|
|
1203
|
+
await startTsxWatch(cwd, port, entry, options);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
async function startViteDevServer(cwd, port, entry, options) {
|
|
1207
|
+
const resolvedPort = port ?? (Number(process.env.PORT) || 3e3);
|
|
1208
|
+
printDevBanner(entry, resolvedPort, true);
|
|
1209
|
+
const userViteConfig = (0, import_node_path.resolve)(cwd, "vite.config.ts");
|
|
1210
|
+
const hasUserViteConfig = (0, import_node_fs.existsSync)(userViteConfig);
|
|
1211
|
+
let tempConfigPath = null;
|
|
1212
|
+
if (!hasUserViteConfig) {
|
|
1213
|
+
tempConfigPath = (0, import_node_path.resolve)(cwd, "vite.config.voltx.ts");
|
|
1214
|
+
const viteConfigContent = generateViteConfig(entry, resolvedPort);
|
|
1215
|
+
(0, import_node_fs.writeFileSync)(tempConfigPath, viteConfigContent, "utf-8");
|
|
1216
|
+
}
|
|
663
1217
|
const env = {
|
|
664
1218
|
...process.env,
|
|
665
1219
|
NODE_ENV: "development"
|
|
666
1220
|
};
|
|
667
|
-
if (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1221
|
+
if (port) {
|
|
1222
|
+
env.PORT = String(port);
|
|
1223
|
+
}
|
|
1224
|
+
const viteBin = findBin(cwd, "vite");
|
|
1225
|
+
const viteArgs = ["dev"];
|
|
1226
|
+
if (tempConfigPath) {
|
|
1227
|
+
viteArgs.push("--config", tempConfigPath);
|
|
1228
|
+
}
|
|
1229
|
+
viteArgs.push("--port", String(resolvedPort));
|
|
1230
|
+
let child;
|
|
1231
|
+
if (viteBin) {
|
|
1232
|
+
child = (0, import_node_child_process.spawn)(viteBin, viteArgs, { cwd, env, stdio: "inherit" });
|
|
1233
|
+
} else {
|
|
1234
|
+
child = (0, import_node_child_process.spawn)("npx", ["vite", ...viteArgs], { cwd, env, stdio: "inherit" });
|
|
1235
|
+
}
|
|
1236
|
+
const cleanup = () => {
|
|
1237
|
+
if (tempConfigPath && (0, import_node_fs.existsSync)(tempConfigPath)) {
|
|
1238
|
+
try {
|
|
1239
|
+
(0, import_node_fs.unlinkSync)(tempConfigPath);
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
677
1242
|
}
|
|
1243
|
+
};
|
|
1244
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
1245
|
+
for (const signal of signals) {
|
|
1246
|
+
process.on(signal, () => {
|
|
1247
|
+
cleanup();
|
|
1248
|
+
child.kill(signal);
|
|
1249
|
+
});
|
|
678
1250
|
}
|
|
1251
|
+
child.on("error", (err) => {
|
|
1252
|
+
cleanup();
|
|
1253
|
+
if (err.code === "ENOENT") {
|
|
1254
|
+
console.error("[voltx] vite not found. Install it with: npm install -D vite @hono/vite-dev-server");
|
|
1255
|
+
} else {
|
|
1256
|
+
console.error("[voltx] Dev server error:", err.message);
|
|
1257
|
+
}
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
});
|
|
1260
|
+
child.on("exit", (code) => {
|
|
1261
|
+
cleanup();
|
|
1262
|
+
process.exit(code ?? 0);
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
function generateViteConfig(entry, port) {
|
|
1266
|
+
return `// Auto-generated by VoltX \u2014 do not commit this file
|
|
1267
|
+
import { defineConfig } from "vite";
|
|
1268
|
+
import devServer from "@hono/vite-dev-server";
|
|
1269
|
+
|
|
1270
|
+
export default defineConfig({
|
|
1271
|
+
server: {
|
|
1272
|
+
port: ${port},
|
|
1273
|
+
},
|
|
1274
|
+
plugins: [
|
|
1275
|
+
devServer({
|
|
1276
|
+
entry: "${entry}",
|
|
1277
|
+
exclude: [
|
|
1278
|
+
/.*\\.tsx?($|\\?)/,
|
|
1279
|
+
/.*\\.(s?css|less)($|\\?)/,
|
|
1280
|
+
/.*\\.(svg|png|jpg|jpeg|gif|webp|ico)($|\\?)/,
|
|
1281
|
+
/^\\/@.+$/,
|
|
1282
|
+
/^\\/favicon\\.svg$/,
|
|
1283
|
+
/^\\/node_modules\\/.*/,
|
|
1284
|
+
/^\\/src\\/.*/,
|
|
1285
|
+
],
|
|
1286
|
+
injectClientScript: false,
|
|
1287
|
+
}),
|
|
1288
|
+
],
|
|
1289
|
+
});
|
|
1290
|
+
`;
|
|
1291
|
+
}
|
|
1292
|
+
async function startTsxWatch(cwd, port, entry, options) {
|
|
1293
|
+
const resolvedPort = port ?? (Number(process.env.PORT) || 3e3);
|
|
1294
|
+
printDevBanner(entry, resolvedPort, false);
|
|
1295
|
+
const env = {
|
|
1296
|
+
...process.env,
|
|
1297
|
+
NODE_ENV: "development"
|
|
1298
|
+
};
|
|
679
1299
|
if (port) {
|
|
680
1300
|
env.PORT = String(port);
|
|
681
1301
|
}
|
|
682
|
-
printDevBanner(entry, port);
|
|
683
1302
|
const tsxArgs = ["watch"];
|
|
684
|
-
if (clearScreen) {
|
|
1303
|
+
if (options.clearScreen ?? true) {
|
|
685
1304
|
tsxArgs.push("--clear-screen=false");
|
|
686
1305
|
}
|
|
687
|
-
const watchDirs = [
|
|
688
|
-
"src/routes",
|
|
689
|
-
"src/agents",
|
|
690
|
-
"src/tools",
|
|
691
|
-
"src/jobs",
|
|
692
|
-
"src/lib",
|
|
693
|
-
"voltx.config.ts",
|
|
694
|
-
...options.watch ?? []
|
|
695
|
-
];
|
|
696
1306
|
tsxArgs.push("--ignore=node_modules", "--ignore=dist", "--ignore=.turbo");
|
|
697
1307
|
tsxArgs.push(entry);
|
|
698
|
-
const tsxBin =
|
|
1308
|
+
const tsxBin = findBin(cwd, "tsx");
|
|
699
1309
|
let child;
|
|
700
1310
|
if (tsxBin) {
|
|
701
|
-
child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, {
|
|
702
|
-
cwd,
|
|
703
|
-
env,
|
|
704
|
-
stdio: "inherit"
|
|
705
|
-
});
|
|
1311
|
+
child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, { cwd, env, stdio: "inherit" });
|
|
706
1312
|
} else {
|
|
707
|
-
child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], {
|
|
708
|
-
cwd,
|
|
709
|
-
env,
|
|
710
|
-
stdio: "inherit"
|
|
711
|
-
});
|
|
1313
|
+
child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], { cwd, env, stdio: "inherit" });
|
|
712
1314
|
}
|
|
713
1315
|
const signals = ["SIGINT", "SIGTERM"];
|
|
714
1316
|
for (const signal of signals) {
|
|
@@ -719,7 +1321,6 @@ async function runDev(options = {}) {
|
|
|
719
1321
|
child.on("error", (err) => {
|
|
720
1322
|
if (err.code === "ENOENT") {
|
|
721
1323
|
console.error("[voltx] tsx not found. Install it with: npm install -D tsx");
|
|
722
|
-
console.error("[voltx] Or run your app directly: npx tsx watch src/index.ts");
|
|
723
1324
|
} else {
|
|
724
1325
|
console.error("[voltx] Dev server error:", err.message);
|
|
725
1326
|
}
|
|
@@ -731,6 +1332,8 @@ async function runDev(options = {}) {
|
|
|
731
1332
|
}
|
|
732
1333
|
function findEntryPoint(cwd) {
|
|
733
1334
|
const candidates = [
|
|
1335
|
+
"server.ts",
|
|
1336
|
+
"server.js",
|
|
734
1337
|
"src/index.ts",
|
|
735
1338
|
"src/index.js",
|
|
736
1339
|
"src/index.mts",
|
|
@@ -746,34 +1349,38 @@ function findEntryPoint(cwd) {
|
|
|
746
1349
|
}
|
|
747
1350
|
return null;
|
|
748
1351
|
}
|
|
749
|
-
function
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1352
|
+
function findBin(cwd, name) {
|
|
1353
|
+
const paths = [
|
|
1354
|
+
(0, import_node_path.join)(cwd, "node_modules", ".bin", name),
|
|
1355
|
+
(0, import_node_path.join)(cwd, "..", "node_modules", ".bin", name),
|
|
1356
|
+
(0, import_node_path.join)(cwd, "..", "..", "node_modules", ".bin", name)
|
|
1357
|
+
];
|
|
1358
|
+
for (const p of paths) {
|
|
1359
|
+
if ((0, import_node_fs.existsSync)(p)) return p;
|
|
1360
|
+
}
|
|
756
1361
|
return null;
|
|
757
1362
|
}
|
|
758
|
-
function printDevBanner(entry, port) {
|
|
1363
|
+
function printDevBanner(entry, port, fullStack) {
|
|
759
1364
|
console.log("");
|
|
760
1365
|
console.log(" \u26A1 VoltX Dev Server");
|
|
761
1366
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
762
1367
|
console.log(` Entry: ${entry}`);
|
|
763
|
-
|
|
764
|
-
|
|
1368
|
+
console.log(` Port: ${port}`);
|
|
1369
|
+
console.log(` Mode: ${fullStack ? "full-stack (Vite + Hono)" : "API-only (tsx watch)"}`);
|
|
1370
|
+
if (fullStack) {
|
|
1371
|
+
console.log(` HMR: enabled (frontend + backend)`);
|
|
765
1372
|
}
|
|
766
|
-
console.log(` Mode: development (hot reload)`);
|
|
767
1373
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
768
1374
|
console.log("");
|
|
769
1375
|
}
|
|
770
|
-
var import_node_child_process, import_node_path, import_node_fs;
|
|
1376
|
+
var import_node_child_process, import_node_path, import_node_fs, import_core;
|
|
771
1377
|
var init_dev = __esm({
|
|
772
1378
|
"src/dev.ts"() {
|
|
773
1379
|
"use strict";
|
|
774
1380
|
import_node_child_process = require("child_process");
|
|
775
1381
|
import_node_path = require("path");
|
|
776
1382
|
import_node_fs = require("fs");
|
|
1383
|
+
import_core = require("@voltx/core");
|
|
777
1384
|
}
|
|
778
1385
|
});
|
|
779
1386
|
|
|
@@ -791,7 +1398,7 @@ async function runBuild(options = {}) {
|
|
|
791
1398
|
sourcemap = false
|
|
792
1399
|
} = options;
|
|
793
1400
|
if (!entry) {
|
|
794
|
-
console.error("[voltx] Could not find entry point. Expected src/index.ts");
|
|
1401
|
+
console.error("[voltx] Could not find entry point. Expected server.ts or src/index.ts");
|
|
795
1402
|
process.exit(1);
|
|
796
1403
|
}
|
|
797
1404
|
const entryPath = (0, import_node_path2.resolve)(cwd, entry);
|
|
@@ -799,59 +1406,87 @@ async function runBuild(options = {}) {
|
|
|
799
1406
|
console.error(`[voltx] Entry file not found: ${entry}`);
|
|
800
1407
|
process.exit(1);
|
|
801
1408
|
}
|
|
1409
|
+
(0, import_core2.loadEnv)("production", cwd);
|
|
1410
|
+
const hasViteConfig = (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(cwd, "vite.config.ts"));
|
|
1411
|
+
const hasServerEntry = (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(cwd, "server.ts"));
|
|
1412
|
+
const isFullStack = hasViteConfig || hasServerEntry;
|
|
802
1413
|
console.log("");
|
|
803
1414
|
console.log(" \u26A1 VoltX Build");
|
|
804
1415
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
805
1416
|
console.log(` Entry: ${entry}`);
|
|
806
1417
|
console.log(` Output: ${outDir}/`);
|
|
1418
|
+
console.log(` Mode: ${isFullStack ? "full-stack" : "API-only"}`);
|
|
807
1419
|
console.log(` Minify: ${minify}`);
|
|
808
1420
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
809
1421
|
console.log("");
|
|
810
1422
|
(0, import_node_fs2.mkdirSync)((0, import_node_path2.resolve)(cwd, outDir), { recursive: true });
|
|
811
|
-
|
|
1423
|
+
if (isFullStack) {
|
|
1424
|
+
await buildFullStack(cwd, entry, outDir, minify, sourcemap);
|
|
1425
|
+
} else {
|
|
1426
|
+
await buildApiOnly(cwd, entry, outDir, minify, sourcemap);
|
|
1427
|
+
}
|
|
1428
|
+
console.log("");
|
|
1429
|
+
console.log(" \u26A1 Build complete!");
|
|
1430
|
+
console.log(` Run \`voltx start\` to start the production server.`);
|
|
1431
|
+
console.log("");
|
|
1432
|
+
}
|
|
1433
|
+
async function buildFullStack(cwd, entry, outDir, minify, sourcemap) {
|
|
1434
|
+
const ssrEntry = (0, import_node_fs2.existsSync)((0, import_node_path2.join)(cwd, "src", "entry-server.tsx")) ? "src/entry-server.tsx" : null;
|
|
1435
|
+
const totalSteps = ssrEntry ? 3 : 2;
|
|
1436
|
+
console.log(` [1/${totalSteps}] Building client...`);
|
|
1437
|
+
const viteBin = findBin2(cwd, "vite");
|
|
1438
|
+
const clientOutDir = (0, import_node_path2.join)(outDir, "client");
|
|
1439
|
+
await runCommand(
|
|
1440
|
+
viteBin ?? "npx",
|
|
1441
|
+
viteBin ? ["build", "--outDir", clientOutDir] : ["vite", "build", "--outDir", clientOutDir],
|
|
1442
|
+
cwd
|
|
1443
|
+
);
|
|
1444
|
+
console.log(" \u2713 Client built");
|
|
1445
|
+
if (ssrEntry) {
|
|
1446
|
+
console.log(` [2/${totalSteps}] Building SSR bundle...`);
|
|
1447
|
+
const serverOutDir = (0, import_node_path2.join)(outDir, "server");
|
|
1448
|
+
await runCommand(
|
|
1449
|
+
viteBin ?? "npx",
|
|
1450
|
+
viteBin ? ["build", "--ssr", ssrEntry, "--outDir", serverOutDir] : ["vite", "build", "--ssr", ssrEntry, "--outDir", serverOutDir],
|
|
1451
|
+
cwd
|
|
1452
|
+
);
|
|
1453
|
+
console.log(" \u2713 SSR bundle built");
|
|
1454
|
+
}
|
|
1455
|
+
const serverStep = ssrEntry ? 3 : 2;
|
|
1456
|
+
console.log(` [${serverStep}/${totalSteps}] Building server...`);
|
|
1457
|
+
await buildServer(cwd, entry, outDir, minify, sourcemap, false);
|
|
1458
|
+
console.log(" \u2713 Server built");
|
|
1459
|
+
}
|
|
1460
|
+
async function buildApiOnly(cwd, entry, outDir, minify, sourcemap) {
|
|
1461
|
+
console.log(" [1/1] Building server...");
|
|
1462
|
+
await buildServer(cwd, entry, outDir, minify, sourcemap);
|
|
1463
|
+
console.log(" \u2713 Server built");
|
|
1464
|
+
}
|
|
1465
|
+
async function buildServer(cwd, entry, outDir, minify, sourcemap, clean = true) {
|
|
1466
|
+
const tsupBin = findBin2(cwd, "tsup");
|
|
812
1467
|
const tsupArgs = [
|
|
813
1468
|
entry,
|
|
814
1469
|
"--format",
|
|
815
1470
|
"esm",
|
|
816
1471
|
"--out-dir",
|
|
817
1472
|
outDir,
|
|
818
|
-
"--clean",
|
|
819
1473
|
"--target",
|
|
820
|
-
"node20"
|
|
1474
|
+
"node20",
|
|
1475
|
+
"--no-splitting"
|
|
821
1476
|
];
|
|
1477
|
+
if (clean) tsupArgs.push("--clean");
|
|
822
1478
|
if (minify) tsupArgs.push("--minify");
|
|
823
1479
|
if (sourcemap) tsupArgs.push("--sourcemap");
|
|
824
|
-
tsupArgs.push("--no-splitting");
|
|
825
|
-
const tsupBin = findBin(cwd, "tsup");
|
|
826
1480
|
await runCommand(
|
|
827
1481
|
tsupBin ?? "npx",
|
|
828
1482
|
tsupBin ? tsupArgs : ["tsup", ...tsupArgs],
|
|
829
1483
|
cwd
|
|
830
1484
|
);
|
|
831
|
-
console.log(" \u2713 Server built successfully");
|
|
832
|
-
const frontendDir = (0, import_node_path2.resolve)(cwd, "src", "frontend");
|
|
833
|
-
const viteConfig = (0, import_node_path2.resolve)(cwd, "vite.config.ts");
|
|
834
|
-
const hasFrontend = (0, import_node_fs2.existsSync)(frontendDir) || (0, import_node_fs2.existsSync)(viteConfig);
|
|
835
|
-
if (hasFrontend) {
|
|
836
|
-
console.log(" [2/2] Building frontend...");
|
|
837
|
-
const viteBin = findBin(cwd, "vite");
|
|
838
|
-
const viteArgs = ["build", "--outDir", (0, import_node_path2.join)(outDir, "public")];
|
|
839
|
-
await runCommand(
|
|
840
|
-
viteBin ?? "npx",
|
|
841
|
-
viteBin ? viteArgs : ["vite", ...viteArgs],
|
|
842
|
-
cwd
|
|
843
|
-
);
|
|
844
|
-
console.log(" \u2713 Frontend built successfully");
|
|
845
|
-
} else {
|
|
846
|
-
console.log(" [2/2] No frontend found, skipping...");
|
|
847
|
-
}
|
|
848
|
-
console.log("");
|
|
849
|
-
console.log(" \u26A1 Build complete!");
|
|
850
|
-
console.log(` Run \`voltx start\` to start the production server.`);
|
|
851
|
-
console.log("");
|
|
852
1485
|
}
|
|
853
1486
|
function findEntryPoint2(cwd) {
|
|
854
1487
|
const candidates = [
|
|
1488
|
+
"server.ts",
|
|
1489
|
+
"server.js",
|
|
855
1490
|
"src/index.ts",
|
|
856
1491
|
"src/index.js",
|
|
857
1492
|
"src/index.mts",
|
|
@@ -865,7 +1500,7 @@ function findEntryPoint2(cwd) {
|
|
|
865
1500
|
}
|
|
866
1501
|
return null;
|
|
867
1502
|
}
|
|
868
|
-
function
|
|
1503
|
+
function findBin2(cwd, name) {
|
|
869
1504
|
const paths = [
|
|
870
1505
|
(0, import_node_path2.join)(cwd, "node_modules", ".bin", name),
|
|
871
1506
|
(0, import_node_path2.join)(cwd, "..", "node_modules", ".bin", name),
|
|
@@ -895,13 +1530,14 @@ function runCommand(cmd, args2, cwd) {
|
|
|
895
1530
|
});
|
|
896
1531
|
});
|
|
897
1532
|
}
|
|
898
|
-
var import_node_child_process2, import_node_path2, import_node_fs2;
|
|
1533
|
+
var import_node_child_process2, import_node_path2, import_node_fs2, import_core2;
|
|
899
1534
|
var init_build = __esm({
|
|
900
1535
|
"src/build.ts"() {
|
|
901
1536
|
"use strict";
|
|
902
1537
|
import_node_child_process2 = require("child_process");
|
|
903
1538
|
import_node_path2 = require("path");
|
|
904
1539
|
import_node_fs2 = require("fs");
|
|
1540
|
+
import_core2 = require("@voltx/core");
|
|
905
1541
|
}
|
|
906
1542
|
});
|
|
907
1543
|
|
|
@@ -930,26 +1566,15 @@ async function runStart(options = {}) {
|
|
|
930
1566
|
console.error(`[voltx] Entry file not found: ${outDir}/${entry}`);
|
|
931
1567
|
process.exit(1);
|
|
932
1568
|
}
|
|
1569
|
+
(0, import_core3.loadEnv)("production", cwd);
|
|
933
1570
|
const env = {
|
|
934
1571
|
...process.env,
|
|
935
1572
|
NODE_ENV: "production"
|
|
936
1573
|
};
|
|
937
|
-
const envFile = (0, import_node_path3.resolve)(cwd, ".env");
|
|
938
|
-
if ((0, import_node_fs3.existsSync)(envFile)) {
|
|
939
|
-
const envContent = (0, import_node_fs3.readFileSync)(envFile, "utf-8");
|
|
940
|
-
for (const line of envContent.split("\n")) {
|
|
941
|
-
const trimmed = line.trim();
|
|
942
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
943
|
-
const eqIdx = trimmed.indexOf("=");
|
|
944
|
-
if (eqIdx === -1) continue;
|
|
945
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
946
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
947
|
-
env[key] = value;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
1574
|
if (port) {
|
|
951
1575
|
env.PORT = String(port);
|
|
952
1576
|
}
|
|
1577
|
+
const hasClientBuild = (0, import_node_fs3.existsSync)((0, import_node_path3.resolve)(distDir, "client"));
|
|
953
1578
|
console.log("");
|
|
954
1579
|
console.log(" \u26A1 VoltX Production Server");
|
|
955
1580
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -957,7 +1582,7 @@ async function runStart(options = {}) {
|
|
|
957
1582
|
if (port) {
|
|
958
1583
|
console.log(` Port: ${port}`);
|
|
959
1584
|
}
|
|
960
|
-
console.log(` Mode:
|
|
1585
|
+
console.log(` Mode: ${hasClientBuild ? "full-stack" : "API-only"}`);
|
|
961
1586
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
962
1587
|
console.log("");
|
|
963
1588
|
const child = (0, import_node_child_process3.spawn)("node", [entryPath], {
|
|
@@ -981,13 +1606,13 @@ async function runStart(options = {}) {
|
|
|
981
1606
|
}
|
|
982
1607
|
function findDistEntry(distDir) {
|
|
983
1608
|
const candidates = [
|
|
1609
|
+
"server.mjs",
|
|
1610
|
+
"server.js",
|
|
984
1611
|
"index.mjs",
|
|
985
1612
|
"index.js",
|
|
986
1613
|
"index.cjs",
|
|
987
1614
|
"main.mjs",
|
|
988
|
-
"main.js"
|
|
989
|
-
"src/index.mjs",
|
|
990
|
-
"src/index.js"
|
|
1615
|
+
"main.js"
|
|
991
1616
|
];
|
|
992
1617
|
for (const candidate of candidates) {
|
|
993
1618
|
if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(distDir, candidate))) {
|
|
@@ -996,13 +1621,14 @@ function findDistEntry(distDir) {
|
|
|
996
1621
|
}
|
|
997
1622
|
return null;
|
|
998
1623
|
}
|
|
999
|
-
var import_node_child_process3, import_node_path3, import_node_fs3;
|
|
1624
|
+
var import_node_child_process3, import_node_path3, import_node_fs3, import_core3;
|
|
1000
1625
|
var init_start = __esm({
|
|
1001
1626
|
"src/start.ts"() {
|
|
1002
1627
|
"use strict";
|
|
1003
1628
|
import_node_child_process3 = require("child_process");
|
|
1004
1629
|
import_node_path3 = require("path");
|
|
1005
1630
|
import_node_fs3 = require("fs");
|
|
1631
|
+
import_core3 = require("@voltx/core");
|
|
1006
1632
|
}
|
|
1007
1633
|
});
|
|
1008
1634
|
|
|
@@ -1035,13 +1661,13 @@ async function runGenerate(options) {
|
|
|
1035
1661
|
}
|
|
1036
1662
|
function generateRoute(cwd, name, method = "POST") {
|
|
1037
1663
|
const routePath = name.startsWith("/") ? name.slice(1) : name;
|
|
1038
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1664
|
+
const filePath = (0, import_node_path4.join)(cwd, "api", `${routePath}.ts`);
|
|
1039
1665
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1040
|
-
console.error(`[voltx] Route already exists:
|
|
1666
|
+
console.error(`[voltx] Route already exists: api/${routePath}.ts`);
|
|
1041
1667
|
process.exit(1);
|
|
1042
1668
|
}
|
|
1043
1669
|
const upperMethod = method.toUpperCase();
|
|
1044
|
-
const urlPath = "/" + routePath;
|
|
1670
|
+
const urlPath = "/api/" + routePath;
|
|
1045
1671
|
const content = `// ${upperMethod} ${urlPath}
|
|
1046
1672
|
import type { Context } from "@voltx/server";
|
|
1047
1673
|
|
|
@@ -1050,13 +1676,13 @@ export async function ${upperMethod}(c: Context) {
|
|
|
1050
1676
|
}
|
|
1051
1677
|
`;
|
|
1052
1678
|
writeFileSafe(filePath, content);
|
|
1053
|
-
console.log(` \u2713 Created route:
|
|
1679
|
+
console.log(` \u2713 Created route: api/${routePath}.ts`);
|
|
1054
1680
|
console.log(` ${upperMethod} ${urlPath}`);
|
|
1055
1681
|
}
|
|
1056
1682
|
function generateAgent(cwd, name) {
|
|
1057
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1683
|
+
const filePath = (0, import_node_path4.join)(cwd, "agents", `${name}.ts`);
|
|
1058
1684
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1059
|
-
console.error(`[voltx] Agent already exists:
|
|
1685
|
+
console.error(`[voltx] Agent already exists: agents/${name}.ts`);
|
|
1060
1686
|
process.exit(1);
|
|
1061
1687
|
}
|
|
1062
1688
|
const content = `// Agent: ${name}
|
|
@@ -1078,12 +1704,12 @@ export const ${toCamelCase(name)} = createAgent({
|
|
|
1078
1704
|
});
|
|
1079
1705
|
`;
|
|
1080
1706
|
writeFileSafe(filePath, content);
|
|
1081
|
-
console.log(` \u2713 Created agent:
|
|
1707
|
+
console.log(` \u2713 Created agent: agents/${name}.ts`);
|
|
1082
1708
|
}
|
|
1083
1709
|
function generateTool(cwd, name) {
|
|
1084
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1710
|
+
const filePath = (0, import_node_path4.join)(cwd, "tools", `${name}.ts`);
|
|
1085
1711
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1086
|
-
console.error(`[voltx] Tool already exists:
|
|
1712
|
+
console.error(`[voltx] Tool already exists: tools/${name}.ts`);
|
|
1087
1713
|
process.exit(1);
|
|
1088
1714
|
}
|
|
1089
1715
|
const content = `// Tool: ${name}
|
|
@@ -1105,12 +1731,12 @@ export const ${toCamelCase(name)}Tool = {
|
|
|
1105
1731
|
};
|
|
1106
1732
|
`;
|
|
1107
1733
|
writeFileSafe(filePath, content);
|
|
1108
|
-
console.log(` \u2713 Created tool:
|
|
1734
|
+
console.log(` \u2713 Created tool: tools/${name}.ts`);
|
|
1109
1735
|
}
|
|
1110
1736
|
function generateJob(cwd, name) {
|
|
1111
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1737
|
+
const filePath = (0, import_node_path4.join)(cwd, "jobs", `${name}.ts`);
|
|
1112
1738
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1113
|
-
console.error(`[voltx] Job already exists:
|
|
1739
|
+
console.error(`[voltx] Job already exists: jobs/${name}.ts`);
|
|
1114
1740
|
process.exit(1);
|
|
1115
1741
|
}
|
|
1116
1742
|
const content = `// Job: ${name}
|
|
@@ -1135,7 +1761,7 @@ export async function run(ctx: any, data?: Record<string, unknown>) {
|
|
|
1135
1761
|
}
|
|
1136
1762
|
`;
|
|
1137
1763
|
writeFileSafe(filePath, content);
|
|
1138
|
-
console.log(` \u2713 Created job:
|
|
1764
|
+
console.log(` \u2713 Created job: jobs/${name}.ts`);
|
|
1139
1765
|
}
|
|
1140
1766
|
function writeFileSafe(filePath, content) {
|
|
1141
1767
|
const dir = (0, import_node_path4.dirname)(filePath);
|
|
@@ -1197,13 +1823,14 @@ async function main() {
|
|
|
1197
1823
|
case "create": {
|
|
1198
1824
|
const projectName = args[1];
|
|
1199
1825
|
if (!projectName || projectName.startsWith("-")) {
|
|
1200
|
-
console.error("[voltx] Usage: voltx create <project-name> [--template chatbot|rag-app|agent-app] [--auth better-auth|jwt|none]");
|
|
1826
|
+
console.error("[voltx] Usage: voltx create <project-name> [--template chatbot|rag-app|agent-app] [--auth better-auth|jwt|none] [--shadcn]");
|
|
1201
1827
|
process.exit(1);
|
|
1202
1828
|
}
|
|
1203
1829
|
const template = parseFlag("--template");
|
|
1204
1830
|
const auth = parseFlag("--auth");
|
|
1831
|
+
const useShadcn = args.includes("--shadcn");
|
|
1205
1832
|
const { createProject: createProject2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
1206
|
-
await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none" });
|
|
1833
|
+
await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none", shadcn: useShadcn });
|
|
1207
1834
|
break;
|
|
1208
1835
|
}
|
|
1209
1836
|
// ─── voltx dev ─────────────────────────────────────────────────────
|
|
@@ -1246,7 +1873,7 @@ async function main() {
|
|
|
1246
1873
|
console.error("[voltx] Usage: voltx generate <type> <name>");
|
|
1247
1874
|
console.error("");
|
|
1248
1875
|
console.error(" Types:");
|
|
1249
|
-
console.error(" route <path> Generate a new API route (e.g.,
|
|
1876
|
+
console.error(" route <path> Generate a new API route (e.g., users)");
|
|
1250
1877
|
console.error(" agent <name> Generate a new agent (e.g., assistant)");
|
|
1251
1878
|
console.error(" tool <name> Generate a new tool (e.g., search)");
|
|
1252
1879
|
console.error(" job <name> Generate a new background job (e.g., cleanup)");
|
|
@@ -1299,7 +1926,7 @@ function printHelp() {
|
|
|
1299
1926
|
|
|
1300
1927
|
Dev Options:
|
|
1301
1928
|
--port, -p <number> Port override
|
|
1302
|
-
--entry <file> Custom entry file (default:
|
|
1929
|
+
--entry <file> Custom entry file (default: server.ts)
|
|
1303
1930
|
--no-clear Don't clear screen on restart
|
|
1304
1931
|
|
|
1305
1932
|
Build Options:
|
|
@@ -1314,7 +1941,7 @@ function printHelp() {
|
|
|
1314
1941
|
--entry <file> Entry file within output dir
|
|
1315
1942
|
|
|
1316
1943
|
Generate Types:
|
|
1317
|
-
route <path> API route (e.g., voltx generate route
|
|
1944
|
+
route <path> API route (e.g., voltx generate route users)
|
|
1318
1945
|
agent <name> Agent (e.g., voltx generate agent assistant)
|
|
1319
1946
|
tool <name> Tool (e.g., voltx generate tool search)
|
|
1320
1947
|
job <name> Background job (e.g., voltx generate job cleanup)
|
|
@@ -1324,7 +1951,7 @@ function printHelp() {
|
|
|
1324
1951
|
voltx dev --port 4000
|
|
1325
1952
|
voltx build --sourcemap
|
|
1326
1953
|
voltx start
|
|
1327
|
-
voltx generate route
|
|
1954
|
+
voltx generate route users --method GET
|
|
1328
1955
|
voltx generate agent assistant
|
|
1329
1956
|
`);
|
|
1330
1957
|
}
|