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