fexapi 0.1.0 → 0.1.2
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 +142 -1
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +98 -0
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +51 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +114 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +81 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +250 -0
- package/dist/commands/serve.d.ts +12 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +68 -0
- package/dist/config/generated-spec.d.ts +3 -0
- package/dist/config/generated-spec.d.ts.map +1 -0
- package/dist/config/generated-spec.js +26 -0
- package/dist/config/runtime-config.d.ts +3 -0
- package/dist/config/runtime-config.d.ts.map +1 -0
- package/dist/config/runtime-config.js +97 -0
- package/dist/config/schema-definitions.d.ts +3 -0
- package/dist/config/schema-definitions.d.ts.map +1 -0
- package/dist/config/schema-definitions.js +90 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/index.js +74 -411
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.d.ts.map +1 -0
- package/dist/project/detect.js +113 -0
- package/dist/project/paths.d.ts +3 -0
- package/dist/project/paths.d.ts.map +1 -0
- package/dist/project/paths.js +28 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +4 -6
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +123 -43
- package/dist/types/config.d.ts +20 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/project.d.ts +7 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +2 -0
- package/package.json +56 -51
package/README.md
CHANGED
|
@@ -3,15 +3,156 @@
|
|
|
3
3
|
Frontend Experience API - Mock API generation CLI tool for local development and testing.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
|
+
|
|
6
7
|
```bash
|
|
7
8
|
npm install -g fexapi
|
|
8
9
|
```
|
|
9
10
|
|
|
10
11
|
## Usage
|
|
12
|
+
|
|
11
13
|
```bash
|
|
12
14
|
fexapi [options]
|
|
13
15
|
```
|
|
14
16
|
|
|
17
|
+
## Prisma-like Flow (Dynamic)
|
|
18
|
+
|
|
19
|
+
### 1) Initialize
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm dlx fexapi init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`fexapi init` runs an interactive setup wizard and asks:
|
|
26
|
+
|
|
27
|
+
- What port? (default: 3000)
|
|
28
|
+
- Enable CORS? (Y/n)
|
|
29
|
+
- Generate sample schemas? (Y/n)
|
|
30
|
+
|
|
31
|
+
Creates:
|
|
32
|
+
|
|
33
|
+
- `fexapi/schema.fexapi`
|
|
34
|
+
- `fexapi.config.json`
|
|
35
|
+
- `fexapi.config.js`
|
|
36
|
+
- `schemas/user.yaml` and `schemas/post.yaml` (if sample schemas are enabled)
|
|
37
|
+
|
|
38
|
+
### 2) Edit schema file
|
|
39
|
+
|
|
40
|
+
`fexapi/schema.fexapi` uses a simple DSL with only `:` and `,` (no semicolons):
|
|
41
|
+
|
|
42
|
+
```txt
|
|
43
|
+
# Server
|
|
44
|
+
port: 4100
|
|
45
|
+
|
|
46
|
+
# Routes
|
|
47
|
+
GET /users: id:uuid,name:name,email:email,age:number,phone:phone,pic:url,courseName:string
|
|
48
|
+
GET /courses: id:uuid,courseName:string,mentor:name
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3) Generate artifacts (updates migration)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx fexapi generate
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Generates:
|
|
58
|
+
|
|
59
|
+
- `fexapi/generated.api.json`
|
|
60
|
+
- `fexapi/migrations/schema.json`
|
|
61
|
+
|
|
62
|
+
### 4) Start server
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx fexapi run
|
|
66
|
+
# or
|
|
67
|
+
npx fexapi serve
|
|
68
|
+
# request/response logging
|
|
69
|
+
npx fexapi serve --log
|
|
70
|
+
# or dev watch mode (nodemon-like)
|
|
71
|
+
npx fexapi dev --watch
|
|
72
|
+
# watch mode + request logs
|
|
73
|
+
npx fexapi dev --watch --log
|
|
74
|
+
# or (inside local workspace package)
|
|
75
|
+
npm run serve
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Server port is read from `schema.fexapi` unless overridden by CLI `--port`.
|
|
79
|
+
|
|
80
|
+
`fexapi dev --watch` auto-reloads when these files change:
|
|
81
|
+
|
|
82
|
+
- `fexapi/schema.fexapi`
|
|
83
|
+
- `fexapi/generated.api.json`
|
|
84
|
+
- `fexapi.config.js`
|
|
85
|
+
- `fexapi.config.json`
|
|
86
|
+
- `schemas/*.yaml`
|
|
87
|
+
|
|
88
|
+
`fexapi serve --log` prints request logs like:
|
|
89
|
+
|
|
90
|
+
- `[GET] /users/1 → 200 (45ms)`
|
|
91
|
+
- `[POST] /posts → 201 (12ms)`
|
|
92
|
+
|
|
93
|
+
## Configuration File Support
|
|
94
|
+
|
|
95
|
+
Create a `fexapi.config.js` in your project root:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// fexapi.config.js
|
|
99
|
+
module.exports = {
|
|
100
|
+
port: 3000,
|
|
101
|
+
routes: {
|
|
102
|
+
"/users": { count: 50, schema: "user" },
|
|
103
|
+
"/posts": { count: 100, schema: "post" },
|
|
104
|
+
},
|
|
105
|
+
cors: true,
|
|
106
|
+
delay: 200,
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Then run:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
fexapi serve
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Notes:
|
|
117
|
+
|
|
118
|
+
- `port` sets the default server port (CLI `--port` still has priority).
|
|
119
|
+
- `routes` maps endpoint paths to generated payload settings.
|
|
120
|
+
- `schema` maps to files under `schemas/` (for example `schema: "user"` -> `schemas/user.yaml`); unknown names fall back to a generic record.
|
|
121
|
+
- `cors: true` enables CORS headers and OPTIONS preflight handling.
|
|
122
|
+
- `delay` adds response latency in milliseconds.
|
|
123
|
+
|
|
124
|
+
## Custom Schema Definitions
|
|
125
|
+
|
|
126
|
+
You can define custom schemas in YAML files under `schemas/`.
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# schemas/user.yaml
|
|
130
|
+
name:
|
|
131
|
+
type: string
|
|
132
|
+
faker: person.fullName
|
|
133
|
+
email:
|
|
134
|
+
type: string
|
|
135
|
+
faker: internet.email
|
|
136
|
+
age:
|
|
137
|
+
type: number
|
|
138
|
+
min: 18
|
|
139
|
+
max: 80
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Then reference it in `fexapi.config.js`:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
routes: {
|
|
146
|
+
"/users": { count: 50, schema: "user" }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Notes:
|
|
151
|
+
|
|
152
|
+
- Supported file extensions: `.yaml`, `.yml`
|
|
153
|
+
- Schema name is taken from filename (for example `schemas/user.yaml` -> `schema: "user"`)
|
|
154
|
+
- `faker` values map to Faker paths like `person.fullName`, `internet.email`
|
|
155
|
+
|
|
15
156
|
## Features
|
|
16
157
|
|
|
17
158
|
- Schema-based mock API generation
|
|
@@ -21,4 +162,4 @@ fexapi [options]
|
|
|
21
162
|
|
|
22
163
|
## License
|
|
23
164
|
|
|
24
|
-
MIT
|
|
165
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const parseInitOptions: (initArgs: string[]) => {
|
|
2
|
+
force: boolean;
|
|
3
|
+
} | {
|
|
4
|
+
error: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const parseGenerateOptions: (generateArgs: string[]) => {
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const parseServeOptions: (serveArgs: string[]) => {
|
|
10
|
+
host: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
logEnabled: boolean;
|
|
13
|
+
} | {
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const parseDevOptions: (devArgs: string[]) => {
|
|
17
|
+
host: string;
|
|
18
|
+
port?: number;
|
|
19
|
+
watchEnabled: boolean;
|
|
20
|
+
logEnabled: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAAE,KACjB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAWtC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,cAAc,MAAM,EAAE,KACrB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAQlB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,MAAM,EAAE,KAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAkDxE,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,EAAE,KAEf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,KAAK,EAAE,MAAM,CAAA;CAwDlB,CAAC"}
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDevOptions = exports.parseServeOptions = exports.parseGenerateOptions = exports.parseInitOptions = void 0;
|
|
4
|
+
const parseInitOptions = (initArgs) => {
|
|
5
|
+
const validFlags = new Set(["--force"]);
|
|
6
|
+
const invalidFlags = initArgs.filter((value) => value.startsWith("-") && !validFlags.has(value));
|
|
7
|
+
if (invalidFlags.length > 0) {
|
|
8
|
+
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
9
|
+
}
|
|
10
|
+
return { force: initArgs.includes("--force") };
|
|
11
|
+
};
|
|
12
|
+
exports.parseInitOptions = parseInitOptions;
|
|
13
|
+
const parseGenerateOptions = (generateArgs) => {
|
|
14
|
+
const invalidFlags = generateArgs.filter((value) => value.startsWith("-"));
|
|
15
|
+
if (invalidFlags.length > 0) {
|
|
16
|
+
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
17
|
+
}
|
|
18
|
+
return {};
|
|
19
|
+
};
|
|
20
|
+
exports.parseGenerateOptions = parseGenerateOptions;
|
|
21
|
+
const parseServeOptions = (serveArgs) => {
|
|
22
|
+
const getFlagValue = (flagName) => {
|
|
23
|
+
const index = serveArgs.indexOf(flagName);
|
|
24
|
+
if (index === -1) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const value = serveArgs[index + 1];
|
|
28
|
+
if (!value || value.startsWith("-")) {
|
|
29
|
+
return { error: `Missing value for ${flagName}` };
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
const unknownFlags = serveArgs.filter((value) => value.startsWith("-") &&
|
|
34
|
+
value !== "--host" &&
|
|
35
|
+
value !== "--port" &&
|
|
36
|
+
value !== "--log");
|
|
37
|
+
if (unknownFlags.length > 0) {
|
|
38
|
+
return { error: `Unknown option(s): ${unknownFlags.join(", ")}` };
|
|
39
|
+
}
|
|
40
|
+
const hostValue = getFlagValue("--host");
|
|
41
|
+
if (hostValue && typeof hostValue !== "string") {
|
|
42
|
+
return hostValue;
|
|
43
|
+
}
|
|
44
|
+
const portValue = getFlagValue("--port");
|
|
45
|
+
if (portValue && typeof portValue !== "string") {
|
|
46
|
+
return portValue;
|
|
47
|
+
}
|
|
48
|
+
const host = hostValue ?? "127.0.0.1";
|
|
49
|
+
const port = portValue ? Number(portValue) : undefined;
|
|
50
|
+
if (port !== undefined &&
|
|
51
|
+
(!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
52
|
+
return { error: `Invalid port: ${portValue ?? ""}`.trim() };
|
|
53
|
+
}
|
|
54
|
+
return { host, port, logEnabled: serveArgs.includes("--log") };
|
|
55
|
+
};
|
|
56
|
+
exports.parseServeOptions = parseServeOptions;
|
|
57
|
+
const parseDevOptions = (devArgs) => {
|
|
58
|
+
const getFlagValue = (flagName) => {
|
|
59
|
+
const index = devArgs.indexOf(flagName);
|
|
60
|
+
if (index === -1) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const value = devArgs[index + 1];
|
|
64
|
+
if (!value || value.startsWith("-")) {
|
|
65
|
+
return { error: `Missing value for ${flagName}` };
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
};
|
|
69
|
+
const unknownFlags = devArgs.filter((value) => value.startsWith("-") &&
|
|
70
|
+
value !== "--host" &&
|
|
71
|
+
value !== "--port" &&
|
|
72
|
+
value !== "--watch" &&
|
|
73
|
+
value !== "--log");
|
|
74
|
+
if (unknownFlags.length > 0) {
|
|
75
|
+
return { error: `Unknown option(s): ${unknownFlags.join(", ")}` };
|
|
76
|
+
}
|
|
77
|
+
const hostValue = getFlagValue("--host");
|
|
78
|
+
if (hostValue && typeof hostValue !== "string") {
|
|
79
|
+
return hostValue;
|
|
80
|
+
}
|
|
81
|
+
const portValue = getFlagValue("--port");
|
|
82
|
+
if (portValue && typeof portValue !== "string") {
|
|
83
|
+
return portValue;
|
|
84
|
+
}
|
|
85
|
+
const host = hostValue ?? "127.0.0.1";
|
|
86
|
+
const port = portValue ? Number(portValue) : undefined;
|
|
87
|
+
if (port !== undefined &&
|
|
88
|
+
(!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
89
|
+
return { error: `Invalid port: ${portValue ?? ""}`.trim() };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
host,
|
|
93
|
+
port,
|
|
94
|
+
watchEnabled: devArgs.includes("--watch"),
|
|
95
|
+
logEnabled: devArgs.includes("--log"),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
exports.parseDevOptions = parseDevOptions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,YAgDrB,CAAC"}
|
package/dist/cli/help.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printHelp = void 0;
|
|
4
|
+
const printHelp = () => {
|
|
5
|
+
console.log("fexapi-cli");
|
|
6
|
+
console.log("");
|
|
7
|
+
console.log("Usage:");
|
|
8
|
+
console.log(" fexapi init [--force]");
|
|
9
|
+
console.log(" fexapi generate");
|
|
10
|
+
console.log(" fexapi dev [--watch] [--host <host>] [--port <number>] [--log]");
|
|
11
|
+
console.log(" fexapi serve [--host <host>] [--port <number>] [--log]");
|
|
12
|
+
console.log(" fexapi run [--host <host>] [--port <number>] [--log]");
|
|
13
|
+
console.log(" fexapi [--host <host>] [--port <number>] [--log]");
|
|
14
|
+
console.log(" fexapi --help");
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log("Examples:");
|
|
17
|
+
console.log(" fexapi init");
|
|
18
|
+
console.log(" fexapi init --force");
|
|
19
|
+
console.log(" fexapi generate");
|
|
20
|
+
console.log(" fexapi dev --watch");
|
|
21
|
+
console.log(" fexapi dev --watch --log");
|
|
22
|
+
console.log(" fexapi serve --log");
|
|
23
|
+
console.log(" fexapi serve --host 127.0.0.1 --port 5000");
|
|
24
|
+
console.log(" fexapi --port 4000");
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("Package manager usage:");
|
|
27
|
+
console.log(" npx fexapi init");
|
|
28
|
+
console.log(" pnpm dlx fexapi init");
|
|
29
|
+
console.log(" yarn dlx fexapi init");
|
|
30
|
+
console.log("");
|
|
31
|
+
console.log("`fexapi init` creates:");
|
|
32
|
+
console.log(" fexapi.config.json");
|
|
33
|
+
console.log(" fexapi.config.js");
|
|
34
|
+
console.log(" fexapi/schema.fexapi");
|
|
35
|
+
console.log(" schemas/*.yaml (optional, via wizard)");
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log("Init wizard asks:");
|
|
38
|
+
console.log(" What port? (default: 3000)");
|
|
39
|
+
console.log(" Enable CORS? (Y/n)");
|
|
40
|
+
console.log(" Generate sample schemas? (Y/n)");
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log("Then run:");
|
|
43
|
+
console.log(" # edit fexapi/schema.fexapi");
|
|
44
|
+
console.log(" fexapi generate");
|
|
45
|
+
console.log(" fexapi run");
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log("Generate output:");
|
|
48
|
+
console.log(" fexapi/generated.api.json");
|
|
49
|
+
console.log(" fexapi/migrations/schema.json");
|
|
50
|
+
};
|
|
51
|
+
exports.printHelp = printHelp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,aAAa,GAAI,2CAK3B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,KAAG,MAuHH,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDevCommand = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const paths_1 = require("../project/paths");
|
|
7
|
+
const serve_1 = require("./serve");
|
|
8
|
+
const WATCH_DEBOUNCE_MS = 150;
|
|
9
|
+
const normalizePath = (pathValue) => {
|
|
10
|
+
return pathValue.replace(/\\/g, "/").toLowerCase();
|
|
11
|
+
};
|
|
12
|
+
const isWatchedPath = (projectRoot, changedPath) => {
|
|
13
|
+
const relativePath = normalizePath((0, node_path_1.relative)(projectRoot, changedPath));
|
|
14
|
+
if (relativePath === "fexapi.config.js" ||
|
|
15
|
+
relativePath === "fexapi.config.json") {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (relativePath.startsWith("fexapi/")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return (relativePath.startsWith("schemas/") &&
|
|
22
|
+
(relativePath.endsWith(".yaml") || relativePath.endsWith(".yml")));
|
|
23
|
+
};
|
|
24
|
+
const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
25
|
+
if (!watchEnabled) {
|
|
26
|
+
return (0, serve_1.serveProject)({ host, port, logEnabled });
|
|
27
|
+
}
|
|
28
|
+
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
29
|
+
if (!projectRoot) {
|
|
30
|
+
console.error("Could not find package.json in this directory or parent directories.");
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
let currentServer = (0, serve_1.createProjectServer)({ host, port, logEnabled });
|
|
34
|
+
if (!currentServer) {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
console.log("Watch mode enabled. Restarting on config/schema changes...");
|
|
38
|
+
let restartTimer;
|
|
39
|
+
let restartQueued = false;
|
|
40
|
+
let restartInProgress = false;
|
|
41
|
+
const restartServer = async (reason) => {
|
|
42
|
+
if (!currentServer) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (restartInProgress) {
|
|
46
|
+
restartQueued = true;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
restartInProgress = true;
|
|
50
|
+
console.log(`\n[watch] change detected (${reason})`);
|
|
51
|
+
await new Promise((resolve) => {
|
|
52
|
+
currentServer?.close(() => {
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
currentServer = (0, serve_1.createProjectServer)({ host, port, logEnabled });
|
|
57
|
+
restartInProgress = false;
|
|
58
|
+
if (restartQueued) {
|
|
59
|
+
restartQueued = false;
|
|
60
|
+
await restartServer("queued changes");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const scheduleRestart = (reason) => {
|
|
64
|
+
if (restartTimer) {
|
|
65
|
+
clearTimeout(restartTimer);
|
|
66
|
+
}
|
|
67
|
+
restartTimer = setTimeout(() => {
|
|
68
|
+
void restartServer(reason);
|
|
69
|
+
}, WATCH_DEBOUNCE_MS);
|
|
70
|
+
};
|
|
71
|
+
const activeWatchers = [];
|
|
72
|
+
const watchTargets = [
|
|
73
|
+
(0, node_path_1.join)(projectRoot, "fexapi"),
|
|
74
|
+
(0, node_path_1.join)(projectRoot, "schemas"),
|
|
75
|
+
projectRoot,
|
|
76
|
+
];
|
|
77
|
+
for (const watchTarget of watchTargets) {
|
|
78
|
+
if (!(0, node_fs_1.existsSync)(watchTarget)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const watcher = (0, node_fs_1.watch)(watchTarget, { recursive: true }, (_event, file) => {
|
|
82
|
+
if (!file) {
|
|
83
|
+
scheduleRestart("unknown file");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const changedPath = (0, node_path_1.join)(watchTarget, file.toString());
|
|
87
|
+
if (isWatchedPath(projectRoot, changedPath)) {
|
|
88
|
+
scheduleRestart((0, node_path_1.relative)(projectRoot, changedPath));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
activeWatchers.push(watcher);
|
|
92
|
+
}
|
|
93
|
+
const cleanupAndExit = async () => {
|
|
94
|
+
if (restartTimer) {
|
|
95
|
+
clearTimeout(restartTimer);
|
|
96
|
+
restartTimer = undefined;
|
|
97
|
+
}
|
|
98
|
+
for (const watcher of activeWatchers) {
|
|
99
|
+
watcher.close();
|
|
100
|
+
}
|
|
101
|
+
await new Promise((resolve) => {
|
|
102
|
+
currentServer?.close(() => resolve());
|
|
103
|
+
});
|
|
104
|
+
process.exit(0);
|
|
105
|
+
};
|
|
106
|
+
process.on("SIGINT", () => {
|
|
107
|
+
void cleanupAndExit();
|
|
108
|
+
});
|
|
109
|
+
process.on("SIGTERM", () => {
|
|
110
|
+
void cleanupAndExit();
|
|
111
|
+
});
|
|
112
|
+
return 0;
|
|
113
|
+
};
|
|
114
|
+
exports.runDevCommand = runDevCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,kBAAkB,QAAO,MAyGrC,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateFromSchema = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
|
+
const paths_1 = require("../project/paths");
|
|
8
|
+
const schema_1 = require("../schema");
|
|
9
|
+
const generateFromSchema = () => {
|
|
10
|
+
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
11
|
+
if (!projectRoot) {
|
|
12
|
+
console.error("Could not find package.json in this directory or parent directories.");
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
const schemaPath = (0, node_path_1.join)(projectRoot, "fexapi", "schema.fexapi");
|
|
16
|
+
const generatedPath = (0, node_path_1.join)(projectRoot, "fexapi", "generated.api.json");
|
|
17
|
+
const migrationsDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "migrations");
|
|
18
|
+
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
19
|
+
if (!(0, node_fs_1.existsSync)(schemaPath)) {
|
|
20
|
+
console.error(`Schema file not found: ${schemaPath}`);
|
|
21
|
+
console.error("Run `fexapi init` first.");
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
const schemaText = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
|
|
25
|
+
const parsed = (0, schema_1.parseFexapiSchema)(schemaText);
|
|
26
|
+
if (parsed.errors.length > 0 || !parsed.schema) {
|
|
27
|
+
console.error("Failed to generate API from schema.fexapi");
|
|
28
|
+
for (const error of parsed.errors) {
|
|
29
|
+
console.error(`- ${error}`);
|
|
30
|
+
}
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const generated = {
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
generatedAt: new Date().toISOString(),
|
|
36
|
+
port: parsed.schema.port,
|
|
37
|
+
routes: parsed.schema.routes,
|
|
38
|
+
};
|
|
39
|
+
(0, node_fs_1.mkdirSync)(migrationsDirectoryPath, { recursive: true });
|
|
40
|
+
const existingMigrationFiles = (0, node_fs_1.readdirSync)(migrationsDirectoryPath, {
|
|
41
|
+
withFileTypes: true,
|
|
42
|
+
})
|
|
43
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
44
|
+
.map((entry) => (0, node_path_1.join)(migrationsDirectoryPath, entry.name));
|
|
45
|
+
for (const migrationFilePath of existingMigrationFiles) {
|
|
46
|
+
(0, node_fs_1.unlinkSync)(migrationFilePath);
|
|
47
|
+
}
|
|
48
|
+
const migrationId = new Date().toISOString().replace(/[.:]/g, "-");
|
|
49
|
+
const migrationPath = (0, node_path_1.join)(migrationsDirectoryPath, "schema.json");
|
|
50
|
+
const migration = {
|
|
51
|
+
migrationId,
|
|
52
|
+
sourceSchema: "fexapi/schema.fexapi",
|
|
53
|
+
createdAt: generated.generatedAt,
|
|
54
|
+
port: parsed.schema.port,
|
|
55
|
+
routes: parsed.schema.routes,
|
|
56
|
+
};
|
|
57
|
+
(0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
|
|
58
|
+
(0, node_fs_1.writeFileSync)(migrationPath, `${JSON.stringify(migration, null, 2)}\n`, "utf-8");
|
|
59
|
+
let existingConfig = {};
|
|
60
|
+
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
61
|
+
try {
|
|
62
|
+
existingConfig = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
existingConfig = {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const updatedConfig = {
|
|
69
|
+
...existingConfig,
|
|
70
|
+
schemaPath: "fexapi/schema.fexapi",
|
|
71
|
+
generatedPath: constants_1.GENERATED_SPEC_RELATIVE_PATH,
|
|
72
|
+
lastGeneratedAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
|
|
75
|
+
console.log(`Generated API spec at ${generatedPath}`);
|
|
76
|
+
console.log(`Migration updated at ${migrationPath}`);
|
|
77
|
+
console.log(`Routes generated: ${parsed.schema.routes.length}`);
|
|
78
|
+
console.log(`Configured server port: ${parsed.schema.port}`);
|
|
79
|
+
return 0;
|
|
80
|
+
};
|
|
81
|
+
exports.generateFromSchema = generateFromSchema;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAwKA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CAmJjB,CAAC"}
|