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