@flight-framework/cli 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,6 +33,11 @@ var UI_FRAMEWORKS = [
33
33
  { title: "Htmx", value: "htmx", description: "HTML over the wire, no JavaScript" },
34
34
  { title: "Vanilla", value: "vanilla", description: "No framework, just TypeScript" }
35
35
  ];
36
+ var BUNDLERS = [
37
+ { title: "Vite", value: "vite", description: "Next Gen Frontend Tooling (recommended)" },
38
+ { title: "esbuild", value: "esbuild", description: "Ultra-fast builds for performance-focused projects" },
39
+ { title: "Rolldown", value: "rolldown", description: "Rust-based Rollup replacement (experimental)" }
40
+ ];
36
41
  async function createCommand(name, options) {
37
42
  printLogo();
38
43
  console.log(pc.cyan("\n[*] Creating a new Flight project...\n"));
@@ -70,6 +75,21 @@ async function createCommand(name, options) {
70
75
  console.log(pc.red("Project creation cancelled."));
71
76
  return;
72
77
  }
78
+ let bundler = options.bundler;
79
+ if (!bundler) {
80
+ const response = await prompts({
81
+ type: "select",
82
+ name: "bundler",
83
+ message: "Choose your bundler:",
84
+ choices: BUNDLERS,
85
+ initial: 0
86
+ });
87
+ bundler = response.bundler;
88
+ }
89
+ if (!bundler) {
90
+ console.log(pc.red("Project creation cancelled."));
91
+ return;
92
+ }
73
93
  const projectPath = resolve(process.cwd(), projectName);
74
94
  if (existsSync(projectPath)) {
75
95
  const files = readdirSync(projectPath);
@@ -90,7 +110,7 @@ async function createCommand(name, options) {
90
110
  Creating project in ${projectPath}...
91
111
  `));
92
112
  try {
93
- copyTemplate(projectPath, uiFramework, projectName);
113
+ copyTemplate(projectPath, uiFramework, bundler, projectName);
94
114
  console.log(pc.green("\u2713") + " Project structure created");
95
115
  if (options.git) {
96
116
  try {
@@ -136,7 +156,7 @@ function detectPackageManager() {
136
156
  if (process.env.npm_config_user_agent?.includes("bun")) return "bun";
137
157
  return "npm";
138
158
  }
139
- function copyTemplate(projectPath, ui, projectName) {
159
+ function copyTemplate(projectPath, ui, bundler, projectName) {
140
160
  const baseDir = join(TEMPLATES_DIR, "base");
141
161
  const uiDir = join(TEMPLATES_DIR, ui);
142
162
  mkdirSync(projectPath, { recursive: true });
@@ -146,6 +166,8 @@ function copyTemplate(projectPath, ui, projectName) {
146
166
  const vars = {
147
167
  "{{PROJECT_NAME}}": projectName,
148
168
  "{{UI_FRAMEWORK}}": ui,
169
+ "{{BUNDLER}}": bundler,
170
+ "{{BUNDLER_PACKAGE}}": `@flight-framework/bundler-${bundler}`,
149
171
  "{{LANGUAGE}}": "TypeScript"
150
172
  };
151
173
  copyDirWithTemplates(baseDir, projectPath, vars);
@@ -449,8 +471,16 @@ function getNetworkAddress() {
449
471
 
450
472
  // src/commands/build.ts
451
473
  import { resolve as resolve3 } from "path";
474
+ import { existsSync as existsSync3 } from "fs";
452
475
  import pc3 from "picocolors";
453
476
  import { loadConfig as loadConfig2 } from "@flight-framework/core/config";
477
+ function findEntryServer(root, srcDir) {
478
+ const tsxPath = resolve3(root, srcDir, "entry-server.tsx");
479
+ const tsPath = resolve3(root, srcDir, "entry-server.ts");
480
+ if (existsSync3(tsxPath)) return tsxPath;
481
+ if (existsSync3(tsPath)) return tsPath;
482
+ return tsxPath;
483
+ }
454
484
  async function buildCommand(options) {
455
485
  const startTime = Date.now();
456
486
  printLogo();
@@ -491,7 +521,7 @@ async function buildCommand(options) {
491
521
  minify: minify ? "esbuild" : false,
492
522
  ssr: true,
493
523
  rollupOptions: {
494
- input: resolve3(root, config.build.srcDir, "entry-server.ts")
524
+ input: findEntryServer(root, config.build.srcDir)
495
525
  }
496
526
  }
497
527
  });
@@ -563,6 +593,292 @@ async function previewCommand(options) {
563
593
  }
564
594
  }
565
595
 
596
+ // src/commands/routes-generate.ts
597
+ import { resolve as resolve5 } from "path";
598
+
599
+ // src/generators/routes.ts
600
+ import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
601
+ import { join as join3, dirname as dirname2 } from "path";
602
+ function filePathToUrlPath(filePath) {
603
+ 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");
604
+ if (!urlPath.startsWith("/")) {
605
+ urlPath = "/" + urlPath;
606
+ }
607
+ if (urlPath === "" || urlPath === "/") {
608
+ urlPath = "/";
609
+ }
610
+ urlPath = urlPath.replace(/\/+/g, "/");
611
+ return urlPath;
612
+ }
613
+ function extractHttpMethod(filename) {
614
+ const match = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
615
+ return match ? match[1].toUpperCase() : void 0;
616
+ }
617
+ function isLayoutFile(filename) {
618
+ return filename.startsWith("_layout.");
619
+ }
620
+ function isLoadingFile(filename) {
621
+ return filename.startsWith("_loading.");
622
+ }
623
+ function isErrorFile(filename) {
624
+ return filename.startsWith("_error.");
625
+ }
626
+ function isNotFoundFile(filename) {
627
+ return filename.startsWith("_not-found.");
628
+ }
629
+ function isRouteFile(filename) {
630
+ return /\.(page|route)\.(tsx?|jsx?)$/.test(filename);
631
+ }
632
+ function isApiRouteFile(filename) {
633
+ return /\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/.test(filename);
634
+ }
635
+ function hasDynamicSegments(path) {
636
+ return path.includes("[") && path.includes("]");
637
+ }
638
+ function scanDirectory(dir, basePath = "", results = []) {
639
+ if (!existsSync4(dir)) {
640
+ return results;
641
+ }
642
+ const entries = readdirSync2(dir);
643
+ for (const entry of entries) {
644
+ const fullPath = join3(dir, entry);
645
+ const relativePath = join3(basePath, entry);
646
+ const stat = statSync2(fullPath);
647
+ if (stat.isDirectory()) {
648
+ if (entry.startsWith(".") || entry === "node_modules") {
649
+ continue;
650
+ }
651
+ scanDirectory(fullPath, relativePath, results);
652
+ } else if (stat.isFile()) {
653
+ const routePath = filePathToUrlPath(dirname2(relativePath));
654
+ const normalizedFilePath = relativePath.replace(/\\/g, "/");
655
+ const isDynamic = hasDynamicSegments(relativePath);
656
+ if (isLayoutFile(entry)) {
657
+ results.push({
658
+ path: routePath,
659
+ filePath: normalizedFilePath,
660
+ isLayout: true,
661
+ isLoading: false,
662
+ isError: false,
663
+ isNotFound: false,
664
+ isDynamic,
665
+ isApiRoute: false
666
+ });
667
+ } else if (isLoadingFile(entry)) {
668
+ results.push({
669
+ path: routePath,
670
+ filePath: normalizedFilePath,
671
+ isLayout: false,
672
+ isLoading: true,
673
+ isError: false,
674
+ isNotFound: false,
675
+ isDynamic,
676
+ isApiRoute: false
677
+ });
678
+ } else if (isErrorFile(entry)) {
679
+ results.push({
680
+ path: routePath,
681
+ filePath: normalizedFilePath,
682
+ isLayout: false,
683
+ isLoading: false,
684
+ isError: true,
685
+ isNotFound: false,
686
+ isDynamic,
687
+ isApiRoute: false
688
+ });
689
+ } else if (isNotFoundFile(entry)) {
690
+ results.push({
691
+ path: routePath,
692
+ filePath: normalizedFilePath,
693
+ isLayout: false,
694
+ isLoading: false,
695
+ isError: false,
696
+ isNotFound: true,
697
+ isDynamic,
698
+ isApiRoute: false
699
+ });
700
+ } else if (isApiRouteFile(entry)) {
701
+ const method = extractHttpMethod(entry);
702
+ results.push({
703
+ path: filePathToUrlPath(relativePath),
704
+ filePath: normalizedFilePath,
705
+ isLayout: false,
706
+ isLoading: false,
707
+ isError: false,
708
+ isNotFound: false,
709
+ isDynamic,
710
+ isApiRoute: true,
711
+ httpMethod: method
712
+ });
713
+ } else if (isRouteFile(entry)) {
714
+ results.push({
715
+ path: filePathToUrlPath(relativePath),
716
+ filePath: normalizedFilePath,
717
+ isLayout: false,
718
+ isLoading: false,
719
+ isError: false,
720
+ isNotFound: false,
721
+ isDynamic,
722
+ isApiRoute: false
723
+ });
724
+ }
725
+ }
726
+ }
727
+ return results;
728
+ }
729
+ function sortRoutes(routes) {
730
+ return routes.sort((a, b) => {
731
+ if (!a.isDynamic && b.isDynamic) return -1;
732
+ if (a.isDynamic && !b.isDynamic) return 1;
733
+ const aSegments = a.path.split("/").length;
734
+ const bSegments = b.path.split("/").length;
735
+ if (aSegments !== bSegments) return aSegments - bSegments;
736
+ return a.path.localeCompare(b.path);
737
+ });
738
+ }
739
+ function generateRouteManifest(routesDir) {
740
+ const allRoutes = scanDirectory(routesDir);
741
+ const isPageRoute = (r) => !r.isLayout && !r.isLoading && !r.isError && !r.isNotFound && !r.isApiRoute;
742
+ const routes = sortRoutes(allRoutes.filter(isPageRoute));
743
+ const layouts = allRoutes.filter((r) => r.isLayout);
744
+ const loadingStates = allRoutes.filter((r) => r.isLoading);
745
+ const errorBoundaries = allRoutes.filter((r) => r.isError);
746
+ const notFoundPages = allRoutes.filter((r) => r.isNotFound);
747
+ const apiRoutes = sortRoutes(allRoutes.filter((r) => r.isApiRoute));
748
+ return {
749
+ routes,
750
+ layouts,
751
+ loadingStates,
752
+ errorBoundaries,
753
+ notFoundPages,
754
+ apiRoutes,
755
+ generated: (/* @__PURE__ */ new Date()).toISOString()
756
+ };
757
+ }
758
+ function generateRoutesFile(manifest, outputDir) {
759
+ if (!existsSync4(outputDir)) {
760
+ mkdirSync2(outputDir, { recursive: true });
761
+ }
762
+ const routesContent = `/**
763
+ * Auto-generated by Flight CLI
764
+ * Do not edit manually
765
+ * Generated: ${manifest.generated}
766
+ */
767
+
768
+ import type { RouteDefinition } from '@flight-framework/router';
769
+
770
+ // Page Routes
771
+ export const routes: RouteDefinition[] = [
772
+ ${manifest.routes.map((r) => ` {
773
+ path: '${r.path}',
774
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
775
+ },`).join("\n")}
776
+ ];
777
+
778
+ // Layout Components
779
+ export const layouts = [
780
+ ${manifest.layouts.map((r) => ` {
781
+ path: '${r.path}',
782
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
783
+ },`).join("\n")}
784
+ ];
785
+
786
+ // Loading State Components
787
+ export const loadingStates = [
788
+ ${manifest.loadingStates.map((r) => ` {
789
+ path: '${r.path}',
790
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
791
+ },`).join("\n")}
792
+ ];
793
+
794
+ // Error Boundary Components
795
+ export const errorBoundaries = [
796
+ ${manifest.errorBoundaries.map((r) => ` {
797
+ path: '${r.path}',
798
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
799
+ },`).join("\n")}
800
+ ];
801
+
802
+ // Not Found Page Components
803
+ export const notFoundPages = [
804
+ ${manifest.notFoundPages.map((r) => ` {
805
+ path: '${r.path}',
806
+ component: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
807
+ },`).join("\n")}
808
+ ];
809
+
810
+ // API Routes
811
+ export const apiRoutes = [
812
+ ${manifest.apiRoutes.map((r) => ` {
813
+ path: '${r.path}',
814
+ method: '${r.httpMethod}',
815
+ handler: () => import('../routes/${r.filePath.replace(/\.(tsx?|jsx?)$/, "")}'),
816
+ },`).join("\n")}
817
+ ];
818
+
819
+ // Type-safe route paths
820
+ export type AppRoutes = ${manifest.routes.length > 0 ? manifest.routes.map((r) => `'${r.path}'`).join(" | ") : "never"};
821
+
822
+ // Type-safe API route paths
823
+ export type ApiRoutes = ${manifest.apiRoutes.length > 0 ? manifest.apiRoutes.map((r) => `'${r.path}'`).join(" | ") : "never"};
824
+ `;
825
+ writeFileSync2(join3(outputDir, "routes.ts"), routesContent, "utf-8");
826
+ const typesContent = `/**
827
+ * Auto-generated route types
828
+ * Generated: ${manifest.generated}
829
+ */
830
+
831
+ import type { RouteParams } from '@flight-framework/router';
832
+
833
+ // Extract params from route patterns
834
+ ${manifest.routes.filter((r) => r.isDynamic).map((r) => {
835
+ const paramMatches = r.path.match(/:(\w+)/g) || [];
836
+ const params = paramMatches.map((p) => p.slice(1));
837
+ const typeName = r.path.replace(/[/:]/g, "_").replace(/^_/, "").replace(/_$/, "") || "Root";
838
+ return `export type ${typeName}Params = { ${params.map((p) => `${p}: string`).join("; ")} };`;
839
+ }).join("\n")}
840
+ `;
841
+ writeFileSync2(join3(outputDir, "types.ts"), typesContent, "utf-8");
842
+ }
843
+ async function generateRoutes(options) {
844
+ const { routesDir, outputDir } = options;
845
+ console.log(`Scanning routes in: ${routesDir}`);
846
+ const manifest = generateRouteManifest(routesDir);
847
+ const stats = [
848
+ `${manifest.routes.length} pages`,
849
+ `${manifest.layouts.length} layouts`,
850
+ `${manifest.loadingStates.length} loading states`,
851
+ `${manifest.errorBoundaries.length} error boundaries`,
852
+ `${manifest.notFoundPages.length} not-found pages`,
853
+ `${manifest.apiRoutes.length} API routes`
854
+ ].join(", ");
855
+ console.log(`Found: ${stats}`);
856
+ generateRoutesFile(manifest, outputDir);
857
+ console.log(`Generated route manifest in: ${outputDir}`);
858
+ return manifest;
859
+ }
860
+
861
+ // src/commands/routes-generate.ts
862
+ async function routesGenerateCommand(options = {}) {
863
+ const cwd = process.cwd();
864
+ const routesDir = options.routesDir ? resolve5(cwd, options.routesDir) : resolve5(cwd, "src/routes");
865
+ const outputDir = options.outputDir ? resolve5(cwd, options.outputDir) : resolve5(cwd, "src/.flight");
866
+ try {
867
+ const manifest = await generateRoutes({
868
+ routesDir,
869
+ outputDir,
870
+ watch: options.watch
871
+ });
872
+ console.log("\nRoute manifest generated successfully!");
873
+ console.log(` Pages: ${manifest.routes.length}`);
874
+ console.log(` API Routes: ${manifest.apiRoutes.length}`);
875
+ console.log(` Layouts: ${manifest.layouts.length}`);
876
+ } catch (error) {
877
+ console.error("Failed to generate routes:", error);
878
+ process.exit(1);
879
+ }
880
+ }
881
+
566
882
  // src/index.ts
567
883
  var cli = cac("flight");
568
884
  var LOGO = `
@@ -585,6 +901,7 @@ cli.command("create [name]", "Create a new Flight project").option("-t, --templa
585
901
  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);
586
902
  cli.command("build", "Build for production").option("--outDir <dir>", "Output directory").option("--sourcemap", "Generate source maps").option("--minify", "Minify output", { default: true }).action(buildCommand);
587
903
  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);
904
+ cli.command("routes:generate", "Generate route manifest from routes directory").option("--routesDir <dir>", "Routes directory", { default: "src/routes" }).option("--outputDir <dir>", "Output directory", { default: "src/.flight" }).action(routesGenerateCommand);
588
905
  function run() {
589
906
  try {
590
907
  cli.parse(process.argv, { run: false });