@flight-framework/cli 0.0.10 → 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/bin.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);
@@ -571,6 +593,292 @@ async function previewCommand(options) {
571
593
  }
572
594
  }
573
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
+
574
882
  // src/index.ts
575
883
  var cli = cac("flight");
576
884
  var LOGO = `
@@ -593,6 +901,7 @@ cli.command("create [name]", "Create a new Flight project").option("-t, --templa
593
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);
594
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);
595
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);
596
905
  function run() {
597
906
  try {
598
907
  cli.parse(process.argv, { run: false });