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