@flight-framework/cli 0.1.0 → 0.2.1

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.
Files changed (92) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +544 -474
  3. package/dist/bin.js +1352 -829
  4. package/dist/bin.js.map +1 -1
  5. package/dist/index.js +1352 -829
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/templates/angular/index.html +13 -13
  9. package/templates/angular/package.json.template +25 -25
  10. package/templates/angular/src/app.component.ts +13 -13
  11. package/templates/angular/src/main.server.ts +11 -11
  12. package/templates/angular/src/main.ts +4 -4
  13. package/templates/angular/tsconfig.json +16 -16
  14. package/templates/base/README.md.template +26 -26
  15. package/templates/base/_gitignore +25 -25
  16. package/templates/base/flight.config.ts.template +15 -15
  17. package/templates/base/styles/global.css +58 -58
  18. package/templates/htmx/index.html +18 -18
  19. package/templates/htmx/package.json.template +18 -18
  20. package/templates/htmx/vite.config.ts +6 -6
  21. package/templates/lit/index.html +14 -14
  22. package/templates/lit/package.json.template +21 -21
  23. package/templates/lit/src/app-root.ts +18 -18
  24. package/templates/lit/src/entry-client.ts +5 -5
  25. package/templates/lit/src/entry-server.ts +9 -9
  26. package/templates/lit/tsconfig.json +18 -18
  27. package/templates/lit/vite.config.ts +6 -6
  28. package/templates/preact/index.html +14 -14
  29. package/templates/preact/package.json.template +22 -22
  30. package/templates/preact/src/App.tsx +8 -8
  31. package/templates/preact/src/entry-client.tsx +11 -11
  32. package/templates/preact/src/entry-server.tsx +6 -6
  33. package/templates/preact/tsconfig.json +18 -18
  34. package/templates/preact/vite.config.ts +8 -8
  35. package/templates/qwik/index.html +14 -14
  36. package/templates/qwik/package.json.template +20 -20
  37. package/templates/qwik/src/App.tsx +10 -10
  38. package/templates/qwik/src/entry-client.tsx +4 -4
  39. package/templates/qwik/src/entry-server.tsx +9 -9
  40. package/templates/qwik/tsconfig.json +18 -18
  41. package/templates/qwik/vite.config.ts +8 -8
  42. package/templates/react/index.html +13 -13
  43. package/templates/react/package.json.template +24 -24
  44. package/templates/react/src/App.tsx +13 -13
  45. package/templates/react/src/context/RouterContext.tsx +63 -63
  46. package/templates/react/src/entry-client.tsx +19 -19
  47. package/templates/react/src/entry-server.tsx +17 -17
  48. package/templates/react/tsconfig.json +19 -19
  49. package/templates/react/vite.config.ts +12 -12
  50. package/templates/solid/index.html +14 -14
  51. package/templates/solid/package.json.template +21 -21
  52. package/templates/solid/src/App.tsx +8 -8
  53. package/templates/solid/src/entry-client.tsx +11 -11
  54. package/templates/solid/src/entry-server.tsx +6 -6
  55. package/templates/solid/tsconfig.json +18 -18
  56. package/templates/solid/vite.config.ts +8 -8
  57. package/templates/svelte/index.html +14 -14
  58. package/templates/svelte/package.json.template +21 -21
  59. package/templates/svelte/src/App.svelte +4 -4
  60. package/templates/svelte/src/entry-client.ts +7 -7
  61. package/templates/svelte/src/entry-server.ts +7 -7
  62. package/templates/svelte/tsconfig.json +17 -17
  63. package/templates/svelte/vite.config.ts +8 -8
  64. package/templates/use-cases/api/README.md +41 -41
  65. package/templates/use-cases/api/package.json.template +14 -14
  66. package/templates/use-cases/api/src/routes/api/health.get.ts.template +3 -3
  67. package/templates/use-cases/blog/README.md +47 -47
  68. package/templates/use-cases/blog/flight.config.ts.template +11 -11
  69. package/templates/use-cases/blog/package.json.template +15 -15
  70. package/templates/use-cases/blog/src/routes/blog/[slug].page.tsx.template +23 -23
  71. package/templates/use-cases/blog/src/routes/index.page.tsx.template +9 -9
  72. package/templates/use-cases/docs/README.md +49 -49
  73. package/templates/use-cases/docs/package.json.template +15 -15
  74. package/templates/use-cases/docs/src/content/index.md.template +16 -16
  75. package/templates/use-cases/ecommerce/README.md +32 -32
  76. package/templates/use-cases/ecommerce/package.json.template +16 -16
  77. package/templates/use-cases/ecommerce/src/routes/index.page.tsx.template +9 -9
  78. package/templates/use-cases/saas/README.md +34 -34
  79. package/templates/use-cases/saas/package.json.template +15 -15
  80. package/templates/use-cases/saas/src/routes/index.page.tsx.template +9 -9
  81. package/templates/vanilla/index.html +14 -14
  82. package/templates/vanilla/package.json.template +19 -19
  83. package/templates/vanilla/src/main.ts +10 -10
  84. package/templates/vanilla/tsconfig.json +16 -16
  85. package/templates/vanilla/vite.config.ts +6 -6
  86. package/templates/vue/index.html +14 -14
  87. package/templates/vue/package.json.template +21 -21
  88. package/templates/vue/src/App.vue +6 -6
  89. package/templates/vue/src/entry-client.ts +12 -12
  90. package/templates/vue/src/entry-server.ts +8 -8
  91. package/templates/vue/tsconfig.json +17 -17
  92. package/templates/vue/vite.config.ts +8 -8
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/index.ts
10
10
  import { cac } from "cac";
11
- import pc5 from "picocolors";
11
+ import pc6 from "picocolors";
12
12
 
13
13
  // src/version.ts
14
14
  var VERSION = "0.0.1";
@@ -61,6 +61,21 @@ async function createCommand(name, options) {
61
61
  console.log(pc.red("Project creation cancelled."));
62
62
  return;
63
63
  }
64
+ if (options.raw) {
65
+ const projectPath2 = resolve(process.cwd(), projectName);
66
+ createRawProject(projectPath2, projectName, options);
67
+ return;
68
+ }
69
+ if (options.empty) {
70
+ const projectPath2 = resolve(process.cwd(), projectName);
71
+ createEmptyProject(projectPath2, projectName, options);
72
+ return;
73
+ }
74
+ if (options.minimal) {
75
+ const projectPath2 = resolve(process.cwd(), projectName);
76
+ createMinimalProject(projectPath2, projectName, options);
77
+ return;
78
+ }
64
79
  if (!uiFramework) {
65
80
  const response = await prompts({
66
81
  type: "select",
@@ -221,710 +236,311 @@ function copyDirWithTemplates(srcDir, destDir, vars) {
221
236
  }
222
237
  }
223
238
  }
239
+ function createRawProject(projectPath, projectName, options) {
240
+ mkdirSync(projectPath, { recursive: true });
241
+ const packageJson = {
242
+ name: projectName,
243
+ version: "0.0.1",
244
+ type: "module",
245
+ scripts: {
246
+ // Works with Bun, Node 22+, or Deno
247
+ "dev": "node --watch server.js",
248
+ "dev:bun": "bun --watch server.js",
249
+ "dev:deno": "deno run --allow-net server.js",
250
+ "start": "node server.js"
251
+ },
252
+ dependencies: {},
253
+ devDependencies: {}
254
+ };
255
+ writeFileSync(
256
+ join(projectPath, "package.json"),
257
+ JSON.stringify(packageJson, null, 2)
258
+ );
259
+ const serverCode = `/**
260
+ * ${projectName}
261
+ *
262
+ * 100% Web Standards server. Zero dependencies. Zero lock-in.
263
+ * Works on: Bun, Deno, Node 22+, Cloudflare Workers
264
+ *
265
+ * Run:
266
+ * bun server.js
267
+ * deno run --allow-net server.js
268
+ * node server.js (Node 22+)
269
+ */
224
270
 
225
- // src/commands/dev.ts
226
- import { resolve as resolve2, join as join2 } from "path";
227
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
228
- import pc2 from "picocolors";
229
- import { loadConfig } from "@flight-framework/core/config";
230
- async function devCommand(options) {
231
- const startTime = Date.now();
232
- printLogo();
233
- console.log(pc2.cyan("\n\u2708\uFE0F Starting Flight development server...\n"));
234
- try {
235
- const root = resolve2(process.cwd());
236
- const config = await loadConfig(root);
237
- const port = options.port ? parseInt(options.port, 10) : config.dev.port;
238
- const host = options.host ?? config.dev.host;
239
- const open = options.open ?? config.dev.open;
240
- const ssrEnabled = options.ssr ?? config.rendering?.default === "ssr";
241
- const entryServerPath = join2(root, "src", "entry-server.tsx");
242
- const hasSSREntry = existsSync2(entryServerPath);
243
- const { createServer: createViteServer } = await import("vite");
244
- let flightHttpAvailable = false;
245
- let flightRouterAvailable = false;
246
- try {
247
- await import("@flight-framework/http");
248
- flightHttpAvailable = true;
249
- } catch {
271
+ /**
272
+ * Handle incoming requests using Web Standard APIs
273
+ * @param {Request} request
274
+ * @returns {Response}
275
+ */
276
+ function handleRequest(request) {
277
+ const url = new URL(request.url);
278
+ const method = request.method;
279
+
280
+ // Router
281
+ if (method === 'GET' && url.pathname === '/') {
282
+ return Response.json({
283
+ message: 'Hello World!',
284
+ runtime: detectRuntime(),
285
+ docs: 'https://flight.dev/docs/quickstart-http',
286
+ });
250
287
  }
251
- try {
252
- await import("@flight-framework/core/file-router");
253
- flightRouterAvailable = true;
254
- } catch {
288
+
289
+ if (method === 'GET' && url.pathname === '/health') {
290
+ return Response.json({
291
+ status: 'ok',
292
+ timestamp: Date.now(),
293
+ });
255
294
  }
256
- const vite = await createViteServer({
257
- root,
258
- mode: "development",
259
- server: {
260
- middlewareMode: ssrEnabled && hasSSREntry,
261
- port: ssrEnabled && hasSSREntry ? void 0 : port,
262
- host: host === true ? "0.0.0.0" : host,
263
- open: ssrEnabled && hasSSREntry ? false : open,
264
- https: options.https ? {} : void 0
265
- },
266
- appType: ssrEnabled && hasSSREntry ? "custom" : "spa",
267
- plugins: [
268
- // Add Flight plugin when @flight-framework/http is available
269
- flightHttpAvailable ? flightDevPlugin(root) : null
270
- ].filter(Boolean)
271
- });
272
- if (ssrEnabled && hasSSREntry) {
273
- await startSSRServer(vite, root, port, host);
274
- } else {
275
- await vite.listen();
295
+
296
+ if (method === 'GET' && url.pathname.startsWith('/api/')) {
297
+ return Response.json({
298
+ path: url.pathname,
299
+ query: Object.fromEntries(url.searchParams),
300
+ });
276
301
  }
277
- const elapsed = Date.now() - startTime;
278
- const isSSR = ssrEnabled && hasSSREntry;
279
- console.log(`
280
- ${pc2.green("\u2713")} Flight dev server ready in ${pc2.bold(elapsed + "ms")}
302
+
303
+ // 404
304
+ return Response.json(
305
+ { error: 'Not Found', path: url.pathname },
306
+ { status: 404 }
307
+ );
308
+ }
281
309
 
282
- ${pc2.cyan("\u279C")} Local: ${pc2.cyan(`http://localhost:${port}/`)}
283
- ${host === true || host === "0.0.0.0" ? ` ${pc2.cyan("\u279C")} Network: ${pc2.cyan(`http://${getNetworkAddress()}:${port}/`)}` : ""}
284
-
285
- ${isSSR ? pc2.green("\u2713") : pc2.yellow("\u25CB")} SSR ${isSSR ? "enabled (streaming)" : "disabled (CSR mode)"}
286
- ${flightHttpAvailable ? pc2.green("\u2713") : pc2.yellow("\u25CB")} @flight-framework/http ${flightHttpAvailable ? "enabled" : "not installed"}
287
- ${flightRouterAvailable ? pc2.green("\u2713") : pc2.yellow("\u25CB")} File-based routing ${flightRouterAvailable ? "enabled" : "not available"}
310
+ /**
311
+ * Detect which runtime we're running on
312
+ */
313
+ function detectRuntime() {
314
+ if (typeof Bun !== 'undefined') return 'bun';
315
+ if (typeof Deno !== 'undefined') return 'deno';
316
+ return 'node';
317
+ }
288
318
 
289
- ${pc2.dim("press")} ${pc2.bold("h")} ${pc2.dim("to show help")}
290
- `);
291
- if (!isSSR) {
292
- vite.bindCLIShortcuts({ print: true });
319
+ // Export for different runtimes
320
+ const port = process.env.PORT || 3000;
321
+
322
+ // Bun / Cloudflare Workers style
323
+ export default {
324
+ port,
325
+ fetch: handleRequest,
326
+ };
327
+
328
+ // Also start server for Node.js
329
+ if (detectRuntime() === 'node') {
330
+ const { serve } = await import('node:http');
331
+ serve({ port }, (req, res) => {
332
+ const url = 'http://localhost' + req.url;
333
+ const request = new Request(url, { method: req.method });
334
+ handleRequest(request).then(response => {
335
+ res.writeHead(response.status, Object.fromEntries(response.headers));
336
+ response.text().then(body => res.end(body));
337
+ });
338
+ });
339
+ console.log(\`Server running at http://localhost:\${port}\`);
340
+ }
341
+ `;
342
+ writeFileSync(join(projectPath, "server.js"), serverCode);
343
+ if (options.git) {
344
+ writeFileSync(
345
+ join(projectPath, ".gitignore"),
346
+ "node_modules\ndist\n.env\n.env.local\n"
347
+ );
348
+ try {
349
+ execSync("git init", { cwd: projectPath, stdio: "ignore" });
350
+ } catch {
293
351
  }
294
- } catch (error) {
295
- console.error(pc2.red("\nFailed to start dev server:"), error);
296
- process.exit(1);
297
352
  }
353
+ console.log(`
354
+ ${pc.green("[OK] Raw project created!")}
355
+
356
+ ${pc.bold("ZERO dependencies. ZERO lock-in. 100% Web Standards.")}
357
+
358
+ ${pc.cyan("Run with any runtime:")}
359
+
360
+ ${pc.dim("# Bun (fastest)")}
361
+ bun server.js
362
+
363
+ ${pc.dim("# Deno")}
364
+ deno run --allow-net server.js
365
+
366
+ ${pc.dim("# Node.js 22+")}
367
+ node server.js
368
+
369
+ ${pc.dim("Want to add Flight later? Just run:")}
370
+ flight add http ${pc.dim("# HTTP server with routing")}
371
+ flight add db ${pc.dim("# Database abstraction")}
372
+ flight add cache ${pc.dim("# Caching layer")}
373
+
374
+ ${pc.dim("Path:")} ${projectPath}
375
+ `);
298
376
  }
299
- async function startSSRServer(vite, root, port, host) {
300
- const { createServer: createHttpServer } = await import("http");
301
- let pageRouter = null;
302
- try {
303
- const { createFileRouter } = await import("@flight-framework/core/file-router");
304
- const moduleLoader = async (filePath) => {
305
- const normalizedFilePath = filePath.replace(/\\/g, "/");
306
- const normalizedRoot = root.replace(/\\/g, "/");
307
- const relativePath = normalizedFilePath.replace(normalizedRoot, "");
308
- return vite.ssrLoadModule(relativePath);
309
- };
310
- pageRouter = await createFileRouter({
311
- directory: join2(root, "src", "routes"),
312
- extensions: [".tsx", ".ts", ".jsx", ".js"],
313
- moduleLoader
314
- // Use Vite's ssrLoadModule
315
- });
316
- console.log(pc2.green(` \u2713 Page router loaded: ${pageRouter.routes.filter((r) => r.type === "page").length} pages`));
317
- } catch {
318
- }
319
- const server = createHttpServer(async (req, res) => {
320
- const url = req.url || "/";
321
- const pathname = url.split("?")[0];
322
- const isStaticAsset = url.startsWith("/@") || url.startsWith("/node_modules") || url.startsWith("/src") && !url.includes("entry-server") || // Static file extensions
323
- pathname.endsWith(".css") || pathname.endsWith(".js") || pathname.endsWith(".ts") || pathname.endsWith(".tsx") || pathname.endsWith(".svg") || pathname.endsWith(".png") || pathname.endsWith(".jpg") || pathname.endsWith(".jpeg") || pathname.endsWith(".gif") || pathname.endsWith(".ico") || pathname.endsWith(".woff") || pathname.endsWith(".woff2") || pathname.endsWith(".ttf") || pathname.endsWith(".eot") || pathname.endsWith(".json") || pathname.endsWith(".webp") || pathname.endsWith(".mp4") || pathname.endsWith(".webm");
324
- if (isStaticAsset) {
325
- vite.middlewares(req, res);
326
- return;
327
- }
328
- if (pathname.startsWith("/api/")) {
329
- vite.middlewares(req, res);
330
- return;
331
- }
377
+ function createEmptyProject(projectPath, projectName, options) {
378
+ mkdirSync(projectPath, { recursive: true });
379
+ const packageJson = {
380
+ name: projectName,
381
+ version: "0.0.1",
382
+ type: "module",
383
+ scripts: {
384
+ dev: "node server.js"
385
+ },
386
+ dependencies: {},
387
+ devDependencies: {}
388
+ };
389
+ writeFileSync(
390
+ join(projectPath, "package.json"),
391
+ JSON.stringify(packageJson, null, 2)
392
+ );
393
+ if (options.git) {
394
+ writeFileSync(
395
+ join(projectPath, ".gitignore"),
396
+ "node_modules\ndist\n.env\n.env.local\n"
397
+ );
332
398
  try {
333
- let template = readFileSync2(
334
- join2(root, "index.html"),
335
- "utf-8"
336
- );
337
- template = await vite.transformIndexHtml(url, template);
338
- let appHtml = "";
339
- if (pageRouter) {
340
- const pageRoute = pageRouter.routes.find(
341
- (r) => r.type === "page" && matchPath(r.path, pathname)
342
- );
343
- if (pageRoute && pageRoute.component) {
344
- const normalizedFilePath = pageRoute.filePath.replace(/\\/g, "/");
345
- const normalizedRoot = root.replace(/\\/g, "/");
346
- const relativePath = normalizedFilePath.replace(normalizedRoot, "");
347
- const mod = await vite.ssrLoadModule(relativePath);
348
- const Component = mod.default;
349
- const { render } = await vite.ssrLoadModule("/src/entry-server.tsx");
350
- if (typeof render === "function" && Component) {
351
- appHtml = await render(url, { Component });
352
- }
353
- }
354
- }
355
- if (!appHtml) {
356
- const { render } = await vite.ssrLoadModule("/src/entry-server.tsx");
357
- if (typeof render === "function") {
358
- appHtml = await render(url);
359
- }
360
- }
361
- if (appHtml) {
362
- const html = template.replace("<!--ssr-outlet-->", appHtml);
363
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
364
- res.end(html);
365
- } else {
366
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
367
- res.end(template);
368
- }
369
- } catch (e) {
370
- vite.ssrFixStacktrace(e);
371
- console.error(pc2.red("[SSR Error]"), e.stack);
372
- if (!res.headersSent) {
373
- res.writeHead(500, { "Content-Type": "text/plain" });
374
- res.end(`SSR Error: ${e.message}
375
-
376
- ${e.stack}`);
377
- }
399
+ execSync("git init", { cwd: projectPath, stdio: "ignore" });
400
+ } catch {
378
401
  }
379
- });
380
- const listenHost = host === true ? "0.0.0.0" : host || "localhost";
381
- server.listen(port, listenHost, () => {
382
- console.log(pc2.green(` \u2713 SSR server listening`));
383
- });
402
+ }
403
+ console.log(`
404
+ ${pc.green("[OK] Empty project created!")}
405
+
406
+ ${pc.cyan("Your project is a blank canvas.")} Add what you need:
407
+
408
+ ${pc.dim("# HTTP server")}
409
+ npm install @flight-framework/http
410
+
411
+ ${pc.dim("# Database")}
412
+ npm install @flight-framework/db
413
+
414
+ ${pc.dim("# Cache")}
415
+ npm install @flight-framework/cache
416
+
417
+ ${pc.dim("# Authentication")}
418
+ npm install @flight-framework/auth
419
+
420
+ ${pc.dim("Path:")} ${projectPath}
421
+ `);
384
422
  }
385
- function flightDevPlugin(root) {
386
- return {
387
- name: "flight:dev",
388
- configureServer(server) {
389
- server.middlewares.use(async (req, res, next) => {
390
- const url = req.url || "/";
391
- if (url.startsWith("/__flight_action/")) {
392
- try {
393
- const { handleActionRequest } = await import("@flight-framework/core");
394
- const webRequest = await nodeToWebRequest(req);
395
- const response = await handleActionRequest(webRequest);
396
- res.statusCode = response.status;
397
- response.headers.forEach((value, key) => {
398
- res.setHeader(key, value);
399
- });
400
- const body = await response.text();
401
- res.end(body);
402
- } catch (error) {
403
- console.error("[Flight] Action error:", error);
404
- res.statusCode = 500;
405
- res.end(JSON.stringify({ error: "Internal server error" }));
406
- }
407
- return;
408
- }
409
- if (url.startsWith("/api/")) {
410
- try {
411
- const { createFileRouter } = await import("@flight-framework/core/file-router");
412
- const routesDir = join2(root, "src", "routes");
413
- const router = await createFileRouter({ directory: routesDir });
414
- const route = router.routes.find((r) => {
415
- return matchPath(r.path, url);
416
- });
417
- if (route && route.handler) {
418
- const webRequest = await nodeToWebRequest(req);
419
- const response = await route.handler({ req: webRequest, params: {} });
420
- res.statusCode = response.status;
421
- response.headers.forEach((value, key) => {
422
- res.setHeader(key, value);
423
- });
424
- const body = await response.text();
425
- res.end(body);
426
- return;
427
- }
428
- } catch (error) {
429
- }
430
- }
431
- next();
432
- });
423
+ function createMinimalProject(projectPath, projectName, options) {
424
+ mkdirSync(projectPath, { recursive: true });
425
+ const packageJson = {
426
+ name: projectName,
427
+ version: "0.0.1",
428
+ type: "module",
429
+ scripts: {
430
+ dev: "node --watch server.js",
431
+ start: "node server.js"
432
+ },
433
+ dependencies: {
434
+ "@flight-framework/http": "^0.0.1"
433
435
  }
434
436
  };
435
- }
436
- async function nodeToWebRequest(req) {
437
- const host = req.headers.host || "localhost";
438
- const protocol = "http";
439
- const url = new URL(req.url || "/", `${protocol}://${host}`);
440
- let body = null;
441
- if (req.method !== "GET" && req.method !== "HEAD") {
442
- const chunks = [];
443
- for await (const chunk of req) {
444
- chunks.push(chunk);
437
+ writeFileSync(
438
+ join(projectPath, "package.json"),
439
+ JSON.stringify(packageJson, null, 2)
440
+ );
441
+ const serverCode = `/**
442
+ * ${projectName} - Built with Flight
443
+ *
444
+ * This is a minimal Flight server. Add more as you need.
445
+ * Run: npm run dev
446
+ */
447
+
448
+ import { createServer } from '@flight-framework/http';
449
+
450
+ const app = createServer();
451
+
452
+ // Your routes
453
+ app.get('/', (c) => c.json({
454
+ message: 'Hello from Flight!',
455
+ docs: 'https://flight.dev/docs/quickstart-http'
456
+ }));
457
+
458
+ app.get('/health', (c) => c.json({
459
+ status: 'ok',
460
+ timestamp: Date.now()
461
+ }));
462
+
463
+ // Start server
464
+ const port = process.env.PORT || 3000;
465
+ console.log(\`Server running at http://localhost:\${port}\`);
466
+
467
+ export default { port, fetch: app.fetch };
468
+ `;
469
+ writeFileSync(join(projectPath, "server.js"), serverCode);
470
+ if (options.git) {
471
+ writeFileSync(
472
+ join(projectPath, ".gitignore"),
473
+ "node_modules\ndist\n.env\n.env.local\n"
474
+ );
475
+ try {
476
+ execSync("git init", { cwd: projectPath, stdio: "ignore" });
477
+ } catch {
445
478
  }
446
- body = Buffer.concat(chunks);
447
479
  }
448
- const headers = new Headers();
449
- for (const [key, value] of Object.entries(req.headers)) {
450
- if (value) {
451
- if (Array.isArray(value)) {
452
- for (const v of value) {
453
- headers.append(key, v);
454
- }
455
- } else {
456
- headers.set(key, value);
457
- }
458
- }
459
- }
460
- return new Request(url.toString(), {
461
- method: req.method || "GET",
462
- headers,
463
- body
464
- });
465
- }
466
- function matchPath(pattern, path) {
467
- const cleanPath = path.split("?")[0];
468
- const regexPattern = pattern.replace(/:\w+/g, "[^/]+").replace(/\*/g, ".*");
469
- const regex = new RegExp(`^${regexPattern}$`);
470
- return regex.test(cleanPath || "/");
471
- }
472
- function getNetworkAddress() {
473
- try {
474
- const { networkInterfaces } = __require("os");
475
- const nets = networkInterfaces();
476
- for (const name of Object.keys(nets)) {
477
- for (const net of nets[name]) {
478
- if (net.family === "IPv4" && !net.internal) {
479
- return net.address;
480
- }
481
- }
480
+ if (options.install) {
481
+ console.log(pc.dim("\\nInstalling dependencies...\\n"));
482
+ try {
483
+ const pm = detectPackageManager();
484
+ execSync(`${pm} install`, { cwd: projectPath, stdio: "inherit" });
485
+ } catch {
482
486
  }
483
- } catch {
484
487
  }
485
- return "0.0.0.0";
486
- }
488
+ console.log(`
489
+ ${pc.green("[OK] Minimal project created!")}
487
490
 
488
- // src/commands/build.ts
489
- import { resolve as resolve3 } from "path";
490
- import { existsSync as existsSync3 } from "fs";
491
- import pc3 from "picocolors";
492
- import { loadConfig as loadConfig2 } from "@flight-framework/core/config";
493
- function findEntryServer(root, srcDir) {
494
- const tsxPath = resolve3(root, srcDir, "entry-server.tsx");
495
- const tsPath = resolve3(root, srcDir, "entry-server.ts");
496
- if (existsSync3(tsxPath)) return tsxPath;
497
- if (existsSync3(tsPath)) return tsPath;
498
- return tsxPath;
499
- }
500
- async function buildCommand(options) {
501
- const startTime = Date.now();
502
- printLogo();
503
- console.log(pc3.cyan("\n[*] Building Flight project for production...\n"));
504
- try {
505
- const root = resolve3(process.cwd());
506
- const config = await loadConfig2(root);
507
- const outDir = options.outDir ?? config.build.outDir;
508
- const sourcemap = options.sourcemap ?? config.build.sourcemap;
509
- const minify = options.minify ?? config.build.minify;
510
- const { build } = await import("vite");
511
- console.log(pc3.dim(`Output directory: ${outDir}`));
512
- console.log(pc3.dim(`Sourcemaps: ${sourcemap ? "enabled" : "disabled"}`));
513
- console.log(pc3.dim(`Minification: ${minify ? "enabled" : "disabled"}
514
- `));
515
- console.log(pc3.cyan("Building client..."));
516
- await build({
517
- root,
518
- mode: "production",
519
- build: {
520
- outDir: `${outDir}/client`,
521
- sourcemap,
522
- minify: minify ? "esbuild" : false,
523
- rollupOptions: {
524
- input: resolve3(root, "index.html")
525
- }
526
- }
527
- });
528
- console.log(pc3.green("\u2713") + " Client build complete");
529
- if (config.rendering.default !== "csr") {
530
- console.log(pc3.cyan("\nBuilding server..."));
531
- await build({
532
- root,
533
- mode: "production",
534
- build: {
535
- outDir: `${outDir}/server`,
536
- sourcemap,
537
- minify: minify ? "esbuild" : false,
538
- ssr: true,
539
- rollupOptions: {
540
- input: findEntryServer(root, config.build.srcDir)
541
- }
542
- }
543
- });
544
- console.log(pc3.green("\u2713") + " Server build complete");
545
- }
546
- if (config.adapter) {
547
- console.log(pc3.cyan(`
548
- Running ${config.adapter.name} adapter...`));
549
- console.log(pc3.green("\u2713") + ` ${config.adapter.name} adapter complete`);
550
- }
551
- const elapsed = Date.now() - startTime;
552
- const elapsedSeconds = (elapsed / 1e3).toFixed(2);
553
- console.log(`
554
- ${pc3.green("[OK] Build complete!")} ${pc3.dim(`(${elapsedSeconds}s)`)}
491
+ ${pc.cyan("Next steps:")}
555
492
 
556
- ${pc3.cyan("Output:")} ${resolve3(root, outDir)}
493
+ ${pc.dim("$")} cd ${projectName}
494
+ ${pc.dim("$")} ${options.install ? "" : "npm install && "}npm run dev
557
495
 
558
- ${pc3.dim("To preview the build:")}
559
- ${pc3.dim("$")} flight preview
496
+ ${pc.dim("Add more features as needed:")}
560
497
 
561
- ${pc3.dim("To deploy:")}
562
- ${pc3.dim("\u2022")} Upload ${outDir}/ to your server
563
- ${pc3.dim("\u2022")} Or use your configured adapter
564
- `);
565
- } catch (error) {
566
- console.error(pc3.red("\nBuild failed:"), error);
567
- process.exit(1);
568
- }
569
- }
498
+ ${pc.dim("# File-based routing")}
499
+ npm install @flight-framework/core
570
500
 
571
- // src/commands/preview.ts
572
- import { resolve as resolve4 } from "path";
573
- import pc4 from "picocolors";
574
- import { loadConfig as loadConfig3 } from "@flight-framework/core/config";
575
- async function previewCommand(options) {
576
- printLogo();
577
- console.log(pc4.cyan("\n\u2708\uFE0F Starting Flight preview server...\n"));
578
- try {
579
- const root = resolve4(process.cwd());
580
- const config = await loadConfig3(root);
581
- const port = options.port ? parseInt(options.port, 10) : config.dev.port + 1;
582
- const host = options.host ?? config.dev.host;
583
- const open = options.open ?? false;
584
- const { preview } = await import("vite");
585
- const server = await preview({
586
- root,
587
- preview: {
588
- port,
589
- host: host === true ? "0.0.0.0" : host,
590
- open
591
- },
592
- build: {
593
- outDir: config.build.outDir
594
- }
595
- });
596
- console.log(`
597
- ${pc4.green("\u2713")} Flight preview server ready
501
+ ${pc.dim("# Database")}
502
+ npm install @flight-framework/db pg
598
503
 
599
- ${pc4.cyan("\u279C")} Local: ${pc4.cyan(`http://localhost:${port}/`)}
600
- ${host === true || host === "0.0.0.0" ? ` ${pc4.cyan("\u279C")} Network: ${pc4.cyan(`http://0.0.0.0:${port}/`)}` : ""}
504
+ ${pc.dim("# Caching")}
505
+ npm install @flight-framework/cache
601
506
 
602
- ${pc4.dim("This is a preview of your production build.")}
603
- ${pc4.dim("For development, use")} ${pc4.bold("flight dev")}
507
+ ${pc.cyan("Happy flying!")}
604
508
  `);
605
- server.printUrls();
606
- } catch (error) {
607
- console.error(pc4.red("\nFailed to start preview server:"), error);
608
- process.exit(1);
609
- }
610
509
  }
611
510
 
612
- // src/commands/routes-generate.ts
613
- import { resolve as resolve5 } from "path";
511
+ // src/commands/dev.ts
512
+ import { resolve as resolve3, join as join3 } from "path";
513
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
514
+ import pc2 from "picocolors";
515
+ import { loadConfig } from "@flight-framework/core/config";
614
516
 
615
- // src/generators/routes.ts
616
- import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
617
- import { join as join3, dirname as dirname2 } from "path";
618
- function filePathToUrlPath(filePath) {
619
- let urlPath = filePath.replace(/\.(page|route)\.(tsx?|jsx?)$/, "").replace(/\.(get|post|put|patch|delete|options|head)$/, "").replace(/\/index$/, "").replace(/\/?\([^)]+\)/g, "").replace(/\[\.\.\.(\w+)\]/g, "*$1").replace(/\[(\w+)\]/g, ":$1");
620
- if (!urlPath.startsWith("/")) {
621
- urlPath = "/" + urlPath;
517
+ // src/generators/typegen.ts
518
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, watch } from "fs";
519
+ import { join as join2 } from "path";
520
+ function generateHeader(command) {
521
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
522
+ return `/**
523
+ * Auto-generated by Flight CLI
524
+ * Do not edit manually - changes will be overwritten
525
+ *
526
+ * Command: ${command}
527
+ * Generated: ${timestamp}
528
+ */`;
529
+ }
530
+ function extractParams(routePath) {
531
+ const params = [];
532
+ const dynamicMatches = routePath.match(/:(\w+)/g);
533
+ if (dynamicMatches) {
534
+ params.push(...dynamicMatches.map((m) => m.slice(1)));
622
535
  }
623
- if (urlPath === "" || urlPath === "/") {
624
- urlPath = "/";
536
+ const catchAllMatches = routePath.match(/\*(\w+)/g);
537
+ if (catchAllMatches) {
538
+ params.push(...catchAllMatches.map((m) => m.slice(1)));
625
539
  }
626
- urlPath = urlPath.replace(/\/+/g, "/");
627
- return urlPath;
628
- }
629
- function extractHttpMethod(filename) {
630
- const match = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
631
- return match ? match[1].toUpperCase() : void 0;
632
- }
633
- function isLayoutFile(filename) {
634
- return filename.startsWith("_layout.");
635
- }
636
- function isLoadingFile(filename) {
637
- return filename.startsWith("_loading.");
638
- }
639
- function isErrorFile(filename) {
640
- return filename.startsWith("_error.");
641
- }
642
- function isNotFoundFile(filename) {
643
- return filename.startsWith("_not-found.");
644
- }
645
- function isRouteFile(filename) {
646
- return /\.(page|route)\.(tsx?|jsx?)$/.test(filename);
647
- }
648
- function isApiRouteFile(filename) {
649
- return /\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/.test(filename);
540
+ return params;
650
541
  }
651
- function hasDynamicSegments(path) {
652
- return path.includes("[") && path.includes("]");
653
- }
654
- function scanDirectory(dir, basePath = "", results = []) {
655
- if (!existsSync4(dir)) {
656
- return results;
657
- }
658
- const entries = readdirSync2(dir);
659
- for (const entry of entries) {
660
- const fullPath = join3(dir, entry);
661
- const relativePath = join3(basePath, entry);
662
- const stat = statSync2(fullPath);
663
- if (stat.isDirectory()) {
664
- if (entry.startsWith(".") || entry === "node_modules") {
665
- continue;
666
- }
667
- scanDirectory(fullPath, relativePath, results);
668
- } else if (stat.isFile()) {
669
- const routePath = filePathToUrlPath(dirname2(relativePath));
670
- const normalizedFilePath = relativePath.replace(/\\/g, "/");
671
- const isDynamic = hasDynamicSegments(relativePath);
672
- if (isLayoutFile(entry)) {
673
- results.push({
674
- path: routePath,
675
- filePath: normalizedFilePath,
676
- isLayout: true,
677
- isLoading: false,
678
- isError: false,
679
- isNotFound: false,
680
- isDynamic,
681
- isApiRoute: false
682
- });
683
- } else if (isLoadingFile(entry)) {
684
- results.push({
685
- path: routePath,
686
- filePath: normalizedFilePath,
687
- isLayout: false,
688
- isLoading: true,
689
- isError: false,
690
- isNotFound: false,
691
- isDynamic,
692
- isApiRoute: false
693
- });
694
- } else if (isErrorFile(entry)) {
695
- results.push({
696
- path: routePath,
697
- filePath: normalizedFilePath,
698
- isLayout: false,
699
- isLoading: false,
700
- isError: true,
701
- isNotFound: false,
702
- isDynamic,
703
- isApiRoute: false
704
- });
705
- } else if (isNotFoundFile(entry)) {
706
- results.push({
707
- path: routePath,
708
- filePath: normalizedFilePath,
709
- isLayout: false,
710
- isLoading: false,
711
- isError: false,
712
- isNotFound: true,
713
- isDynamic,
714
- isApiRoute: false
715
- });
716
- } else if (isApiRouteFile(entry)) {
717
- const method = extractHttpMethod(entry);
718
- results.push({
719
- path: filePathToUrlPath(relativePath),
720
- filePath: normalizedFilePath,
721
- isLayout: false,
722
- isLoading: false,
723
- isError: false,
724
- isNotFound: false,
725
- isDynamic,
726
- isApiRoute: true,
727
- httpMethod: method
728
- });
729
- } else if (isRouteFile(entry)) {
730
- results.push({
731
- path: filePathToUrlPath(relativePath),
732
- filePath: normalizedFilePath,
733
- isLayout: false,
734
- isLoading: false,
735
- isError: false,
736
- isNotFound: false,
737
- isDynamic,
738
- isApiRoute: false
739
- });
740
- }
741
- }
742
- }
743
- return results;
744
- }
745
- function sortRoutes(routes) {
746
- return routes.sort((a, b) => {
747
- if (!a.isDynamic && b.isDynamic) return -1;
748
- if (a.isDynamic && !b.isDynamic) return 1;
749
- const aSegments = a.path.split("/").length;
750
- const bSegments = b.path.split("/").length;
751
- if (aSegments !== bSegments) return aSegments - bSegments;
752
- return a.path.localeCompare(b.path);
753
- });
754
- }
755
- function generateRouteManifest(routesDir) {
756
- const allRoutes = scanDirectory(routesDir);
757
- const isPageRoute = (r) => !r.isLayout && !r.isLoading && !r.isError && !r.isNotFound && !r.isApiRoute;
758
- const routes = sortRoutes(allRoutes.filter(isPageRoute));
759
- const layouts = allRoutes.filter((r) => r.isLayout);
760
- const loadingStates = allRoutes.filter((r) => r.isLoading);
761
- const errorBoundaries = allRoutes.filter((r) => r.isError);
762
- const notFoundPages = allRoutes.filter((r) => r.isNotFound);
763
- const apiRoutes = sortRoutes(allRoutes.filter((r) => r.isApiRoute));
764
- return {
765
- routes,
766
- layouts,
767
- loadingStates,
768
- errorBoundaries,
769
- notFoundPages,
770
- apiRoutes,
771
- generated: (/* @__PURE__ */ new Date()).toISOString()
772
- };
773
- }
774
- function generateRoutesFile(manifest, outputDir) {
775
- if (!existsSync4(outputDir)) {
776
- mkdirSync2(outputDir, { recursive: true });
777
- }
778
- const routesContent = `/**
779
- * Auto-generated by Flight CLI
780
- * Do not edit manually
781
- * Generated: ${manifest.generated}
782
- */
783
-
784
- import type { RouteDefinition } from '@flight-framework/router';
785
-
786
- // Page Routes
787
- export const routes: RouteDefinition[] = [
788
- ${manifest.routes.map((r) => ` {
789
- path: '${r.path}',
790
- component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
791
- },`).join("\n")}
792
- ];
793
-
794
- // Layout Components
795
- export const layouts = [
796
- ${manifest.layouts.map((r) => ` {
797
- path: '${r.path}',
798
- component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
799
- },`).join("\n")}
800
- ];
801
-
802
- // Loading State Components
803
- export const loadingStates = [
804
- ${manifest.loadingStates.map((r) => ` {
805
- path: '${r.path}',
806
- component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
807
- },`).join("\n")}
808
- ];
809
-
810
- // Error Boundary Components
811
- export const errorBoundaries = [
812
- ${manifest.errorBoundaries.map((r) => ` {
813
- path: '${r.path}',
814
- component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
815
- },`).join("\n")}
816
- ];
817
-
818
- // Not Found Page Components
819
- export const notFoundPages = [
820
- ${manifest.notFoundPages.map((r) => ` {
821
- path: '${r.path}',
822
- component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
823
- },`).join("\n")}
824
- ];
825
-
826
- // API Routes
827
- export const apiRoutes = [
828
- ${manifest.apiRoutes.map((r) => ` {
829
- path: '${r.path}',
830
- method: '${r.httpMethod}',
831
- handler: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
832
- },`).join("\n")}
833
- ];
834
-
835
- // Type-safe route paths
836
- export type AppRoutes = ${manifest.routes.length > 0 ? manifest.routes.map((r) => `'${r.path}'`).join(" | ") : "never"};
837
-
838
- // Type-safe API route paths
839
- export type ApiRoutes = ${manifest.apiRoutes.length > 0 ? manifest.apiRoutes.map((r) => `'${r.path}'`).join(" | ") : "never"};
840
- `;
841
- writeFileSync2(join3(outputDir, "routes.ts"), routesContent, "utf-8");
842
- const typesContent = `/**
843
- * Auto-generated route types
844
- * Generated: ${manifest.generated}
845
- */
846
-
847
- import type { RouteParams } from '@flight-framework/router';
848
-
849
- // Extract params from route patterns
850
- ${manifest.routes.filter((r) => r.isDynamic).map((r) => {
851
- const paramMatches = r.path.match(/:(\w+)/g) || [];
852
- const params = paramMatches.map((p) => p.slice(1));
853
- const typeName = r.path.replace(/[/:]/g, "_").replace(/^_/, "").replace(/_$/, "") || "Root";
854
- return `export type ${typeName}Params = { ${params.map((p) => `${p}: string`).join("; ")} };`;
855
- }).join("\n")}
856
- `;
857
- writeFileSync2(join3(outputDir, "types.ts"), typesContent, "utf-8");
858
- }
859
- async function generateRoutes(options) {
860
- const { routesDir, outputDir } = options;
861
- console.log(`Scanning routes in: ${routesDir}`);
862
- const manifest = generateRouteManifest(routesDir);
863
- const stats = [
864
- `${manifest.routes.length} pages`,
865
- `${manifest.layouts.length} layouts`,
866
- `${manifest.loadingStates.length} loading states`,
867
- `${manifest.errorBoundaries.length} error boundaries`,
868
- `${manifest.notFoundPages.length} not-found pages`,
869
- `${manifest.apiRoutes.length} API routes`
870
- ].join(", ");
871
- console.log(`Found: ${stats}`);
872
- generateRoutesFile(manifest, outputDir);
873
- console.log(`Generated route manifest in: ${outputDir}`);
874
- return manifest;
875
- }
876
-
877
- // src/commands/routes-generate.ts
878
- async function routesGenerateCommand(options = {}) {
879
- const cwd = process.cwd();
880
- const routesDir = options.routesDir ? resolve5(cwd, options.routesDir) : resolve5(cwd, "src/routes");
881
- const outputDir = options.outputDir ? resolve5(cwd, options.outputDir) : resolve5(cwd, "src/.flight");
882
- try {
883
- const manifest = await generateRoutes({
884
- routesDir,
885
- outputDir,
886
- watch: options.watch
887
- });
888
- console.log("\nRoute manifest generated successfully!");
889
- console.log(` Pages: ${manifest.routes.length}`);
890
- console.log(` API Routes: ${manifest.apiRoutes.length}`);
891
- console.log(` Layouts: ${manifest.layouts.length}`);
892
- } catch (error) {
893
- console.error("Failed to generate routes:", error);
894
- process.exit(1);
895
- }
896
- }
897
-
898
- // src/commands/types-generate.ts
899
- import { resolve as resolve7 } from "path";
900
-
901
- // src/generators/typegen.ts
902
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3, watch } from "fs";
903
- import { join as join4 } from "path";
904
- function generateHeader(command) {
905
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
906
- return `/**
907
- * Auto-generated by Flight CLI
908
- * Do not edit manually - changes will be overwritten
909
- *
910
- * Command: ${command}
911
- * Generated: ${timestamp}
912
- */`;
913
- }
914
- function extractParams(routePath) {
915
- const params = [];
916
- const dynamicMatches = routePath.match(/:(\w+)/g);
917
- if (dynamicMatches) {
918
- params.push(...dynamicMatches.map((m) => m.slice(1)));
919
- }
920
- const catchAllMatches = routePath.match(/\*(\w+)/g);
921
- if (catchAllMatches) {
922
- params.push(...catchAllMatches.map((m) => m.slice(1)));
923
- }
924
- return params;
925
- }
926
- function isDynamicRoute(routePath) {
927
- return routePath.includes(":") || routePath.includes("*");
542
+ function isDynamicRoute(routePath) {
543
+ return routePath.includes(":") || routePath.includes("*");
928
544
  }
929
545
  function generateRouteTypes(routes) {
930
546
  const pageRoutes = routes.filter((r) => !r.isApiRoute);
@@ -1071,11 +687,11 @@ function loadEnvFiles(projectRoot) {
1071
687
  const merged = { server: [], client: [] };
1072
688
  const seenKeys = /* @__PURE__ */ new Set();
1073
689
  for (const envFile of envFiles) {
1074
- const envPath = join4(projectRoot, envFile);
1075
- if (!existsSync5(envPath)) {
690
+ const envPath = join2(projectRoot, envFile);
691
+ if (!existsSync2(envPath)) {
1076
692
  continue;
1077
693
  }
1078
- const content = readFileSync3(envPath, "utf-8");
694
+ const content = readFileSync2(envPath, "utf-8");
1079
695
  const parsed = parseEnvFile(content);
1080
696
  for (const envVar of parsed.server) {
1081
697
  if (!seenKeys.has(envVar.key)) {
@@ -1097,161 +713,859 @@ function generateEnvTypes(env) {
1097
713
  const clientVars = env.client.sort((a, b) => a.key.localeCompare(b.key)).map((v) => ` ${v.key}${v.optional ? "?" : ""}: string;`).join("\n");
1098
714
  return `${generateHeader("flight types:generate --env")}
1099
715
 
1100
- // ============================================================================
1101
- // Server-side Environment Variables
1102
- // ============================================================================
716
+ // ============================================================================
717
+ // Server-side Environment Variables
718
+ // ============================================================================
719
+
720
+ /**
721
+ * Server-side environment variables accessible via process.env
722
+ * These are NOT exposed to the client.
723
+ */
724
+ declare namespace NodeJS {
725
+ interface ProcessEnv {
726
+ ${serverVars || " // No server environment variables defined"}
727
+ }
728
+ }
729
+
730
+ // ============================================================================
731
+ // Client-side Environment Variables
732
+ // ============================================================================
733
+
734
+ /**
735
+ * Client-side environment variables accessible via import.meta.env
736
+ * Only variables with PUBLIC_ prefix are included.
737
+ */
738
+ interface ImportMetaEnv {
739
+ ${clientVars || " // No client environment variables defined"}
740
+ }
741
+
742
+ interface ImportMeta {
743
+ readonly env: ImportMetaEnv;
744
+ }
745
+ `;
746
+ }
747
+ function scanRoutesForTypes(routesDir) {
748
+ if (!existsSync2(routesDir)) {
749
+ return [];
750
+ }
751
+ const routes = [];
752
+ scanDirectoryRecursive(routesDir, "", routes);
753
+ return routes;
754
+ }
755
+ function scanDirectoryRecursive(dir, basePath, results) {
756
+ const entries = readdirSync2(dir, { withFileTypes: true });
757
+ for (const entry of entries) {
758
+ const fullPath = join2(dir, entry.name);
759
+ const relativePath = join2(basePath, entry.name);
760
+ if (entry.isDirectory()) {
761
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
762
+ continue;
763
+ }
764
+ scanDirectoryRecursive(fullPath, relativePath, results);
765
+ } else if (entry.isFile()) {
766
+ const route = parseRouteFile(entry.name, relativePath);
767
+ if (route) {
768
+ results.push(route);
769
+ }
770
+ }
771
+ }
772
+ }
773
+ function parseRouteFile(filename, relativePath) {
774
+ if (/\.(page|route)\.(tsx?|jsx?)$/.test(filename)) {
775
+ const urlPath = filePathToUrlPath(relativePath);
776
+ return {
777
+ path: urlPath,
778
+ filePath: relativePath.replace(/\\/g, "/"),
779
+ isDynamic: isDynamicRoute(urlPath),
780
+ isApiRoute: false
781
+ };
782
+ }
783
+ const apiMatch = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
784
+ if (apiMatch) {
785
+ const urlPath = filePathToUrlPath(relativePath);
786
+ return {
787
+ path: urlPath,
788
+ filePath: relativePath.replace(/\\/g, "/"),
789
+ isDynamic: isDynamicRoute(urlPath),
790
+ isApiRoute: true,
791
+ httpMethod: apiMatch[1].toUpperCase()
792
+ };
793
+ }
794
+ return null;
795
+ }
796
+ function filePathToUrlPath(filePath) {
797
+ let urlPath = filePath.replace(/\\/g, "/").replace(/\.(page|route)\.(tsx?|jsx?)$/, "").replace(/\.(get|post|put|patch|delete|options|head)$/i, "").replace(/\/index$/, "").replace(/^index$/, "").replace(/\/?\\?\([^)]+\\?\)/g, "").replace(/\[\.\.\.(\w+)\]/g, "*$1").replace(/\[(\w+)\]/g, ":$1");
798
+ if (!urlPath.startsWith("/")) {
799
+ urlPath = "/" + urlPath;
800
+ }
801
+ urlPath = urlPath.replace(/\/+/g, "/");
802
+ return urlPath || "/";
803
+ }
804
+ async function generateTypes(options) {
805
+ const {
806
+ routesDir,
807
+ outputDir,
808
+ includeRoutes = true,
809
+ includeEnv = false,
810
+ projectRoot = process.cwd()
811
+ } = options;
812
+ const result = {
813
+ routeTypes: "",
814
+ envTypes: "",
815
+ filesWritten: [],
816
+ routeCount: 0,
817
+ envVarCount: 0
818
+ };
819
+ if (!existsSync2(outputDir)) {
820
+ mkdirSync2(outputDir, { recursive: true });
821
+ }
822
+ if (includeRoutes) {
823
+ const routes = scanRoutesForTypes(routesDir);
824
+ result.routeCount = routes.length;
825
+ result.routeTypes = generateRouteTypes(routes);
826
+ const routeTypesPath = join2(outputDir, "routes.d.ts");
827
+ writeFileSync2(routeTypesPath, result.routeTypes, "utf-8");
828
+ result.filesWritten.push(routeTypesPath);
829
+ console.log(`Generated route types: ${routes.length} routes`);
830
+ }
831
+ if (includeEnv) {
832
+ const env = loadEnvFiles(projectRoot);
833
+ result.envVarCount = env.server.length + env.client.length;
834
+ result.envTypes = generateEnvTypes(env);
835
+ const envTypesPath = join2(outputDir, "env.d.ts");
836
+ writeFileSync2(envTypesPath, result.envTypes, "utf-8");
837
+ result.filesWritten.push(envTypesPath);
838
+ console.log(`Generated env types: ${env.server.length} server, ${env.client.length} client`);
839
+ }
840
+ return result;
841
+ }
842
+ function watchAndGenerate(options) {
843
+ const { routesDir, debounce = 100, onRegenerate } = options;
844
+ let timeout = null;
845
+ const regenerate = async () => {
846
+ try {
847
+ const result = await generateTypes(options);
848
+ onRegenerate?.(result);
849
+ } catch (error) {
850
+ console.error("Type generation failed:", error.message);
851
+ }
852
+ };
853
+ const debouncedRegenerate = () => {
854
+ if (timeout) clearTimeout(timeout);
855
+ timeout = setTimeout(regenerate, debounce);
856
+ };
857
+ regenerate();
858
+ const watcher = watch(routesDir, { recursive: true }, (eventType, filename) => {
859
+ if (filename && /\.(tsx?|jsx?)$/.test(filename)) {
860
+ console.log(`Route file changed: ${filename}`);
861
+ debouncedRegenerate();
862
+ }
863
+ });
864
+ return () => {
865
+ watcher.close();
866
+ if (timeout) clearTimeout(timeout);
867
+ };
868
+ }
869
+
870
+ // src/commands/dev.ts
871
+ async function devCommand(options) {
872
+ const startTime = Date.now();
873
+ printLogo();
874
+ console.log(pc2.cyan("\n Starting Flight development server...\n"));
875
+ try {
876
+ const root = resolve3(process.cwd());
877
+ const config = await loadConfig(root);
878
+ const routesDir = join3(root, "src", "routes");
879
+ const outputDir = join3(root, "src", ".flight");
880
+ if (existsSync3(routesDir)) {
881
+ try {
882
+ const result = await generateTypes({
883
+ routesDir,
884
+ outputDir,
885
+ includeRoutes: true,
886
+ includeEnv: false,
887
+ projectRoot: root
888
+ });
889
+ console.log(pc2.green(` Route types generated: ${result.routeCount} routes`));
890
+ const cleanup = watchAndGenerate({
891
+ routesDir,
892
+ outputDir,
893
+ includeRoutes: true,
894
+ includeEnv: false,
895
+ projectRoot: root,
896
+ onRegenerate: (watchResult) => {
897
+ console.log(pc2.blue(` Route types updated: ${watchResult.routeCount} routes`));
898
+ }
899
+ });
900
+ process.on("SIGINT", () => cleanup());
901
+ process.on("SIGTERM", () => cleanup());
902
+ } catch (typeError) {
903
+ console.log(pc2.yellow(` Route type generation skipped: ${typeError}`));
904
+ }
905
+ }
906
+ const port = options.port ? parseInt(options.port, 10) : config.dev.port;
907
+ const host = options.host ?? config.dev.host;
908
+ const open = options.open ?? config.dev.open;
909
+ const ssrEnabled = options.ssr ?? config.rendering?.default === "ssr";
910
+ const entryServerPath = join3(root, "src", "entry-server.tsx");
911
+ const hasSSREntry = existsSync3(entryServerPath);
912
+ const { createServer: createViteServer } = await import("vite");
913
+ let flightHttpAvailable = false;
914
+ let flightRouterAvailable = false;
915
+ try {
916
+ await import("@flight-framework/http");
917
+ flightHttpAvailable = true;
918
+ } catch {
919
+ }
920
+ try {
921
+ await import("@flight-framework/core/file-router");
922
+ flightRouterAvailable = true;
923
+ } catch {
924
+ }
925
+ const vite = await createViteServer({
926
+ root,
927
+ mode: "development",
928
+ server: {
929
+ middlewareMode: ssrEnabled && hasSSREntry,
930
+ port: ssrEnabled && hasSSREntry ? void 0 : port,
931
+ host: host === true ? "0.0.0.0" : host,
932
+ open: ssrEnabled && hasSSREntry ? false : open,
933
+ https: options.https ? {} : void 0
934
+ },
935
+ appType: ssrEnabled && hasSSREntry ? "custom" : "spa",
936
+ plugins: [
937
+ // Add Flight plugin when @flight-framework/http is available
938
+ flightHttpAvailable ? flightDevPlugin(root) : null
939
+ ].filter(Boolean)
940
+ });
941
+ if (ssrEnabled && hasSSREntry) {
942
+ await startSSRServer(vite, root, port, host);
943
+ } else {
944
+ await vite.listen();
945
+ }
946
+ const elapsed = Date.now() - startTime;
947
+ const isSSR = ssrEnabled && hasSSREntry;
948
+ console.log(`
949
+ ${pc2.green("\u2713")} Flight dev server ready in ${pc2.bold(elapsed + "ms")}
950
+
951
+ ${pc2.cyan("\u279C")} Local: ${pc2.cyan(`http://localhost:${port}/`)}
952
+ ${host === true || host === "0.0.0.0" ? ` ${pc2.cyan("\u279C")} Network: ${pc2.cyan(`http://${getNetworkAddress()}:${port}/`)}` : ""}
953
+
954
+ ${isSSR ? pc2.green("\u2713") : pc2.yellow("\u25CB")} SSR ${isSSR ? "enabled (streaming)" : "disabled (CSR mode)"}
955
+ ${flightHttpAvailable ? pc2.green("\u2713") : pc2.yellow("\u25CB")} @flight-framework/http ${flightHttpAvailable ? "enabled" : "not installed"}
956
+ ${flightRouterAvailable ? pc2.green("\u2713") : pc2.yellow("\u25CB")} File-based routing ${flightRouterAvailable ? "enabled" : "not available"}
957
+
958
+ ${pc2.dim("press")} ${pc2.bold("h")} ${pc2.dim("to show help")}
959
+ `);
960
+ if (!isSSR) {
961
+ vite.bindCLIShortcuts({ print: true });
962
+ }
963
+ } catch (error) {
964
+ console.error(pc2.red("\nFailed to start dev server:"), error);
965
+ process.exit(1);
966
+ }
967
+ }
968
+ async function startSSRServer(vite, root, port, host) {
969
+ const { createServer: createHttpServer } = await import("http");
970
+ let pageRouter = null;
971
+ try {
972
+ const { createFileRouter } = await import("@flight-framework/core/file-router");
973
+ const moduleLoader = async (filePath) => {
974
+ const normalizedFilePath = filePath.replace(/\\/g, "/");
975
+ const normalizedRoot = root.replace(/\\/g, "/");
976
+ const relativePath = normalizedFilePath.replace(normalizedRoot, "");
977
+ return vite.ssrLoadModule(relativePath);
978
+ };
979
+ pageRouter = await createFileRouter({
980
+ directory: join3(root, "src", "routes"),
981
+ extensions: [".tsx", ".ts", ".jsx", ".js"],
982
+ moduleLoader
983
+ // Use Vite's ssrLoadModule
984
+ });
985
+ console.log(pc2.green(` \u2713 Page router loaded: ${pageRouter.routes.filter((r) => r.type === "page").length} pages`));
986
+ } catch {
987
+ }
988
+ const server = createHttpServer(async (req, res) => {
989
+ const url = req.url || "/";
990
+ const pathname = url.split("?")[0];
991
+ const isStaticAsset = url.startsWith("/@") || url.startsWith("/node_modules") || url.startsWith("/src") && !url.includes("entry-server") || // Static file extensions
992
+ pathname.endsWith(".css") || pathname.endsWith(".js") || pathname.endsWith(".ts") || pathname.endsWith(".tsx") || pathname.endsWith(".svg") || pathname.endsWith(".png") || pathname.endsWith(".jpg") || pathname.endsWith(".jpeg") || pathname.endsWith(".gif") || pathname.endsWith(".ico") || pathname.endsWith(".woff") || pathname.endsWith(".woff2") || pathname.endsWith(".ttf") || pathname.endsWith(".eot") || pathname.endsWith(".json") || pathname.endsWith(".webp") || pathname.endsWith(".mp4") || pathname.endsWith(".webm");
993
+ if (isStaticAsset) {
994
+ vite.middlewares(req, res);
995
+ return;
996
+ }
997
+ if (pathname.startsWith("/api/")) {
998
+ vite.middlewares(req, res);
999
+ return;
1000
+ }
1001
+ try {
1002
+ let template = readFileSync3(
1003
+ join3(root, "index.html"),
1004
+ "utf-8"
1005
+ );
1006
+ template = await vite.transformIndexHtml(url, template);
1007
+ let appHtml = "";
1008
+ if (pageRouter) {
1009
+ const pageRoute = pageRouter.routes.find(
1010
+ (r) => r.type === "page" && matchPath(r.path, pathname)
1011
+ );
1012
+ if (pageRoute && pageRoute.component) {
1013
+ const normalizedFilePath = pageRoute.filePath.replace(/\\/g, "/");
1014
+ const normalizedRoot = root.replace(/\\/g, "/");
1015
+ const relativePath = normalizedFilePath.replace(normalizedRoot, "");
1016
+ const mod = await vite.ssrLoadModule(relativePath);
1017
+ const Component = mod.default;
1018
+ const { render } = await vite.ssrLoadModule("/src/entry-server.tsx");
1019
+ if (typeof render === "function" && Component) {
1020
+ appHtml = await render(url, { Component });
1021
+ }
1022
+ }
1023
+ }
1024
+ if (!appHtml) {
1025
+ const { render } = await vite.ssrLoadModule("/src/entry-server.tsx");
1026
+ if (typeof render === "function") {
1027
+ appHtml = await render(url);
1028
+ }
1029
+ }
1030
+ if (appHtml) {
1031
+ const html = template.replace("<!--ssr-outlet-->", appHtml);
1032
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1033
+ res.end(html);
1034
+ } else {
1035
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1036
+ res.end(template);
1037
+ }
1038
+ } catch (e) {
1039
+ vite.ssrFixStacktrace(e);
1040
+ console.error(pc2.red("[SSR Error]"), e.stack);
1041
+ if (!res.headersSent) {
1042
+ res.writeHead(500, { "Content-Type": "text/plain" });
1043
+ res.end(`SSR Error: ${e.message}
1044
+
1045
+ ${e.stack}`);
1046
+ }
1047
+ }
1048
+ });
1049
+ const listenHost = host === true ? "0.0.0.0" : host || "localhost";
1050
+ server.listen(port, listenHost, () => {
1051
+ console.log(pc2.green(` \u2713 SSR server listening`));
1052
+ });
1053
+ }
1054
+ function flightDevPlugin(root) {
1055
+ return {
1056
+ name: "flight:dev",
1057
+ configureServer(server) {
1058
+ server.middlewares.use(async (req, res, next) => {
1059
+ const url = req.url || "/";
1060
+ if (url.startsWith("/__flight_action/")) {
1061
+ try {
1062
+ const { handleActionRequest } = await import("@flight-framework/core");
1063
+ const webRequest = await nodeToWebRequest(req);
1064
+ const response = await handleActionRequest(webRequest);
1065
+ res.statusCode = response.status;
1066
+ response.headers.forEach((value, key) => {
1067
+ res.setHeader(key, value);
1068
+ });
1069
+ const body = await response.text();
1070
+ res.end(body);
1071
+ } catch (error) {
1072
+ console.error("[Flight] Action error:", error);
1073
+ res.statusCode = 500;
1074
+ res.end(JSON.stringify({ error: "Internal server error" }));
1075
+ }
1076
+ return;
1077
+ }
1078
+ if (url.startsWith("/api/")) {
1079
+ try {
1080
+ const { createFileRouter } = await import("@flight-framework/core/file-router");
1081
+ const routesDir = join3(root, "src", "routes");
1082
+ const router = await createFileRouter({ directory: routesDir });
1083
+ const route = router.routes.find((r) => {
1084
+ return matchPath(r.path, url);
1085
+ });
1086
+ if (route && route.handler) {
1087
+ const webRequest = await nodeToWebRequest(req);
1088
+ const response = await route.handler({ req: webRequest, params: {} });
1089
+ res.statusCode = response.status;
1090
+ response.headers.forEach((value, key) => {
1091
+ res.setHeader(key, value);
1092
+ });
1093
+ const body = await response.text();
1094
+ res.end(body);
1095
+ return;
1096
+ }
1097
+ } catch (error) {
1098
+ }
1099
+ }
1100
+ next();
1101
+ });
1102
+ }
1103
+ };
1104
+ }
1105
+ async function nodeToWebRequest(req) {
1106
+ const host = req.headers.host || "localhost";
1107
+ const protocol = "http";
1108
+ const url = new URL(req.url || "/", `${protocol}://${host}`);
1109
+ let body = null;
1110
+ if (req.method !== "GET" && req.method !== "HEAD") {
1111
+ const chunks = [];
1112
+ for await (const chunk of req) {
1113
+ chunks.push(chunk);
1114
+ }
1115
+ body = Buffer.concat(chunks);
1116
+ }
1117
+ const headers = new Headers();
1118
+ for (const [key, value] of Object.entries(req.headers)) {
1119
+ if (value) {
1120
+ if (Array.isArray(value)) {
1121
+ for (const v of value) {
1122
+ headers.append(key, v);
1123
+ }
1124
+ } else {
1125
+ headers.set(key, value);
1126
+ }
1127
+ }
1128
+ }
1129
+ return new Request(url.toString(), {
1130
+ method: req.method || "GET",
1131
+ headers,
1132
+ body
1133
+ });
1134
+ }
1135
+ function matchPath(pattern, path) {
1136
+ const cleanPath = path.split("?")[0];
1137
+ const regexPattern = pattern.replace(/:\w+/g, "[^/]+").replace(/\*/g, ".*");
1138
+ const regex = new RegExp(`^${regexPattern}$`);
1139
+ return regex.test(cleanPath || "/");
1140
+ }
1141
+ function getNetworkAddress() {
1142
+ try {
1143
+ const { networkInterfaces } = __require("os");
1144
+ const nets = networkInterfaces();
1145
+ for (const name of Object.keys(nets)) {
1146
+ for (const net of nets[name]) {
1147
+ if (net.family === "IPv4" && !net.internal) {
1148
+ return net.address;
1149
+ }
1150
+ }
1151
+ }
1152
+ } catch {
1153
+ }
1154
+ return "0.0.0.0";
1155
+ }
1156
+
1157
+ // src/commands/build.ts
1158
+ import { resolve as resolve4 } from "path";
1159
+ import { existsSync as existsSync4 } from "fs";
1160
+ import pc3 from "picocolors";
1161
+ import { loadConfig as loadConfig2 } from "@flight-framework/core/config";
1162
+ function findEntryServer(root, srcDir) {
1163
+ const tsxPath = resolve4(root, srcDir, "entry-server.tsx");
1164
+ const tsPath = resolve4(root, srcDir, "entry-server.ts");
1165
+ if (existsSync4(tsxPath)) return tsxPath;
1166
+ if (existsSync4(tsPath)) return tsPath;
1167
+ return tsxPath;
1168
+ }
1169
+ async function buildCommand(options) {
1170
+ const startTime = Date.now();
1171
+ printLogo();
1172
+ console.log(pc3.cyan("\n[*] Building Flight project for production...\n"));
1173
+ try {
1174
+ const root = resolve4(process.cwd());
1175
+ const config = await loadConfig2(root);
1176
+ const outDir = options.outDir ?? config.build.outDir;
1177
+ const sourcemap = options.sourcemap ?? config.build.sourcemap;
1178
+ const minify = options.minify ?? config.build.minify;
1179
+ const { build } = await import("vite");
1180
+ console.log(pc3.dim(`Output directory: ${outDir}`));
1181
+ console.log(pc3.dim(`Sourcemaps: ${sourcemap ? "enabled" : "disabled"}`));
1182
+ console.log(pc3.dim(`Minification: ${minify ? "enabled" : "disabled"}
1183
+ `));
1184
+ console.log(pc3.cyan("Building client..."));
1185
+ await build({
1186
+ root,
1187
+ mode: "production",
1188
+ build: {
1189
+ outDir: `${outDir}/client`,
1190
+ sourcemap,
1191
+ minify: minify ? "esbuild" : false,
1192
+ rollupOptions: {
1193
+ input: resolve4(root, "index.html")
1194
+ }
1195
+ }
1196
+ });
1197
+ console.log(pc3.green("\u2713") + " Client build complete");
1198
+ if (config.rendering.default !== "csr") {
1199
+ console.log(pc3.cyan("\nBuilding server..."));
1200
+ await build({
1201
+ root,
1202
+ mode: "production",
1203
+ build: {
1204
+ outDir: `${outDir}/server`,
1205
+ sourcemap,
1206
+ minify: minify ? "esbuild" : false,
1207
+ ssr: true,
1208
+ rollupOptions: {
1209
+ input: findEntryServer(root, config.build.srcDir)
1210
+ }
1211
+ }
1212
+ });
1213
+ console.log(pc3.green("\u2713") + " Server build complete");
1214
+ }
1215
+ if (config.adapter) {
1216
+ console.log(pc3.cyan(`
1217
+ Running ${config.adapter.name} adapter...`));
1218
+ console.log(pc3.green("\u2713") + ` ${config.adapter.name} adapter complete`);
1219
+ }
1220
+ const elapsed = Date.now() - startTime;
1221
+ const elapsedSeconds = (elapsed / 1e3).toFixed(2);
1222
+ console.log(`
1223
+ ${pc3.green("[OK] Build complete!")} ${pc3.dim(`(${elapsedSeconds}s)`)}
1224
+
1225
+ ${pc3.cyan("Output:")} ${resolve4(root, outDir)}
1226
+
1227
+ ${pc3.dim("To preview the build:")}
1228
+ ${pc3.dim("$")} flight preview
1229
+
1230
+ ${pc3.dim("To deploy:")}
1231
+ ${pc3.dim("\u2022")} Upload ${outDir}/ to your server
1232
+ ${pc3.dim("\u2022")} Or use your configured adapter
1233
+ `);
1234
+ } catch (error) {
1235
+ console.error(pc3.red("\nBuild failed:"), error);
1236
+ process.exit(1);
1237
+ }
1238
+ }
1239
+
1240
+ // src/commands/preview.ts
1241
+ import { resolve as resolve5 } from "path";
1242
+ import pc4 from "picocolors";
1243
+ import { loadConfig as loadConfig3 } from "@flight-framework/core/config";
1244
+ async function previewCommand(options) {
1245
+ printLogo();
1246
+ console.log(pc4.cyan("\n\u2708\uFE0F Starting Flight preview server...\n"));
1247
+ try {
1248
+ const root = resolve5(process.cwd());
1249
+ const config = await loadConfig3(root);
1250
+ const port = options.port ? parseInt(options.port, 10) : config.dev.port + 1;
1251
+ const host = options.host ?? config.dev.host;
1252
+ const open = options.open ?? false;
1253
+ const { preview } = await import("vite");
1254
+ const server = await preview({
1255
+ root,
1256
+ preview: {
1257
+ port,
1258
+ host: host === true ? "0.0.0.0" : host,
1259
+ open
1260
+ },
1261
+ build: {
1262
+ outDir: config.build.outDir
1263
+ }
1264
+ });
1265
+ console.log(`
1266
+ ${pc4.green("\u2713")} Flight preview server ready
1267
+
1268
+ ${pc4.cyan("\u279C")} Local: ${pc4.cyan(`http://localhost:${port}/`)}
1269
+ ${host === true || host === "0.0.0.0" ? ` ${pc4.cyan("\u279C")} Network: ${pc4.cyan(`http://0.0.0.0:${port}/`)}` : ""}
1270
+
1271
+ ${pc4.dim("This is a preview of your production build.")}
1272
+ ${pc4.dim("For development, use")} ${pc4.bold("flight dev")}
1273
+ `);
1274
+ server.printUrls();
1275
+ } catch (error) {
1276
+ console.error(pc4.red("\nFailed to start preview server:"), error);
1277
+ process.exit(1);
1278
+ }
1279
+ }
1280
+
1281
+ // src/commands/routes-generate.ts
1282
+ import { resolve as resolve6 } from "path";
1283
+
1284
+ // src/generators/routes.ts
1285
+ import { readdirSync as readdirSync3, statSync as statSync2, existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1286
+ import { join as join4, dirname as dirname3 } from "path";
1287
+ function filePathToUrlPath2(filePath) {
1288
+ let urlPath = filePath.replace(/\.(page|route)\.(tsx?|jsx?)$/, "").replace(/\.(get|post|put|patch|delete|options|head)$/, "").replace(/\/index$/, "").replace(/\/?\([^)]+\)/g, "").replace(/\[\.\.\.(\w+)\]/g, "*$1").replace(/\[(\w+)\]/g, ":$1");
1289
+ if (!urlPath.startsWith("/")) {
1290
+ urlPath = "/" + urlPath;
1291
+ }
1292
+ if (urlPath === "" || urlPath === "/") {
1293
+ urlPath = "/";
1294
+ }
1295
+ urlPath = urlPath.replace(/\/+/g, "/");
1296
+ return urlPath;
1297
+ }
1298
+ function extractHttpMethod(filename) {
1299
+ const match = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
1300
+ return match ? match[1].toUpperCase() : void 0;
1301
+ }
1302
+ function isLayoutFile(filename) {
1303
+ return filename.startsWith("_layout.");
1304
+ }
1305
+ function isLoadingFile(filename) {
1306
+ return filename.startsWith("_loading.");
1307
+ }
1308
+ function isErrorFile(filename) {
1309
+ return filename.startsWith("_error.");
1310
+ }
1311
+ function isNotFoundFile(filename) {
1312
+ return filename.startsWith("_not-found.");
1313
+ }
1314
+ function isRouteFile(filename) {
1315
+ return /\.(page|route)\.(tsx?|jsx?)$/.test(filename);
1316
+ }
1317
+ function isApiRouteFile(filename) {
1318
+ return /\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/.test(filename);
1319
+ }
1320
+ function hasDynamicSegments(path) {
1321
+ return path.includes("[") && path.includes("]");
1322
+ }
1323
+ function scanDirectory(dir, basePath = "", results = []) {
1324
+ if (!existsSync5(dir)) {
1325
+ return results;
1326
+ }
1327
+ const entries = readdirSync3(dir);
1328
+ for (const entry of entries) {
1329
+ const fullPath = join4(dir, entry);
1330
+ const relativePath = join4(basePath, entry);
1331
+ const stat = statSync2(fullPath);
1332
+ if (stat.isDirectory()) {
1333
+ if (entry.startsWith(".") || entry === "node_modules") {
1334
+ continue;
1335
+ }
1336
+ scanDirectory(fullPath, relativePath, results);
1337
+ } else if (stat.isFile()) {
1338
+ const routePath = filePathToUrlPath2(dirname3(relativePath));
1339
+ const normalizedFilePath = relativePath.replace(/\\/g, "/");
1340
+ const isDynamic = hasDynamicSegments(relativePath);
1341
+ if (isLayoutFile(entry)) {
1342
+ results.push({
1343
+ path: routePath,
1344
+ filePath: normalizedFilePath,
1345
+ isLayout: true,
1346
+ isLoading: false,
1347
+ isError: false,
1348
+ isNotFound: false,
1349
+ isDynamic,
1350
+ isApiRoute: false
1351
+ });
1352
+ } else if (isLoadingFile(entry)) {
1353
+ results.push({
1354
+ path: routePath,
1355
+ filePath: normalizedFilePath,
1356
+ isLayout: false,
1357
+ isLoading: true,
1358
+ isError: false,
1359
+ isNotFound: false,
1360
+ isDynamic,
1361
+ isApiRoute: false
1362
+ });
1363
+ } else if (isErrorFile(entry)) {
1364
+ results.push({
1365
+ path: routePath,
1366
+ filePath: normalizedFilePath,
1367
+ isLayout: false,
1368
+ isLoading: false,
1369
+ isError: true,
1370
+ isNotFound: false,
1371
+ isDynamic,
1372
+ isApiRoute: false
1373
+ });
1374
+ } else if (isNotFoundFile(entry)) {
1375
+ results.push({
1376
+ path: routePath,
1377
+ filePath: normalizedFilePath,
1378
+ isLayout: false,
1379
+ isLoading: false,
1380
+ isError: false,
1381
+ isNotFound: true,
1382
+ isDynamic,
1383
+ isApiRoute: false
1384
+ });
1385
+ } else if (isApiRouteFile(entry)) {
1386
+ const method = extractHttpMethod(entry);
1387
+ results.push({
1388
+ path: filePathToUrlPath2(relativePath),
1389
+ filePath: normalizedFilePath,
1390
+ isLayout: false,
1391
+ isLoading: false,
1392
+ isError: false,
1393
+ isNotFound: false,
1394
+ isDynamic,
1395
+ isApiRoute: true,
1396
+ httpMethod: method
1397
+ });
1398
+ } else if (isRouteFile(entry)) {
1399
+ results.push({
1400
+ path: filePathToUrlPath2(relativePath),
1401
+ filePath: normalizedFilePath,
1402
+ isLayout: false,
1403
+ isLoading: false,
1404
+ isError: false,
1405
+ isNotFound: false,
1406
+ isDynamic,
1407
+ isApiRoute: false
1408
+ });
1409
+ }
1410
+ }
1411
+ }
1412
+ return results;
1413
+ }
1414
+ function sortRoutes(routes) {
1415
+ return routes.sort((a, b) => {
1416
+ if (!a.isDynamic && b.isDynamic) return -1;
1417
+ if (a.isDynamic && !b.isDynamic) return 1;
1418
+ const aSegments = a.path.split("/").length;
1419
+ const bSegments = b.path.split("/").length;
1420
+ if (aSegments !== bSegments) return aSegments - bSegments;
1421
+ return a.path.localeCompare(b.path);
1422
+ });
1423
+ }
1424
+ function generateRouteManifest(routesDir) {
1425
+ const allRoutes = scanDirectory(routesDir);
1426
+ const isPageRoute = (r) => !r.isLayout && !r.isLoading && !r.isError && !r.isNotFound && !r.isApiRoute;
1427
+ const routes = sortRoutes(allRoutes.filter(isPageRoute));
1428
+ const layouts = allRoutes.filter((r) => r.isLayout);
1429
+ const loadingStates = allRoutes.filter((r) => r.isLoading);
1430
+ const errorBoundaries = allRoutes.filter((r) => r.isError);
1431
+ const notFoundPages = allRoutes.filter((r) => r.isNotFound);
1432
+ const apiRoutes = sortRoutes(allRoutes.filter((r) => r.isApiRoute));
1433
+ return {
1434
+ routes,
1435
+ layouts,
1436
+ loadingStates,
1437
+ errorBoundaries,
1438
+ notFoundPages,
1439
+ apiRoutes,
1440
+ generated: (/* @__PURE__ */ new Date()).toISOString()
1441
+ };
1442
+ }
1443
+ function generateRoutesFile(manifest, outputDir) {
1444
+ if (!existsSync5(outputDir)) {
1445
+ mkdirSync3(outputDir, { recursive: true });
1446
+ }
1447
+ const routesContent = `/**
1448
+ * Auto-generated by Flight CLI
1449
+ * Do not edit manually
1450
+ * Generated: ${manifest.generated}
1451
+ */
1452
+
1453
+ import type { RouteDefinition } from '@flight-framework/router';
1454
+
1455
+ // Page Routes
1456
+ export const routes: RouteDefinition[] = [
1457
+ ${manifest.routes.map((r) => ` {
1458
+ path: '${r.path}',
1459
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1460
+ },`).join("\n")}
1461
+ ];
1462
+
1463
+ // Layout Components
1464
+ export const layouts = [
1465
+ ${manifest.layouts.map((r) => ` {
1466
+ path: '${r.path}',
1467
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1468
+ },`).join("\n")}
1469
+ ];
1470
+
1471
+ // Loading State Components
1472
+ export const loadingStates = [
1473
+ ${manifest.loadingStates.map((r) => ` {
1474
+ path: '${r.path}',
1475
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1476
+ },`).join("\n")}
1477
+ ];
1478
+
1479
+ // Error Boundary Components
1480
+ export const errorBoundaries = [
1481
+ ${manifest.errorBoundaries.map((r) => ` {
1482
+ path: '${r.path}',
1483
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1484
+ },`).join("\n")}
1485
+ ];
1103
1486
 
1104
- /**
1105
- * Server-side environment variables accessible via process.env
1106
- * These are NOT exposed to the client.
1107
- */
1108
- declare namespace NodeJS {
1109
- interface ProcessEnv {
1110
- ${serverVars || " // No server environment variables defined"}
1111
- }
1112
- }
1487
+ // Not Found Page Components
1488
+ export const notFoundPages = [
1489
+ ${manifest.notFoundPages.map((r) => ` {
1490
+ path: '${r.path}',
1491
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1492
+ },`).join("\n")}
1493
+ ];
1113
1494
 
1114
- // ============================================================================
1115
- // Client-side Environment Variables
1116
- // ============================================================================
1495
+ // API Routes
1496
+ export const apiRoutes = [
1497
+ ${manifest.apiRoutes.map((r) => ` {
1498
+ path: '${r.path}',
1499
+ method: '${r.httpMethod}',
1500
+ handler: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
1501
+ },`).join("\n")}
1502
+ ];
1117
1503
 
1118
- /**
1119
- * Client-side environment variables accessible via import.meta.env
1120
- * Only variables with PUBLIC_ prefix are included.
1504
+ // Type-safe route paths
1505
+ export type AppRoutes = ${manifest.routes.length > 0 ? manifest.routes.map((r) => `'${r.path}'`).join(" | ") : "never"};
1506
+
1507
+ // Type-safe API route paths
1508
+ export type ApiRoutes = ${manifest.apiRoutes.length > 0 ? manifest.apiRoutes.map((r) => `'${r.path}'`).join(" | ") : "never"};
1509
+ `;
1510
+ writeFileSync3(join4(outputDir, "routes.ts"), routesContent, "utf-8");
1511
+ const typesContent = `/**
1512
+ * Auto-generated route types
1513
+ * Generated: ${manifest.generated}
1121
1514
  */
1122
- interface ImportMetaEnv {
1123
- ${clientVars || " // No client environment variables defined"}
1124
- }
1125
1515
 
1126
- interface ImportMeta {
1127
- readonly env: ImportMetaEnv;
1128
- }
1516
+ import type { RouteParams } from '@flight-framework/router';
1517
+
1518
+ // Extract params from route patterns
1519
+ ${manifest.routes.filter((r) => r.isDynamic).map((r) => {
1520
+ const paramMatches = r.path.match(/:(\w+)/g) || [];
1521
+ const params = paramMatches.map((p) => p.slice(1));
1522
+ const typeName = r.path.replace(/[/:]/g, "_").replace(/^_/, "").replace(/_$/, "") || "Root";
1523
+ return `export type ${typeName}Params = { ${params.map((p) => `${p}: string`).join("; ")} };`;
1524
+ }).join("\n")}
1129
1525
  `;
1526
+ writeFileSync3(join4(outputDir, "types.ts"), typesContent, "utf-8");
1130
1527
  }
1131
- function scanRoutesForTypes(routesDir) {
1132
- if (!existsSync5(routesDir)) {
1133
- return [];
1134
- }
1135
- const routes = [];
1136
- scanDirectoryRecursive(routesDir, "", routes);
1137
- return routes;
1138
- }
1139
- function scanDirectoryRecursive(dir, basePath, results) {
1140
- const entries = readdirSync3(dir, { withFileTypes: true });
1141
- for (const entry of entries) {
1142
- const fullPath = join4(dir, entry.name);
1143
- const relativePath = join4(basePath, entry.name);
1144
- if (entry.isDirectory()) {
1145
- if (entry.name.startsWith(".") || entry.name === "node_modules") {
1146
- continue;
1147
- }
1148
- scanDirectoryRecursive(fullPath, relativePath, results);
1149
- } else if (entry.isFile()) {
1150
- const route = parseRouteFile(entry.name, relativePath);
1151
- if (route) {
1152
- results.push(route);
1153
- }
1154
- }
1155
- }
1156
- }
1157
- function parseRouteFile(filename, relativePath) {
1158
- if (/\.(page|route)\.(tsx?|jsx?)$/.test(filename)) {
1159
- const urlPath = filePathToUrlPath2(relativePath);
1160
- return {
1161
- path: urlPath,
1162
- filePath: relativePath.replace(/\\/g, "/"),
1163
- isDynamic: isDynamicRoute(urlPath),
1164
- isApiRoute: false
1165
- };
1166
- }
1167
- const apiMatch = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
1168
- if (apiMatch) {
1169
- const urlPath = filePathToUrlPath2(relativePath);
1170
- return {
1171
- path: urlPath,
1172
- filePath: relativePath.replace(/\\/g, "/"),
1173
- isDynamic: isDynamicRoute(urlPath),
1174
- isApiRoute: true,
1175
- httpMethod: apiMatch[1].toUpperCase()
1176
- };
1177
- }
1178
- return null;
1179
- }
1180
- function filePathToUrlPath2(filePath) {
1181
- let urlPath = filePath.replace(/\\/g, "/").replace(/\.(page|route)\.(tsx?|jsx?)$/, "").replace(/\.(get|post|put|patch|delete|options|head)$/i, "").replace(/\/index$/, "").replace(/^index$/, "").replace(/\/?\\?\([^)]+\\?\)/g, "").replace(/\[\.\.\.(\w+)\]/g, "*$1").replace(/\[(\w+)\]/g, ":$1");
1182
- if (!urlPath.startsWith("/")) {
1183
- urlPath = "/" + urlPath;
1184
- }
1185
- urlPath = urlPath.replace(/\/+/g, "/");
1186
- return urlPath || "/";
1528
+ async function generateRoutes(options) {
1529
+ const { routesDir, outputDir } = options;
1530
+ console.log(`Scanning routes in: ${routesDir}`);
1531
+ const manifest = generateRouteManifest(routesDir);
1532
+ const stats = [
1533
+ `${manifest.routes.length} pages`,
1534
+ `${manifest.layouts.length} layouts`,
1535
+ `${manifest.loadingStates.length} loading states`,
1536
+ `${manifest.errorBoundaries.length} error boundaries`,
1537
+ `${manifest.notFoundPages.length} not-found pages`,
1538
+ `${manifest.apiRoutes.length} API routes`
1539
+ ].join(", ");
1540
+ console.log(`Found: ${stats}`);
1541
+ generateRoutesFile(manifest, outputDir);
1542
+ console.log(`Generated route manifest in: ${outputDir}`);
1543
+ return manifest;
1187
1544
  }
1188
- async function generateTypes(options) {
1189
- const {
1190
- routesDir,
1191
- outputDir,
1192
- includeRoutes = true,
1193
- includeEnv = false,
1194
- projectRoot = process.cwd()
1195
- } = options;
1196
- const result = {
1197
- routeTypes: "",
1198
- envTypes: "",
1199
- filesWritten: [],
1200
- routeCount: 0,
1201
- envVarCount: 0
1202
- };
1203
- if (!existsSync5(outputDir)) {
1204
- mkdirSync3(outputDir, { recursive: true });
1205
- }
1206
- if (includeRoutes) {
1207
- const routes = scanRoutesForTypes(routesDir);
1208
- result.routeCount = routes.length;
1209
- result.routeTypes = generateRouteTypes(routes);
1210
- const routeTypesPath = join4(outputDir, "routes.d.ts");
1211
- writeFileSync3(routeTypesPath, result.routeTypes, "utf-8");
1212
- result.filesWritten.push(routeTypesPath);
1213
- console.log(`Generated route types: ${routes.length} routes`);
1214
- }
1215
- if (includeEnv) {
1216
- const env = loadEnvFiles(projectRoot);
1217
- result.envVarCount = env.server.length + env.client.length;
1218
- result.envTypes = generateEnvTypes(env);
1219
- const envTypesPath = join4(outputDir, "env.d.ts");
1220
- writeFileSync3(envTypesPath, result.envTypes, "utf-8");
1221
- result.filesWritten.push(envTypesPath);
1222
- console.log(`Generated env types: ${env.server.length} server, ${env.client.length} client`);
1545
+
1546
+ // src/commands/routes-generate.ts
1547
+ async function routesGenerateCommand(options = {}) {
1548
+ const cwd = process.cwd();
1549
+ const routesDir = options.routesDir ? resolve6(cwd, options.routesDir) : resolve6(cwd, "src/routes");
1550
+ const outputDir = options.outputDir ? resolve6(cwd, options.outputDir) : resolve6(cwd, "src/.flight");
1551
+ try {
1552
+ const manifest = await generateRoutes({
1553
+ routesDir,
1554
+ outputDir,
1555
+ watch: options.watch
1556
+ });
1557
+ console.log("\nRoute manifest generated successfully!");
1558
+ console.log(` Pages: ${manifest.routes.length}`);
1559
+ console.log(` API Routes: ${manifest.apiRoutes.length}`);
1560
+ console.log(` Layouts: ${manifest.layouts.length}`);
1561
+ } catch (error) {
1562
+ console.error("Failed to generate routes:", error);
1563
+ process.exit(1);
1223
1564
  }
1224
- return result;
1225
- }
1226
- function watchAndGenerate(options) {
1227
- const { routesDir, debounce = 100, onRegenerate } = options;
1228
- let timeout = null;
1229
- const regenerate = async () => {
1230
- try {
1231
- const result = await generateTypes(options);
1232
- onRegenerate?.(result);
1233
- } catch (error) {
1234
- console.error("Type generation failed:", error.message);
1235
- }
1236
- };
1237
- const debouncedRegenerate = () => {
1238
- if (timeout) clearTimeout(timeout);
1239
- timeout = setTimeout(regenerate, debounce);
1240
- };
1241
- regenerate();
1242
- const watcher = watch(routesDir, { recursive: true }, (eventType, filename) => {
1243
- if (filename && /\.(tsx?|jsx?)$/.test(filename)) {
1244
- console.log(`Route file changed: ${filename}`);
1245
- debouncedRegenerate();
1246
- }
1247
- });
1248
- return () => {
1249
- watcher.close();
1250
- if (timeout) clearTimeout(timeout);
1251
- };
1252
1565
  }
1253
1566
 
1254
1567
  // src/commands/types-generate.ts
1568
+ import { resolve as resolve7 } from "path";
1255
1569
  async function typesGenerateCommand(options = {}) {
1256
1570
  const cwd = process.cwd();
1257
1571
  const routesDir = options.routesDir ? resolve7(cwd, options.routesDir) : resolve7(cwd, "src/routes");
@@ -1310,25 +1624,234 @@ async function typesGenerateCommand(options = {}) {
1310
1624
  }
1311
1625
  }
1312
1626
 
1627
+ // src/commands/add.ts
1628
+ import { existsSync as existsSync6 } from "fs";
1629
+ import { join as join5 } from "path";
1630
+ import { execSync as execSync2 } from "child_process";
1631
+ import pc5 from "picocolors";
1632
+ var PACKAGES = {
1633
+ // Core
1634
+ "http": {
1635
+ name: "@flight-framework/http",
1636
+ description: "HTTP server with routing and middleware",
1637
+ category: "core"
1638
+ },
1639
+ "core": {
1640
+ name: "@flight-framework/core",
1641
+ description: "File-based routing and configuration",
1642
+ category: "core"
1643
+ },
1644
+ // Data
1645
+ "db": {
1646
+ name: "@flight-framework/db",
1647
+ description: "Database abstraction layer",
1648
+ category: "data",
1649
+ drivers: ["pg", "@libsql/client", "@neondatabase/serverless", "@supabase/supabase-js"]
1650
+ },
1651
+ "cache": {
1652
+ name: "@flight-framework/cache",
1653
+ description: "Caching with multiple adapters",
1654
+ category: "data"
1655
+ },
1656
+ // Auth
1657
+ "auth": {
1658
+ name: "@flight-framework/auth",
1659
+ description: "Authentication adapters",
1660
+ category: "auth"
1661
+ },
1662
+ // Frontend
1663
+ "forms": {
1664
+ name: "@flight-framework/forms",
1665
+ description: "Type-safe form handling",
1666
+ category: "frontend"
1667
+ },
1668
+ "i18n": {
1669
+ name: "@flight-framework/i18n",
1670
+ description: "Internationalization",
1671
+ category: "frontend"
1672
+ },
1673
+ "seo": {
1674
+ name: "@flight-framework/seo",
1675
+ description: "SEO utilities",
1676
+ category: "frontend"
1677
+ },
1678
+ "image": {
1679
+ name: "@flight-framework/image",
1680
+ description: "Image optimization",
1681
+ category: "frontend"
1682
+ },
1683
+ // Communication
1684
+ "email": {
1685
+ name: "@flight-framework/email",
1686
+ description: "Email sending",
1687
+ category: "communication"
1688
+ },
1689
+ "realtime": {
1690
+ name: "@flight-framework/realtime",
1691
+ description: "WebSocket and real-time features",
1692
+ category: "communication"
1693
+ },
1694
+ // Deployment
1695
+ "helpers": {
1696
+ name: "@flight-framework/helpers",
1697
+ description: "Optional helper utilities (detection, suggestions)",
1698
+ category: "utilities"
1699
+ }
1700
+ };
1701
+ async function addCommand(packageName) {
1702
+ const cwd = process.cwd();
1703
+ if (!existsSync6(join5(cwd, "package.json"))) {
1704
+ console.log(pc5.red("No package.json found. Run this command from your project directory."));
1705
+ process.exit(1);
1706
+ }
1707
+ if (!packageName) {
1708
+ showAvailablePackages();
1709
+ return;
1710
+ }
1711
+ const pkg = PACKAGES[packageName];
1712
+ if (!pkg) {
1713
+ console.log(pc5.red(`Unknown package: ${packageName}`));
1714
+ console.log(pc5.dim("Run `flight add` to see available packages."));
1715
+ process.exit(1);
1716
+ }
1717
+ console.log(`
1718
+ ${pc5.cyan("Adding")} ${pc5.bold(pkg.name)}...`);
1719
+ console.log(pc5.dim(pkg.description));
1720
+ console.log();
1721
+ const pm = detectPackageManager2();
1722
+ try {
1723
+ const installCmd = pm === "npm" ? "npm install" : `${pm} add`;
1724
+ execSync2(`${installCmd} ${pkg.name}`, { cwd, stdio: "inherit" });
1725
+ console.log(`
1726
+ ${pc5.green("[OK]")} ${pkg.name} added successfully!`);
1727
+ showNextSteps(packageName, pkg);
1728
+ } catch (error) {
1729
+ console.log(pc5.red(`
1730
+ Failed to install ${pkg.name}`));
1731
+ process.exit(1);
1732
+ }
1733
+ }
1734
+ function detectPackageManager2() {
1735
+ const cwd = process.cwd();
1736
+ if (existsSync6(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
1737
+ if (existsSync6(join5(cwd, "yarn.lock"))) return "yarn";
1738
+ if (existsSync6(join5(cwd, "bun.lockb"))) return "bun";
1739
+ return "npm";
1740
+ }
1741
+ function showAvailablePackages() {
1742
+ console.log(`
1743
+ ${pc5.cyan("Available Flight packages:")}
1744
+
1745
+ ${pc5.bold("Core")}
1746
+ ${pc5.green("http")} ${pc5.dim("HTTP server with routing and middleware")}
1747
+ ${pc5.green("core")} ${pc5.dim("File-based routing and configuration")}
1748
+
1749
+ ${pc5.bold("Data")}
1750
+ ${pc5.green("db")} ${pc5.dim("Database abstraction layer")}
1751
+ ${pc5.green("cache")} ${pc5.dim("Caching with multiple adapters")}
1752
+
1753
+ ${pc5.bold("Auth")}
1754
+ ${pc5.green("auth")} ${pc5.dim("Authentication adapters")}
1755
+
1756
+ ${pc5.bold("Frontend")}
1757
+ ${pc5.green("forms")} ${pc5.dim("Type-safe form handling")}
1758
+ ${pc5.green("i18n")} ${pc5.dim("Internationalization")}
1759
+ ${pc5.green("seo")} ${pc5.dim("SEO utilities")}
1760
+ ${pc5.green("image")} ${pc5.dim("Image optimization")}
1761
+
1762
+ ${pc5.bold("Communication")}
1763
+ ${pc5.green("email")} ${pc5.dim("Email sending")}
1764
+ ${pc5.green("realtime")} ${pc5.dim("WebSocket and real-time features")}
1765
+
1766
+ ${pc5.bold("Utilities")}
1767
+ ${pc5.green("helpers")} ${pc5.dim("Optional helper utilities")}
1768
+
1769
+ ${pc5.cyan("Usage:")}
1770
+ flight add http
1771
+ flight add db
1772
+ flight add auth
1773
+ `);
1774
+ }
1775
+ function showNextSteps(packageName, pkg) {
1776
+ const examples = {
1777
+ "http": `
1778
+ ${pc5.cyan("Quick example:")}
1779
+
1780
+ import { createServer } from '@flight-framework/http';
1781
+
1782
+ const app = createServer();
1783
+ app.get('/', (c) => c.json({ hello: 'world' }));
1784
+
1785
+ export default { port: 3000, fetch: app.fetch };
1786
+ `,
1787
+ "db": `
1788
+ ${pc5.cyan("Quick example:")}
1789
+
1790
+ import { createDb } from '@flight-framework/db';
1791
+ import { postgres } from '@flight-framework/db/postgres';
1792
+
1793
+ const db = createDb(postgres({ connectionString: process.env.DATABASE_URL }));
1794
+ const users = await db.query('SELECT * FROM users');
1795
+
1796
+ ${pc5.dim("You may also need a driver:")}
1797
+ npm install pg ${pc5.dim("# PostgreSQL")}
1798
+ npm install @libsql/client ${pc5.dim("# Turso/SQLite")}
1799
+ npm install @neondatabase/serverless ${pc5.dim("# Neon")}
1800
+ `,
1801
+ "cache": `
1802
+ ${pc5.cyan("Quick example:")}
1803
+
1804
+ import { createCache, lru } from '@flight-framework/cache';
1805
+
1806
+ const cache = createCache(lru({ max: 1000 }));
1807
+ await cache.set('key', { data: 'value' });
1808
+ const value = await cache.get('key');
1809
+ `,
1810
+ "auth": `
1811
+ ${pc5.cyan("Quick example:")}
1812
+
1813
+ import { createAuth } from '@flight-framework/auth';
1814
+ import { betterAuth } from '@flight-framework/auth/better-auth';
1815
+
1816
+ const auth = createAuth(betterAuth({ db }));
1817
+ const user = await auth.getUser(request);
1818
+ `,
1819
+ "forms": `
1820
+ ${pc5.cyan("Quick example:")}
1821
+
1822
+ import { createForm } from '@flight-framework/forms';
1823
+ import { zodAdapter } from '@flight-framework/forms/adapters/zod';
1824
+
1825
+ const form = createForm({ schema: zodAdapter(schema) });
1826
+ const result = form.validate(data);
1827
+ `
1828
+ };
1829
+ if (examples[packageName]) {
1830
+ console.log(examples[packageName]);
1831
+ }
1832
+ console.log(`${pc5.dim("Docs:")} https://flight.dev/docs/packages/${packageName}`);
1833
+ }
1834
+
1313
1835
  // src/index.ts
1314
1836
  var cli = cac("flight");
1315
1837
  var LOGO = `
1316
- ${pc5.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1317
- ${pc5.cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
1318
- ${pc5.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1319
- ${pc5.cyan(" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1320
- ${pc5.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1321
- ${pc5.cyan(" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
1838
+ ${pc6.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
1839
+ ${pc6.cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
1840
+ ${pc6.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1841
+ ${pc6.cyan(" \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1842
+ ${pc6.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}
1843
+ ${pc6.cyan(" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
1322
1844
 
1323
- ${pc5.dim("The Agnostic Full-Stack Framework")}
1324
- ${pc5.dim("Maximum Flexibility. Zero Lock-in.")}
1845
+ ${pc6.dim("The Agnostic Full-Stack Framework")}
1846
+ ${pc6.dim("Maximum Flexibility. Zero Lock-in.")}
1325
1847
  `;
1326
1848
  function printLogo() {
1327
1849
  console.log(LOGO);
1328
1850
  }
1329
1851
  cli.version(VERSION);
1330
1852
  cli.help();
1331
- cli.command("create [name]", "Create a new Flight project").option("-t, --template <template>", "Project template to use", { default: "basic" }).option("--ui <framework>", "UI framework (react, vue, svelte, solid, vanilla)").option("--use-case <useCase>", "Use-case template (blog, ecommerce, saas, api, docs)").option("--ts", "Use TypeScript", { default: true }).option("--git", "Initialize git repository", { default: true }).option("--install", "Install dependencies", { default: true }).action(createCommand);
1853
+ cli.command("create [name]", "Create a new Flight project").option("-t, --template <template>", "Project template to use", { default: "basic" }).option("--ui <framework>", "UI framework (react, vue, svelte, solid, vanilla)").option("--use-case <useCase>", "Use-case template (blog, ecommerce, saas, api, docs)").option("--ts", "Use TypeScript", { default: true }).option("--git", "Initialize git repository", { default: true }).option("--install", "Install dependencies", { default: true }).option("--raw", "Create raw project (100% Web Standards, zero dependencies)").option("--empty", "Create empty project (just package.json)").option("--minimal", "Create minimal project (single server file with Flight)").action(createCommand);
1854
+ cli.command("add [package]", "Add a Flight package to your project").action(addCommand);
1332
1855
  cli.command("dev", "Start development server").option("-p, --port <port>", "Port to listen on").option("-h, --host <host>", "Host to bind to").option("--open", "Open browser on start").option("--https", "Enable HTTPS").option("--ssr", "Enable Server-Side Rendering").action(devCommand);
1333
1856
  cli.command("build", "Build for production").option("--outDir <dir>", "Output directory").option("--sourcemap", "Generate source maps").option("--minify", "Minify output", { default: true }).action(buildCommand);
1334
1857
  cli.command("preview", "Preview production build").option("-p, --port <port>", "Port to listen on").option("-h, --host <host>", "Host to bind to").option("--open", "Open browser on start").action(previewCommand);
@@ -1342,7 +1865,7 @@ function run() {
1342
1865
  }
1343
1866
  cli.runMatchedCommand();
1344
1867
  } catch (error) {
1345
- console.error(pc5.red("Error:"), error instanceof Error ? error.message : error);
1868
+ console.error(pc6.red("Error:"), error instanceof Error ? error.message : error);
1346
1869
  process.exit(1);
1347
1870
  }
1348
1871
  }