@flight-framework/cli 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -77
- package/dist/bin.js +416 -0
- package/dist/bin.js.map +1 -1
- package/dist/index.js +416 -0
- package/dist/index.js.map +1 -1
- package/package.json +53 -53
- package/templates/base/README.md.template +26 -26
- package/templates/base/_gitignore +25 -25
- package/templates/base/flight.config.ts.template +15 -15
- package/templates/base/styles/global.css +58 -58
- package/templates/htmx/index.html +18 -18
- package/templates/htmx/package.json.template +18 -18
- package/templates/htmx/vite.config.ts +6 -6
- package/templates/lit/index.html +14 -14
- package/templates/lit/package.json.template +21 -21
- package/templates/lit/src/app-root.ts +18 -18
- package/templates/lit/src/entry-client.ts +5 -5
- package/templates/lit/src/entry-server.ts +9 -9
- package/templates/lit/tsconfig.json +18 -18
- package/templates/lit/vite.config.ts +6 -6
- package/templates/preact/index.html +14 -14
- package/templates/preact/package.json.template +22 -22
- package/templates/preact/src/App.tsx +8 -8
- package/templates/preact/src/entry-client.tsx +11 -11
- package/templates/preact/src/entry-server.tsx +6 -6
- package/templates/preact/tsconfig.json +18 -18
- package/templates/preact/vite.config.ts +8 -8
- package/templates/qwik/index.html +14 -14
- package/templates/qwik/package.json.template +20 -20
- package/templates/qwik/src/App.tsx +10 -10
- package/templates/qwik/src/entry-client.tsx +4 -4
- package/templates/qwik/src/entry-server.tsx +9 -9
- package/templates/qwik/tsconfig.json +18 -18
- package/templates/qwik/vite.config.ts +8 -8
- package/templates/react/index.html +13 -13
- package/templates/react/package.json.template +24 -24
- package/templates/react/src/App.tsx +13 -13
- package/templates/react/src/context/RouterContext.tsx +63 -63
- package/templates/react/src/entry-client.tsx +19 -19
- package/templates/react/src/entry-server.tsx +17 -17
- package/templates/react/tsconfig.json +19 -19
- package/templates/react/vite.config.ts +12 -12
- package/templates/solid/index.html +14 -14
- package/templates/solid/package.json.template +21 -21
- package/templates/solid/src/App.tsx +8 -8
- package/templates/solid/src/entry-client.tsx +11 -11
- package/templates/solid/src/entry-server.tsx +6 -6
- package/templates/solid/tsconfig.json +18 -18
- package/templates/solid/vite.config.ts +8 -8
- package/templates/svelte/index.html +14 -14
- package/templates/svelte/package.json.template +21 -21
- package/templates/svelte/src/App.svelte +4 -4
- package/templates/svelte/src/entry-client.ts +7 -7
- package/templates/svelte/src/entry-server.ts +7 -7
- package/templates/svelte/tsconfig.json +17 -17
- package/templates/svelte/vite.config.ts +8 -8
- package/templates/vanilla/index.html +14 -14
- package/templates/vanilla/package.json.template +19 -19
- package/templates/vanilla/src/main.ts +10 -10
- package/templates/vanilla/tsconfig.json +16 -16
- package/templates/vanilla/vite.config.ts +6 -6
- package/templates/vue/index.html +14 -14
- package/templates/vue/package.json.template +21 -21
- package/templates/vue/src/App.vue +6 -6
- package/templates/vue/src/entry-client.ts +12 -12
- package/templates/vue/src/entry-server.ts +8 -8
- package/templates/vue/tsconfig.json +17 -17
- package/templates/vue/vite.config.ts +8 -8
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
# @flight-framework/cli
|
|
2
|
-
|
|
3
|
-
Command-line interface for Flight Framework.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g @flight-framework/cli
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or use via npx:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx @flight-framework/cli <command>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Commands
|
|
18
|
-
|
|
19
|
-
### create
|
|
20
|
-
|
|
21
|
-
Create a new Flight project.
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
flight create [project-name]
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Options:
|
|
28
|
-
|
|
29
|
-
| Option | Description |
|
|
30
|
-
|--------|-------------|
|
|
31
|
-
| `--ui <framework>` | UI framework: react, vue, svelte, solid, preact, qwik, lit, htmx |
|
|
32
|
-
| `--typescript` | Use TypeScript (default) |
|
|
33
|
-
| `--no-typescript` | Use JavaScript |
|
|
34
|
-
|
|
35
|
-
### dev
|
|
36
|
-
|
|
37
|
-
Start the development server.
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
flight dev
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Options:
|
|
44
|
-
|
|
45
|
-
| Option | Description | Default |
|
|
46
|
-
|--------|-------------|---------|
|
|
47
|
-
| `-p, --port` | Port number | `5173` |
|
|
48
|
-
| `-h, --host` | Host to bind | `localhost` |
|
|
49
|
-
| `--open` | Open browser | `false` |
|
|
50
|
-
| `--ssr` | Enable SSR | From config |
|
|
51
|
-
|
|
52
|
-
### build
|
|
53
|
-
|
|
54
|
-
Build for production.
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
flight build
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### start
|
|
61
|
-
|
|
62
|
-
Start the production server.
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
flight start
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Options:
|
|
69
|
-
|
|
70
|
-
| Option | Description | Default |
|
|
71
|
-
|--------|-------------|---------|
|
|
72
|
-
| `-p, --port` | Port number | `3000` |
|
|
73
|
-
| `-h, --host` | Host to bind | `0.0.0.0` |
|
|
74
|
-
|
|
75
|
-
## License
|
|
76
|
-
|
|
77
|
-
MIT
|
|
1
|
+
# @flight-framework/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for Flight Framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @flight-framework/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use via npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @flight-framework/cli <command>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### create
|
|
20
|
+
|
|
21
|
+
Create a new Flight project.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
flight create [project-name]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
|
|
29
|
+
| Option | Description |
|
|
30
|
+
|--------|-------------|
|
|
31
|
+
| `--ui <framework>` | UI framework: react, vue, svelte, solid, preact, qwik, lit, htmx |
|
|
32
|
+
| `--typescript` | Use TypeScript (default) |
|
|
33
|
+
| `--no-typescript` | Use JavaScript |
|
|
34
|
+
|
|
35
|
+
### dev
|
|
36
|
+
|
|
37
|
+
Start the development server.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
flight dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
|
|
45
|
+
| Option | Description | Default |
|
|
46
|
+
|--------|-------------|---------|
|
|
47
|
+
| `-p, --port` | Port number | `5173` |
|
|
48
|
+
| `-h, --host` | Host to bind | `localhost` |
|
|
49
|
+
| `--open` | Open browser | `false` |
|
|
50
|
+
| `--ssr` | Enable SSR | From config |
|
|
51
|
+
|
|
52
|
+
### build
|
|
53
|
+
|
|
54
|
+
Build for production.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
flight build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### start
|
|
61
|
+
|
|
62
|
+
Start the production server.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
flight start
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
|
|
70
|
+
| Option | Description | Default |
|
|
71
|
+
|--------|-------------|---------|
|
|
72
|
+
| `-p, --port` | Port number | `3000` |
|
|
73
|
+
| `-h, --host` | Host to bind | `0.0.0.0` |
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/dist/bin.js
CHANGED
|
@@ -879,6 +879,421 @@ async function routesGenerateCommand(options = {}) {
|
|
|
879
879
|
}
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
+
// src/commands/types-generate.ts
|
|
883
|
+
import { resolve as resolve7 } from "path";
|
|
884
|
+
|
|
885
|
+
// src/generators/typegen.ts
|
|
886
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3, watch } from "fs";
|
|
887
|
+
import { join as join4 } from "path";
|
|
888
|
+
function generateHeader(command) {
|
|
889
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
890
|
+
return `/**
|
|
891
|
+
* Auto-generated by Flight CLI
|
|
892
|
+
* Do not edit manually - changes will be overwritten
|
|
893
|
+
*
|
|
894
|
+
* Command: ${command}
|
|
895
|
+
* Generated: ${timestamp}
|
|
896
|
+
*/`;
|
|
897
|
+
}
|
|
898
|
+
function extractParams(routePath) {
|
|
899
|
+
const params = [];
|
|
900
|
+
const dynamicMatches = routePath.match(/:(\w+)/g);
|
|
901
|
+
if (dynamicMatches) {
|
|
902
|
+
params.push(...dynamicMatches.map((m) => m.slice(1)));
|
|
903
|
+
}
|
|
904
|
+
const catchAllMatches = routePath.match(/\*(\w+)/g);
|
|
905
|
+
if (catchAllMatches) {
|
|
906
|
+
params.push(...catchAllMatches.map((m) => m.slice(1)));
|
|
907
|
+
}
|
|
908
|
+
return params;
|
|
909
|
+
}
|
|
910
|
+
function isDynamicRoute(routePath) {
|
|
911
|
+
return routePath.includes(":") || routePath.includes("*");
|
|
912
|
+
}
|
|
913
|
+
function generateRouteTypes(routes) {
|
|
914
|
+
const pageRoutes = routes.filter((r) => !r.isApiRoute);
|
|
915
|
+
const apiRoutes = routes.filter((r) => r.isApiRoute);
|
|
916
|
+
const staticRoutes = pageRoutes.filter((r) => !r.isDynamic);
|
|
917
|
+
const dynamicRoutes = pageRoutes.filter((r) => r.isDynamic);
|
|
918
|
+
const appRoutesUnion = pageRoutes.length > 0 ? pageRoutes.map((r) => ` | '${r.path}'`).join("\n") : " | never";
|
|
919
|
+
const apiRoutesUnion = apiRoutes.length > 0 ? apiRoutes.map((r) => ` | '${r.path}'`).join("\n") : " | never";
|
|
920
|
+
const staticRoutesUnion = staticRoutes.length > 0 ? staticRoutes.map((r) => ` | '${r.path}'`).join("\n") : " | never";
|
|
921
|
+
const dynamicRoutesUnion = dynamicRoutes.length > 0 ? dynamicRoutes.map((r) => ` | '${r.path}'`).join("\n") : " | never";
|
|
922
|
+
const paramTypes = dynamicRoutes.map((r) => {
|
|
923
|
+
const params = extractParams(r.path);
|
|
924
|
+
const typeName = routePathToTypeName(r.path);
|
|
925
|
+
const paramDef = params.map((p) => `${p}: string`).join("; ");
|
|
926
|
+
return `export type ${typeName}Params = { ${paramDef} };`;
|
|
927
|
+
}).join("\n");
|
|
928
|
+
return `${generateHeader("flight types:generate --routes")}
|
|
929
|
+
|
|
930
|
+
// ============================================================================
|
|
931
|
+
// Route Types
|
|
932
|
+
// ============================================================================
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* All available page routes in the application
|
|
936
|
+
*/
|
|
937
|
+
export type AppRoutes =
|
|
938
|
+
${appRoutesUnion};
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* All available API routes in the application
|
|
942
|
+
*/
|
|
943
|
+
export type ApiRoutes =
|
|
944
|
+
${apiRoutesUnion};
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Static routes (no dynamic parameters)
|
|
948
|
+
*/
|
|
949
|
+
export type StaticRoutes =
|
|
950
|
+
${staticRoutesUnion};
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Dynamic routes (with parameters like :id or *slug)
|
|
954
|
+
*/
|
|
955
|
+
export type DynamicRoutes =
|
|
956
|
+
${dynamicRoutesUnion};
|
|
957
|
+
|
|
958
|
+
// ============================================================================
|
|
959
|
+
// Route Parameter Extraction
|
|
960
|
+
// ============================================================================
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Extract route parameters from a route pattern using template literal types.
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* type Params = ExtractRouteParams<'/users/:id'>; // { id: string }
|
|
967
|
+
* type BlogParams = ExtractRouteParams<'/blog/:year/:slug'>; // { year: string; slug: string }
|
|
968
|
+
*/
|
|
969
|
+
type ExtractRouteParams<T extends string> =
|
|
970
|
+
// Handle :param/rest pattern
|
|
971
|
+
T extends \`\${infer _Start}:\${infer Param}/\${infer Rest}\`
|
|
972
|
+
? { [K in Param]: string } & ExtractRouteParams<\`/\${Rest}\`>
|
|
973
|
+
// Handle :param at end
|
|
974
|
+
: T extends \`\${infer _Start}:\${infer Param}\`
|
|
975
|
+
? { [K in Param]: string }
|
|
976
|
+
// Handle *param (catch-all)
|
|
977
|
+
: T extends \`\${infer _Start}*\${infer Param}\`
|
|
978
|
+
? { [K in Param]: string }
|
|
979
|
+
// No params
|
|
980
|
+
: Record<string, never>;
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Get typed parameters for a specific route.
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* const params: RouteParams<'/users/:id'> = { id: '123' };
|
|
987
|
+
*/
|
|
988
|
+
export type RouteParams<T extends AppRoutes | ApiRoutes> = ExtractRouteParams<T>;
|
|
989
|
+
|
|
990
|
+
// ============================================================================
|
|
991
|
+
// Helper Types
|
|
992
|
+
// ============================================================================
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Check if a route requires parameters
|
|
996
|
+
*/
|
|
997
|
+
export type RequiresParams<T extends AppRoutes | ApiRoutes> =
|
|
998
|
+
RouteParams<T> extends Record<string, never> ? false : true;
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Props for a type-safe Link component
|
|
1002
|
+
*/
|
|
1003
|
+
export type TypedLinkProps<T extends AppRoutes> =
|
|
1004
|
+
RequiresParams<T> extends true
|
|
1005
|
+
? { to: T; params: RouteParams<T> }
|
|
1006
|
+
: { to: T; params?: never };
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Build a URL from a route pattern and parameters
|
|
1010
|
+
*/
|
|
1011
|
+
export type BuildUrl<T extends AppRoutes> =
|
|
1012
|
+
RequiresParams<T> extends true
|
|
1013
|
+
? (route: T, params: RouteParams<T>) => string
|
|
1014
|
+
: (route: T) => string;
|
|
1015
|
+
|
|
1016
|
+
// ============================================================================
|
|
1017
|
+
// Individual Route Parameter Types
|
|
1018
|
+
// ============================================================================
|
|
1019
|
+
|
|
1020
|
+
${paramTypes || "// No dynamic routes found"}
|
|
1021
|
+
`;
|
|
1022
|
+
}
|
|
1023
|
+
function routePathToTypeName(routePath) {
|
|
1024
|
+
return routePath.split("/").filter(Boolean).map((segment) => {
|
|
1025
|
+
const clean = segment.replace(/^[:*]/, "");
|
|
1026
|
+
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
1027
|
+
}).join("_") || "Root";
|
|
1028
|
+
}
|
|
1029
|
+
function parseEnvFile(content) {
|
|
1030
|
+
const server = [];
|
|
1031
|
+
const client = [];
|
|
1032
|
+
const lines = content.split("\n");
|
|
1033
|
+
for (const line of lines) {
|
|
1034
|
+
const trimmed = line.trim();
|
|
1035
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)(\?)?=/i);
|
|
1039
|
+
if (!match) {
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
const key = match[1];
|
|
1043
|
+
const optional = match[2] === "?";
|
|
1044
|
+
const envVar = { key, optional };
|
|
1045
|
+
if (key.startsWith("PUBLIC_")) {
|
|
1046
|
+
client.push(envVar);
|
|
1047
|
+
} else {
|
|
1048
|
+
server.push(envVar);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return { server, client };
|
|
1052
|
+
}
|
|
1053
|
+
function loadEnvFiles(projectRoot) {
|
|
1054
|
+
const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
|
|
1055
|
+
const merged = { server: [], client: [] };
|
|
1056
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1057
|
+
for (const envFile of envFiles) {
|
|
1058
|
+
const envPath = join4(projectRoot, envFile);
|
|
1059
|
+
if (!existsSync5(envPath)) {
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
const content = readFileSync3(envPath, "utf-8");
|
|
1063
|
+
const parsed = parseEnvFile(content);
|
|
1064
|
+
for (const envVar of parsed.server) {
|
|
1065
|
+
if (!seenKeys.has(envVar.key)) {
|
|
1066
|
+
seenKeys.add(envVar.key);
|
|
1067
|
+
merged.server.push(envVar);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
for (const envVar of parsed.client) {
|
|
1071
|
+
if (!seenKeys.has(envVar.key)) {
|
|
1072
|
+
seenKeys.add(envVar.key);
|
|
1073
|
+
merged.client.push(envVar);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return merged;
|
|
1078
|
+
}
|
|
1079
|
+
function generateEnvTypes(env) {
|
|
1080
|
+
const serverVars = env.server.sort((a, b) => a.key.localeCompare(b.key)).map((v) => ` ${v.key}${v.optional ? "?" : ""}: string;`).join("\n");
|
|
1081
|
+
const clientVars = env.client.sort((a, b) => a.key.localeCompare(b.key)).map((v) => ` ${v.key}${v.optional ? "?" : ""}: string;`).join("\n");
|
|
1082
|
+
return `${generateHeader("flight types:generate --env")}
|
|
1083
|
+
|
|
1084
|
+
// ============================================================================
|
|
1085
|
+
// Server-side Environment Variables
|
|
1086
|
+
// ============================================================================
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Server-side environment variables accessible via process.env
|
|
1090
|
+
* These are NOT exposed to the client.
|
|
1091
|
+
*/
|
|
1092
|
+
declare namespace NodeJS {
|
|
1093
|
+
interface ProcessEnv {
|
|
1094
|
+
${serverVars || " // No server environment variables defined"}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// ============================================================================
|
|
1099
|
+
// Client-side Environment Variables
|
|
1100
|
+
// ============================================================================
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Client-side environment variables accessible via import.meta.env
|
|
1104
|
+
* Only variables with PUBLIC_ prefix are included.
|
|
1105
|
+
*/
|
|
1106
|
+
interface ImportMetaEnv {
|
|
1107
|
+
${clientVars || " // No client environment variables defined"}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
interface ImportMeta {
|
|
1111
|
+
readonly env: ImportMetaEnv;
|
|
1112
|
+
}
|
|
1113
|
+
`;
|
|
1114
|
+
}
|
|
1115
|
+
function scanRoutesForTypes(routesDir) {
|
|
1116
|
+
if (!existsSync5(routesDir)) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
const routes = [];
|
|
1120
|
+
scanDirectoryRecursive(routesDir, "", routes);
|
|
1121
|
+
return routes;
|
|
1122
|
+
}
|
|
1123
|
+
function scanDirectoryRecursive(dir, basePath, results) {
|
|
1124
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
1125
|
+
for (const entry of entries) {
|
|
1126
|
+
const fullPath = join4(dir, entry.name);
|
|
1127
|
+
const relativePath = join4(basePath, entry.name);
|
|
1128
|
+
if (entry.isDirectory()) {
|
|
1129
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
scanDirectoryRecursive(fullPath, relativePath, results);
|
|
1133
|
+
} else if (entry.isFile()) {
|
|
1134
|
+
const route = parseRouteFile(entry.name, relativePath);
|
|
1135
|
+
if (route) {
|
|
1136
|
+
results.push(route);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function parseRouteFile(filename, relativePath) {
|
|
1142
|
+
if (/\.(page|route)\.(tsx?|jsx?)$/.test(filename)) {
|
|
1143
|
+
const urlPath = filePathToUrlPath2(relativePath);
|
|
1144
|
+
return {
|
|
1145
|
+
path: urlPath,
|
|
1146
|
+
filePath: relativePath.replace(/\\/g, "/"),
|
|
1147
|
+
isDynamic: isDynamicRoute(urlPath),
|
|
1148
|
+
isApiRoute: false
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const apiMatch = filename.match(/\.(get|post|put|patch|delete|options|head)\.(tsx?|jsx?)$/i);
|
|
1152
|
+
if (apiMatch) {
|
|
1153
|
+
const urlPath = filePathToUrlPath2(relativePath);
|
|
1154
|
+
return {
|
|
1155
|
+
path: urlPath,
|
|
1156
|
+
filePath: relativePath.replace(/\\/g, "/"),
|
|
1157
|
+
isDynamic: isDynamicRoute(urlPath),
|
|
1158
|
+
isApiRoute: true,
|
|
1159
|
+
httpMethod: apiMatch[1].toUpperCase()
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
function filePathToUrlPath2(filePath) {
|
|
1165
|
+
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");
|
|
1166
|
+
if (!urlPath.startsWith("/")) {
|
|
1167
|
+
urlPath = "/" + urlPath;
|
|
1168
|
+
}
|
|
1169
|
+
urlPath = urlPath.replace(/\/+/g, "/");
|
|
1170
|
+
return urlPath || "/";
|
|
1171
|
+
}
|
|
1172
|
+
async function generateTypes(options) {
|
|
1173
|
+
const {
|
|
1174
|
+
routesDir,
|
|
1175
|
+
outputDir,
|
|
1176
|
+
includeRoutes = true,
|
|
1177
|
+
includeEnv = false,
|
|
1178
|
+
projectRoot = process.cwd()
|
|
1179
|
+
} = options;
|
|
1180
|
+
const result = {
|
|
1181
|
+
routeTypes: "",
|
|
1182
|
+
envTypes: "",
|
|
1183
|
+
filesWritten: [],
|
|
1184
|
+
routeCount: 0,
|
|
1185
|
+
envVarCount: 0
|
|
1186
|
+
};
|
|
1187
|
+
if (!existsSync5(outputDir)) {
|
|
1188
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
1189
|
+
}
|
|
1190
|
+
if (includeRoutes) {
|
|
1191
|
+
const routes = scanRoutesForTypes(routesDir);
|
|
1192
|
+
result.routeCount = routes.length;
|
|
1193
|
+
result.routeTypes = generateRouteTypes(routes);
|
|
1194
|
+
const routeTypesPath = join4(outputDir, "routes.d.ts");
|
|
1195
|
+
writeFileSync3(routeTypesPath, result.routeTypes, "utf-8");
|
|
1196
|
+
result.filesWritten.push(routeTypesPath);
|
|
1197
|
+
console.log(`Generated route types: ${routes.length} routes`);
|
|
1198
|
+
}
|
|
1199
|
+
if (includeEnv) {
|
|
1200
|
+
const env = loadEnvFiles(projectRoot);
|
|
1201
|
+
result.envVarCount = env.server.length + env.client.length;
|
|
1202
|
+
result.envTypes = generateEnvTypes(env);
|
|
1203
|
+
const envTypesPath = join4(outputDir, "env.d.ts");
|
|
1204
|
+
writeFileSync3(envTypesPath, result.envTypes, "utf-8");
|
|
1205
|
+
result.filesWritten.push(envTypesPath);
|
|
1206
|
+
console.log(`Generated env types: ${env.server.length} server, ${env.client.length} client`);
|
|
1207
|
+
}
|
|
1208
|
+
return result;
|
|
1209
|
+
}
|
|
1210
|
+
function watchAndGenerate(options) {
|
|
1211
|
+
const { routesDir, debounce = 100, onRegenerate } = options;
|
|
1212
|
+
let timeout = null;
|
|
1213
|
+
const regenerate = async () => {
|
|
1214
|
+
try {
|
|
1215
|
+
const result = await generateTypes(options);
|
|
1216
|
+
onRegenerate?.(result);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
console.error("Type generation failed:", error.message);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
const debouncedRegenerate = () => {
|
|
1222
|
+
if (timeout) clearTimeout(timeout);
|
|
1223
|
+
timeout = setTimeout(regenerate, debounce);
|
|
1224
|
+
};
|
|
1225
|
+
regenerate();
|
|
1226
|
+
const watcher = watch(routesDir, { recursive: true }, (eventType, filename) => {
|
|
1227
|
+
if (filename && /\.(tsx?|jsx?)$/.test(filename)) {
|
|
1228
|
+
console.log(`Route file changed: ${filename}`);
|
|
1229
|
+
debouncedRegenerate();
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
return () => {
|
|
1233
|
+
watcher.close();
|
|
1234
|
+
if (timeout) clearTimeout(timeout);
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/commands/types-generate.ts
|
|
1239
|
+
async function typesGenerateCommand(options = {}) {
|
|
1240
|
+
const cwd = process.cwd();
|
|
1241
|
+
const routesDir = options.routesDir ? resolve7(cwd, options.routesDir) : resolve7(cwd, "src/routes");
|
|
1242
|
+
const outputDir = options.outputDir ? resolve7(cwd, options.outputDir) : resolve7(cwd, "src/.flight");
|
|
1243
|
+
const includeRoutes = options.routes ?? !options.env;
|
|
1244
|
+
const includeEnv = options.env ?? false;
|
|
1245
|
+
console.log("Flight Type Generator");
|
|
1246
|
+
console.log("---------------------");
|
|
1247
|
+
console.log(`Routes directory: ${routesDir}`);
|
|
1248
|
+
console.log(`Output directory: ${outputDir}`);
|
|
1249
|
+
console.log(`Generate routes: ${includeRoutes}`);
|
|
1250
|
+
console.log(`Generate env: ${includeEnv}`);
|
|
1251
|
+
console.log("");
|
|
1252
|
+
try {
|
|
1253
|
+
if (options.watch) {
|
|
1254
|
+
console.log("Watching for changes... (Ctrl+C to stop)");
|
|
1255
|
+
console.log("");
|
|
1256
|
+
const cleanup = watchAndGenerate({
|
|
1257
|
+
routesDir,
|
|
1258
|
+
outputDir,
|
|
1259
|
+
includeRoutes,
|
|
1260
|
+
includeEnv,
|
|
1261
|
+
projectRoot: cwd,
|
|
1262
|
+
onRegenerate: (result) => {
|
|
1263
|
+
console.log(`Regenerated: ${result.filesWritten.length} files`);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
process.on("SIGINT", () => {
|
|
1267
|
+
console.log("\nStopping watch mode...");
|
|
1268
|
+
cleanup();
|
|
1269
|
+
process.exit(0);
|
|
1270
|
+
});
|
|
1271
|
+
await new Promise(() => {
|
|
1272
|
+
});
|
|
1273
|
+
} else {
|
|
1274
|
+
const result = await generateTypes({
|
|
1275
|
+
routesDir,
|
|
1276
|
+
outputDir,
|
|
1277
|
+
includeRoutes,
|
|
1278
|
+
includeEnv,
|
|
1279
|
+
projectRoot: cwd
|
|
1280
|
+
});
|
|
1281
|
+
console.log("Type generation complete!");
|
|
1282
|
+
console.log("");
|
|
1283
|
+
console.log("Files written:");
|
|
1284
|
+
for (const file of result.filesWritten) {
|
|
1285
|
+
console.log(` - ${file}`);
|
|
1286
|
+
}
|
|
1287
|
+
console.log("");
|
|
1288
|
+
console.log("Add to your tsconfig.json includes:");
|
|
1289
|
+
console.log(' "include": ["src", "src/.flight/*.d.ts"]');
|
|
1290
|
+
}
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
console.error("Type generation failed:", error);
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
882
1297
|
// src/index.ts
|
|
883
1298
|
var cli = cac("flight");
|
|
884
1299
|
var LOGO = `
|
|
@@ -902,6 +1317,7 @@ cli.command("dev", "Start development server").option("-p, --port <port>", "Port
|
|
|
902
1317
|
cli.command("build", "Build for production").option("--outDir <dir>", "Output directory").option("--sourcemap", "Generate source maps").option("--minify", "Minify output", { default: true }).action(buildCommand);
|
|
903
1318
|
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
1319
|
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);
|
|
1320
|
+
cli.command("types:generate", "Generate TypeScript types for routes and environment").option("--routesDir <dir>", "Routes directory", { default: "src/routes" }).option("--outputDir <dir>", "Output directory", { default: "src/.flight" }).option("--routes", "Generate route types", { default: true }).option("--env", "Generate environment variable types").option("--watch", "Watch for changes and regenerate").action(typesGenerateCommand);
|
|
905
1321
|
function run() {
|
|
906
1322
|
try {
|
|
907
1323
|
cli.parse(process.argv, { run: false });
|