@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/bin.js +320 -3
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +320 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/base/flight.config.ts.template +7 -2
- package/templates/htmx/package.json.template +7 -3
- package/templates/lit/package.json.template +5 -3
- package/templates/preact/package.json.template +5 -3
- package/templates/qwik/package.json.template +5 -3
- package/templates/react/package.json.template +5 -3
- package/templates/solid/package.json.template +5 -3
- package/templates/svelte/package.json.template +5 -3
- package/templates/vanilla/package.json.template +7 -3
- package/templates/vue/package.json.template +5 -3
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:
|
|
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 });
|