fexapi 0.1.4 → 0.2.0
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 +5 -5
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +60 -0
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +48 -47
- package/dist/cli/ui.d.ts +19 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +55 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +6 -9
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +11 -9
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +48 -26
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +7 -6
- package/dist/config/schema-definitions.js +5 -5
- package/dist/index.js +30 -21
- package/dist/project/detect.d.ts.map +1 -1
- package/dist/project/detect.js +78 -10
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +59 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +59 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Creates:
|
|
|
33
33
|
- `fexapi/schema.fexapi`
|
|
34
34
|
- `fexapi.config.json`
|
|
35
35
|
- `fexapi.config.js`
|
|
36
|
-
- `schemas/user.yaml` and `schemas/post.yaml` (if sample schemas are enabled)
|
|
36
|
+
- `fexapi/schemas/user.yaml` and `fexapi/schemas/post.yaml` (if sample schemas are enabled)
|
|
37
37
|
|
|
38
38
|
### 2) Edit schema file
|
|
39
39
|
|
|
@@ -141,16 +141,16 @@ Notes:
|
|
|
141
141
|
|
|
142
142
|
- `port` sets the default server port (CLI `--port` still has priority).
|
|
143
143
|
- `routes` maps endpoint paths to generated payload settings.
|
|
144
|
-
- `schema` maps to files under `schemas/` (for example `schema: "user"` -> `schemas/user.yaml`); unknown names fall back to a generic record.
|
|
144
|
+
- `schema` maps to files under `fexapi/schemas/` (for example `schema: "user"` -> `fexapi/schemas/user.yaml`); unknown names fall back to a generic record.
|
|
145
145
|
- `cors: true` enables CORS headers and OPTIONS preflight handling.
|
|
146
146
|
- `delay` adds response latency in milliseconds.
|
|
147
147
|
|
|
148
148
|
## Custom Schema Definitions
|
|
149
149
|
|
|
150
|
-
You can define custom schemas in YAML files under `schemas/`.
|
|
150
|
+
You can define custom schemas in YAML files under `fexapi/schemas/`.
|
|
151
151
|
|
|
152
152
|
```yaml
|
|
153
|
-
# schemas/user.yaml
|
|
153
|
+
# fexapi/schemas/user.yaml
|
|
154
154
|
name:
|
|
155
155
|
type: string
|
|
156
156
|
faker: person.fullName
|
|
@@ -174,7 +174,7 @@ routes: {
|
|
|
174
174
|
Notes:
|
|
175
175
|
|
|
176
176
|
- Supported file extensions: `.yaml`, `.yml`
|
|
177
|
-
- Schema name is taken from filename (for example `schemas/user.yaml` -> `schema: "user"`)
|
|
177
|
+
- Schema name is taken from filename (for example `fexapi/schemas/user.yaml` -> `schema: "user"`)
|
|
178
178
|
- `faker` values map to Faker paths like `person.fullName`, `internet.email`
|
|
179
179
|
|
|
180
180
|
## Features
|
package/dist/cli/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAAE,KACjB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAgBtC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,cAAc,MAAM,EAAE,KACrB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAalB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,EAAE,KACnB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAalB,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;CA6ExE,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;CAoFlB,CAAC"}
|
package/dist/cli/args.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseDevOptions = exports.parseServeOptions = exports.parseFormatOptions = exports.parseGenerateOptions = exports.parseInitOptions = void 0;
|
|
4
|
+
const findDuplicateFlags = (args, flags) => {
|
|
5
|
+
return flags.filter((flag) => args.filter((value) => value === flag).length > 1);
|
|
6
|
+
};
|
|
4
7
|
const parseInitOptions = (initArgs) => {
|
|
5
8
|
const validFlags = new Set(["--force"]);
|
|
6
9
|
const invalidFlags = initArgs.filter((value) => value.startsWith("-") && !validFlags.has(value));
|
|
7
10
|
if (invalidFlags.length > 0) {
|
|
8
11
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
9
12
|
}
|
|
13
|
+
const positionalArgs = initArgs.filter((value) => !value.startsWith("-"));
|
|
14
|
+
if (positionalArgs.length > 0) {
|
|
15
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
16
|
+
}
|
|
10
17
|
return { force: initArgs.includes("--force") };
|
|
11
18
|
};
|
|
12
19
|
exports.parseInitOptions = parseInitOptions;
|
|
@@ -15,6 +22,10 @@ const parseGenerateOptions = (generateArgs) => {
|
|
|
15
22
|
if (invalidFlags.length > 0) {
|
|
16
23
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
17
24
|
}
|
|
25
|
+
const positionalArgs = generateArgs.filter((value) => !value.startsWith("-"));
|
|
26
|
+
if (positionalArgs.length > 0) {
|
|
27
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
28
|
+
}
|
|
18
29
|
return {};
|
|
19
30
|
};
|
|
20
31
|
exports.parseGenerateOptions = parseGenerateOptions;
|
|
@@ -23,10 +34,22 @@ const parseFormatOptions = (formatArgs) => {
|
|
|
23
34
|
if (invalidFlags.length > 0) {
|
|
24
35
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
25
36
|
}
|
|
37
|
+
const positionalArgs = formatArgs.filter((value) => !value.startsWith("-"));
|
|
38
|
+
if (positionalArgs.length > 0) {
|
|
39
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
40
|
+
}
|
|
26
41
|
return {};
|
|
27
42
|
};
|
|
28
43
|
exports.parseFormatOptions = parseFormatOptions;
|
|
29
44
|
const parseServeOptions = (serveArgs) => {
|
|
45
|
+
const duplicateFlags = findDuplicateFlags(serveArgs, [
|
|
46
|
+
"--host",
|
|
47
|
+
"--port",
|
|
48
|
+
"--log",
|
|
49
|
+
]);
|
|
50
|
+
if (duplicateFlags.length > 0) {
|
|
51
|
+
return { error: `Duplicate option(s): ${duplicateFlags.join(", ")}` };
|
|
52
|
+
}
|
|
30
53
|
const getFlagValue = (flagName) => {
|
|
31
54
|
const index = serveArgs.indexOf(flagName);
|
|
32
55
|
if (index === -1) {
|
|
@@ -53,6 +76,20 @@ const parseServeOptions = (serveArgs) => {
|
|
|
53
76
|
if (portValue && typeof portValue !== "string") {
|
|
54
77
|
return portValue;
|
|
55
78
|
}
|
|
79
|
+
const consumedIndexes = new Set();
|
|
80
|
+
serveArgs.forEach((value, index) => {
|
|
81
|
+
if (value === "--log") {
|
|
82
|
+
consumedIndexes.add(index);
|
|
83
|
+
}
|
|
84
|
+
if ((value === "--host" || value === "--port") && serveArgs[index + 1]) {
|
|
85
|
+
consumedIndexes.add(index);
|
|
86
|
+
consumedIndexes.add(index + 1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const positionalArgs = serveArgs.filter((_value, index) => !consumedIndexes.has(index));
|
|
90
|
+
if (positionalArgs.length > 0) {
|
|
91
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
92
|
+
}
|
|
56
93
|
const host = hostValue ?? "127.0.0.1";
|
|
57
94
|
const port = portValue ? Number(portValue) : undefined;
|
|
58
95
|
if (port !== undefined &&
|
|
@@ -63,6 +100,15 @@ const parseServeOptions = (serveArgs) => {
|
|
|
63
100
|
};
|
|
64
101
|
exports.parseServeOptions = parseServeOptions;
|
|
65
102
|
const parseDevOptions = (devArgs) => {
|
|
103
|
+
const duplicateFlags = findDuplicateFlags(devArgs, [
|
|
104
|
+
"--host",
|
|
105
|
+
"--port",
|
|
106
|
+
"--watch",
|
|
107
|
+
"--log",
|
|
108
|
+
]);
|
|
109
|
+
if (duplicateFlags.length > 0) {
|
|
110
|
+
return { error: `Duplicate option(s): ${duplicateFlags.join(", ")}` };
|
|
111
|
+
}
|
|
66
112
|
const getFlagValue = (flagName) => {
|
|
67
113
|
const index = devArgs.indexOf(flagName);
|
|
68
114
|
if (index === -1) {
|
|
@@ -90,6 +136,20 @@ const parseDevOptions = (devArgs) => {
|
|
|
90
136
|
if (portValue && typeof portValue !== "string") {
|
|
91
137
|
return portValue;
|
|
92
138
|
}
|
|
139
|
+
const consumedIndexes = new Set();
|
|
140
|
+
devArgs.forEach((value, index) => {
|
|
141
|
+
if (value === "--watch" || value === "--log") {
|
|
142
|
+
consumedIndexes.add(index);
|
|
143
|
+
}
|
|
144
|
+
if ((value === "--host" || value === "--port") && devArgs[index + 1]) {
|
|
145
|
+
consumedIndexes.add(index);
|
|
146
|
+
consumedIndexes.add(index + 1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const positionalArgs = devArgs.filter((_value, index) => !consumedIndexes.has(index));
|
|
150
|
+
if (positionalArgs.length > 0) {
|
|
151
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
152
|
+
}
|
|
93
153
|
const host = hostValue ?? "127.0.0.1";
|
|
94
154
|
const port = portValue ? Number(portValue) : undefined;
|
|
95
155
|
if (port !== undefined &&
|
package/dist/cli/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,YAgErB,CAAC"}
|
package/dist/cli/help.js
CHANGED
|
@@ -1,53 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printHelp = void 0;
|
|
4
|
+
const ui_1 = require("./ui");
|
|
4
5
|
const printHelp = () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
console.log("Usage
|
|
8
|
-
console.log("
|
|
9
|
-
console.log("
|
|
10
|
-
console.log("
|
|
11
|
-
console.log("
|
|
12
|
-
console.log("
|
|
13
|
-
console.log("
|
|
14
|
-
console.log("
|
|
15
|
-
console.log("
|
|
16
|
-
|
|
17
|
-
console.log("Examples
|
|
18
|
-
console.log("
|
|
19
|
-
console.log("
|
|
20
|
-
console.log("
|
|
21
|
-
console.log("
|
|
22
|
-
console.log("
|
|
23
|
-
console.log("
|
|
24
|
-
console.log("
|
|
25
|
-
console.log("
|
|
26
|
-
console.log("
|
|
27
|
-
|
|
28
|
-
console.log("Package
|
|
29
|
-
console.log("
|
|
30
|
-
console.log("
|
|
31
|
-
console.log("
|
|
32
|
-
|
|
33
|
-
console.log("
|
|
34
|
-
console.log("
|
|
35
|
-
console.log("
|
|
36
|
-
console.log("
|
|
37
|
-
console.log("
|
|
38
|
-
|
|
39
|
-
console.log("Init wizard asks
|
|
40
|
-
console.log("
|
|
41
|
-
console.log("
|
|
42
|
-
console.log("
|
|
43
|
-
|
|
44
|
-
console.log("Then run
|
|
45
|
-
console.log("
|
|
46
|
-
console.log("
|
|
47
|
-
console.log("
|
|
48
|
-
|
|
49
|
-
console.log("Generate output
|
|
50
|
-
console.log("
|
|
51
|
-
console.log("
|
|
6
|
+
(0, ui_1.printBanner)();
|
|
7
|
+
(0, ui_1.printSpacer)();
|
|
8
|
+
console.log(ui_1.ui.bold("Usage"));
|
|
9
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi init [--force]")}`);
|
|
10
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi generate")}`);
|
|
11
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi format")}`);
|
|
12
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi dev [--watch] [--host <host>] [--port <number>] [--log]")}`);
|
|
13
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi serve [--host <host>] [--port <number>] [--log]")}`);
|
|
14
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi run [--host <host>] [--port <number>] [--log]")}`);
|
|
15
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi [--host <host>] [--port <number>] [--log]")}`);
|
|
16
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi --help")}`);
|
|
17
|
+
(0, ui_1.printSpacer)();
|
|
18
|
+
console.log(ui_1.ui.bold("Examples"));
|
|
19
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi init")}`);
|
|
20
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi init --force")}`);
|
|
21
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi generate")}`);
|
|
22
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi format")}`);
|
|
23
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi dev --watch")}`);
|
|
24
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi dev --watch --log")}`);
|
|
25
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi serve --log")}`);
|
|
26
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi serve --host 127.0.0.1 --port 5000")}`);
|
|
27
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi --port 4000")}`);
|
|
28
|
+
(0, ui_1.printSpacer)();
|
|
29
|
+
console.log(ui_1.ui.bold("Package Manager Usage"));
|
|
30
|
+
console.log(` ${(0, ui_1.formatCommand)("npx fexapi init")}`);
|
|
31
|
+
console.log(` ${(0, ui_1.formatCommand)("pnpm dlx fexapi init")}`);
|
|
32
|
+
console.log(` ${(0, ui_1.formatCommand)("yarn dlx fexapi init")}`);
|
|
33
|
+
(0, ui_1.printSpacer)();
|
|
34
|
+
console.log(ui_1.ui.bold("fexapi init creates"));
|
|
35
|
+
console.log(` ${ui_1.ui.dim("fexapi.config.json")}`);
|
|
36
|
+
console.log(` ${ui_1.ui.dim("fexapi.config.js")}`);
|
|
37
|
+
console.log(` ${ui_1.ui.dim("fexapi/schema.fexapi")}`);
|
|
38
|
+
console.log(` ${ui_1.ui.dim("fexapi/schemas/*.yaml (optional, via wizard)")}`);
|
|
39
|
+
(0, ui_1.printSpacer)();
|
|
40
|
+
console.log(ui_1.ui.bold("Init wizard asks"));
|
|
41
|
+
console.log(` ${ui_1.ui.dim("What port? (default: 3000)")}`);
|
|
42
|
+
console.log(` ${ui_1.ui.dim("Enable CORS? (Y/n)")}`);
|
|
43
|
+
console.log(` ${ui_1.ui.dim("Generate sample schemas? (Y/n)")}`);
|
|
44
|
+
(0, ui_1.printSpacer)();
|
|
45
|
+
console.log(ui_1.ui.bold("Then run"));
|
|
46
|
+
console.log(` ${ui_1.ui.dim("# edit fexapi/schema.fexapi")}`);
|
|
47
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi generate")}`);
|
|
48
|
+
console.log(` ${(0, ui_1.formatCommand)("fexapi run")}`);
|
|
49
|
+
(0, ui_1.printSpacer)();
|
|
50
|
+
console.log(ui_1.ui.bold("Generate output"));
|
|
51
|
+
console.log(` ${ui_1.ui.dim("fexapi/generated.api.json")}`);
|
|
52
|
+
console.log(` ${ui_1.ui.dim("fexapi/migrations/schema.json")}`);
|
|
52
53
|
};
|
|
53
54
|
exports.printHelp = printHelp;
|
package/dist/cli/ui.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const ui: {
|
|
2
|
+
bold: (text: string) => string;
|
|
3
|
+
dim: (text: string) => string;
|
|
4
|
+
cyan: (text: string) => string;
|
|
5
|
+
blue: (text: string) => string;
|
|
6
|
+
green: (text: string) => string;
|
|
7
|
+
yellow: (text: string) => string;
|
|
8
|
+
red: (text: string) => string;
|
|
9
|
+
gray: (text: string) => string;
|
|
10
|
+
};
|
|
11
|
+
export declare const printBanner: () => void;
|
|
12
|
+
export declare const printSpacer: () => void;
|
|
13
|
+
export declare const logInfo: (message: string) => void;
|
|
14
|
+
export declare const logSuccess: (message: string) => void;
|
|
15
|
+
export declare const logWarn: (message: string) => void;
|
|
16
|
+
export declare const logError: (message: string) => void;
|
|
17
|
+
export declare const logStep: (message: string) => void;
|
|
18
|
+
export declare const formatCommand: (command: string) => string;
|
|
19
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,EAAE;iBACA,MAAM,KAAG,MAAM;gBAChB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;iBACf,MAAM,KAAG,MAAM;kBACd,MAAM,KAAG,MAAM;mBACd,MAAM,KAAG,MAAM;gBAClB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;CAC7B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,MAE/C,CAAC"}
|
package/dist/cli/ui.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatCommand = exports.logStep = exports.logError = exports.logWarn = exports.logSuccess = exports.logInfo = exports.printSpacer = exports.printBanner = exports.ui = void 0;
|
|
4
|
+
const shouldUseColor = () => {
|
|
5
|
+
return Boolean(process.stdout.isTTY);
|
|
6
|
+
};
|
|
7
|
+
const colorEnabled = shouldUseColor();
|
|
8
|
+
const paint = (code, text) => {
|
|
9
|
+
if (!colorEnabled) {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
return `\u001b[${code}m${text}\u001b[0m`;
|
|
13
|
+
};
|
|
14
|
+
exports.ui = {
|
|
15
|
+
bold: (text) => paint("1", text),
|
|
16
|
+
dim: (text) => paint("2", text),
|
|
17
|
+
cyan: (text) => paint("36", text),
|
|
18
|
+
blue: (text) => paint("94", text),
|
|
19
|
+
green: (text) => paint("32", text),
|
|
20
|
+
yellow: (text) => paint("33", text),
|
|
21
|
+
red: (text) => paint("31", text),
|
|
22
|
+
gray: (text) => paint("90", text),
|
|
23
|
+
};
|
|
24
|
+
const printBanner = () => {
|
|
25
|
+
console.log(exports.ui.bold(exports.ui.cyan("fexapi")) + exports.ui.gray(" mock api toolkit"));
|
|
26
|
+
};
|
|
27
|
+
exports.printBanner = printBanner;
|
|
28
|
+
const printSpacer = () => {
|
|
29
|
+
console.log("");
|
|
30
|
+
};
|
|
31
|
+
exports.printSpacer = printSpacer;
|
|
32
|
+
const logInfo = (message) => {
|
|
33
|
+
console.log(`${exports.ui.blue("info")} ${message}`);
|
|
34
|
+
};
|
|
35
|
+
exports.logInfo = logInfo;
|
|
36
|
+
const logSuccess = (message) => {
|
|
37
|
+
console.log(`${exports.ui.green("ok ")} ${message}`);
|
|
38
|
+
};
|
|
39
|
+
exports.logSuccess = logSuccess;
|
|
40
|
+
const logWarn = (message) => {
|
|
41
|
+
console.log(`${exports.ui.yellow("warn")} ${message}`);
|
|
42
|
+
};
|
|
43
|
+
exports.logWarn = logWarn;
|
|
44
|
+
const logError = (message) => {
|
|
45
|
+
console.error(`${exports.ui.red("err ")} ${message}`);
|
|
46
|
+
};
|
|
47
|
+
exports.logError = logError;
|
|
48
|
+
const logStep = (message) => {
|
|
49
|
+
console.log(`${exports.ui.cyan("->")} ${message}`);
|
|
50
|
+
};
|
|
51
|
+
exports.logStep = logStep;
|
|
52
|
+
const formatCommand = (command) => {
|
|
53
|
+
return exports.ui.bold(command);
|
|
54
|
+
};
|
|
55
|
+
exports.formatCommand = formatCommand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAiCA,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,MAmHH,CAAC"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runDevCommand = void 0;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
|
+
const ui_1 = require("../cli/ui");
|
|
6
7
|
const paths_1 = require("../project/paths");
|
|
7
8
|
const serve_1 = require("./serve");
|
|
8
9
|
const WATCH_DEBOUNCE_MS = 150;
|
|
@@ -18,7 +19,7 @@ const isWatchedPath = (projectRoot, changedPath) => {
|
|
|
18
19
|
if (relativePath.startsWith("fexapi/")) {
|
|
19
20
|
return true;
|
|
20
21
|
}
|
|
21
|
-
return (relativePath.startsWith("schemas/") &&
|
|
22
|
+
return (relativePath.startsWith("fexapi/schemas/") &&
|
|
22
23
|
(relativePath.endsWith(".yaml") || relativePath.endsWith(".yml")));
|
|
23
24
|
};
|
|
24
25
|
const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
@@ -27,14 +28,14 @@ const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
|
27
28
|
}
|
|
28
29
|
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
29
30
|
if (!projectRoot) {
|
|
30
|
-
|
|
31
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
31
32
|
return 1;
|
|
32
33
|
}
|
|
33
34
|
let currentServer = (0, serve_1.createProjectServer)({ host, port, logEnabled });
|
|
34
35
|
if (!currentServer) {
|
|
35
36
|
return 1;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
(0, ui_1.logInfo)("Watch mode enabled. Restarting on config/schema changes...");
|
|
38
39
|
let restartTimer;
|
|
39
40
|
let restartQueued = false;
|
|
40
41
|
let restartInProgress = false;
|
|
@@ -47,7 +48,7 @@ const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
restartInProgress = true;
|
|
50
|
-
|
|
51
|
+
(0, ui_1.logInfo)(`[watch] change detected (${reason})`);
|
|
51
52
|
await new Promise((resolve) => {
|
|
52
53
|
currentServer?.close(() => {
|
|
53
54
|
resolve();
|
|
@@ -69,11 +70,7 @@ const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
|
69
70
|
}, WATCH_DEBOUNCE_MS);
|
|
70
71
|
};
|
|
71
72
|
const activeWatchers = [];
|
|
72
|
-
const watchTargets = [
|
|
73
|
-
(0, node_path_1.join)(projectRoot, "fexapi"),
|
|
74
|
-
(0, node_path_1.join)(projectRoot, "schemas"),
|
|
75
|
-
projectRoot,
|
|
76
|
-
];
|
|
73
|
+
const watchTargets = [(0, node_path_1.join)(projectRoot, "fexapi"), projectRoot];
|
|
77
74
|
for (const watchTarget of watchTargets) {
|
|
78
75
|
if (!(0, node_fs_1.existsSync)(watchTarget)) {
|
|
79
76
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,kBAAkB,QAAO,MA0GrC,CAAC"}
|
|
@@ -4,12 +4,13 @@ exports.generateFromSchema = void 0;
|
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const constants_1 = require("../constants");
|
|
7
|
+
const ui_1 = require("../cli/ui");
|
|
7
8
|
const paths_1 = require("../project/paths");
|
|
8
9
|
const schema_1 = require("../schema");
|
|
9
10
|
const generateFromSchema = () => {
|
|
10
11
|
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
11
12
|
if (!projectRoot) {
|
|
12
|
-
|
|
13
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
13
14
|
return 1;
|
|
14
15
|
}
|
|
15
16
|
const schemaPath = (0, node_path_1.join)(projectRoot, "fexapi", "schema.fexapi");
|
|
@@ -17,16 +18,16 @@ const generateFromSchema = () => {
|
|
|
17
18
|
const migrationsDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "migrations");
|
|
18
19
|
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
19
20
|
if (!(0, node_fs_1.existsSync)(schemaPath)) {
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
(0, ui_1.logError)(`Schema file not found: ${schemaPath}`);
|
|
22
|
+
(0, ui_1.logInfo)("Run `fexapi init` first.");
|
|
22
23
|
return 1;
|
|
23
24
|
}
|
|
24
25
|
const schemaText = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
|
|
25
26
|
const parsed = (0, schema_1.parseFexapiSchema)(schemaText);
|
|
26
27
|
if (parsed.errors.length > 0 || !parsed.schema) {
|
|
27
|
-
|
|
28
|
+
(0, ui_1.logError)("Failed to generate API from schema.fexapi");
|
|
28
29
|
for (const error of parsed.errors) {
|
|
29
|
-
|
|
30
|
+
(0, ui_1.logError)(`- ${error}`);
|
|
30
31
|
}
|
|
31
32
|
return 1;
|
|
32
33
|
}
|
|
@@ -72,10 +73,11 @@ const generateFromSchema = () => {
|
|
|
72
73
|
lastGeneratedAt: new Date().toISOString(),
|
|
73
74
|
};
|
|
74
75
|
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
(0, ui_1.logSuccess)(`Generated API spec at ${generatedPath}`);
|
|
77
|
+
(0, ui_1.logSuccess)(`Migration updated at ${migrationPath}`);
|
|
78
|
+
(0, ui_1.printSpacer)();
|
|
79
|
+
(0, ui_1.logInfo)(`Routes generated: ${parsed.schema.routes.length}`);
|
|
80
|
+
(0, ui_1.logInfo)(`Configured server port: ${parsed.schema.port}`);
|
|
79
81
|
return 0;
|
|
80
82
|
};
|
|
81
83
|
exports.generateFromSchema = generateFromSchema;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAkMA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CA0JjB,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@ const node_path_1 = require("node:path");
|
|
|
6
6
|
const promises_1 = require("node:readline/promises");
|
|
7
7
|
const node_process_1 = require("node:process");
|
|
8
8
|
const constants_1 = require("../constants");
|
|
9
|
+
const ui_1 = require("../cli/ui");
|
|
9
10
|
const detect_1 = require("../project/detect");
|
|
10
11
|
const paths_1 = require("../project/paths");
|
|
11
12
|
const DEFAULT_INIT_PORT = 3000;
|
|
@@ -82,8 +83,8 @@ const getRuntimeConfigTemplate = ({ port, cors, includeSampleRoutes, }) => {
|
|
|
82
83
|
const routeSection = includeSampleRoutes
|
|
83
84
|
? [
|
|
84
85
|
" routes: {",
|
|
85
|
-
' "/users": { count:
|
|
86
|
-
' "/posts": { count:
|
|
86
|
+
' "/users": { count: 10, schema: "user" },',
|
|
87
|
+
' "/posts": { count: 20, schema: "post" },',
|
|
87
88
|
" },",
|
|
88
89
|
]
|
|
89
90
|
: [];
|
|
@@ -102,12 +103,22 @@ const SAMPLE_USER_SCHEMA = [
|
|
|
102
103
|
"fullName:",
|
|
103
104
|
" type: name",
|
|
104
105
|
" faker: person.fullName",
|
|
106
|
+
"username:",
|
|
107
|
+
" type: string",
|
|
108
|
+
" faker: internet.username",
|
|
105
109
|
"email:",
|
|
106
110
|
" type: email",
|
|
107
111
|
" faker: internet.email",
|
|
108
112
|
"avatarUrl:",
|
|
109
113
|
" type: url",
|
|
110
114
|
" faker: image.avatar",
|
|
115
|
+
"bio:",
|
|
116
|
+
" type: string",
|
|
117
|
+
" faker: lorem.sentence",
|
|
118
|
+
"isActive:",
|
|
119
|
+
" type: boolean",
|
|
120
|
+
"joinedAt:",
|
|
121
|
+
" type: date",
|
|
111
122
|
].join("\n");
|
|
112
123
|
const SAMPLE_POST_SCHEMA = [
|
|
113
124
|
"id:",
|
|
@@ -117,14 +128,22 @@ const SAMPLE_POST_SCHEMA = [
|
|
|
117
128
|
" faker: lorem.sentence",
|
|
118
129
|
"body:",
|
|
119
130
|
" type: string",
|
|
120
|
-
" faker: lorem.
|
|
131
|
+
" faker: lorem.paragraphs",
|
|
132
|
+
"authorId:",
|
|
133
|
+
" type: uuid",
|
|
134
|
+
"published:",
|
|
135
|
+
" type: boolean",
|
|
136
|
+
"likes:",
|
|
137
|
+
" type: number",
|
|
138
|
+
" min: 0",
|
|
139
|
+
" max: 500",
|
|
121
140
|
"createdAt:",
|
|
122
141
|
" type: date",
|
|
123
142
|
].join("\n");
|
|
124
143
|
const initializeProject = async ({ force, }) => {
|
|
125
144
|
const packageJsonPath = (0, paths_1.findClosestPackageJson)(process.cwd());
|
|
126
145
|
if (!packageJsonPath) {
|
|
127
|
-
|
|
146
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
128
147
|
return 1;
|
|
129
148
|
}
|
|
130
149
|
const projectRoot = (0, node_path_1.dirname)(packageJsonPath);
|
|
@@ -133,7 +152,7 @@ const initializeProject = async ({ force, }) => {
|
|
|
133
152
|
const schemaPath = (0, node_path_1.join)(fexapiDirectoryPath, "schema.fexapi");
|
|
134
153
|
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
135
154
|
const runtimeConfigPath = (0, node_path_1.join)(projectRoot, "fexapi.config.js");
|
|
136
|
-
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "schemas");
|
|
155
|
+
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "schemas");
|
|
137
156
|
const userSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "user.yaml");
|
|
138
157
|
const postSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "post.yaml");
|
|
139
158
|
const wizardAnswers = await askInitWizardQuestions();
|
|
@@ -186,65 +205,68 @@ const initializeProject = async ({ force, }) => {
|
|
|
186
205
|
postSchemaStatus = "exists";
|
|
187
206
|
}
|
|
188
207
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
208
|
+
(0, ui_1.logSuccess)(`Initialized fexapi in ${projectRoot}`);
|
|
209
|
+
(0, ui_1.logInfo)(`Detected framework: ${detectedProject.primaryFramework}`);
|
|
210
|
+
(0, ui_1.logInfo)(`Detected frameworks: ${detectedProject.frameworks.join(", ")}`);
|
|
192
211
|
if (detectedProject.tooling.length > 0) {
|
|
193
|
-
|
|
212
|
+
(0, ui_1.logInfo)(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
|
|
194
213
|
}
|
|
214
|
+
(0, ui_1.printSpacer)();
|
|
195
215
|
if (configExists && !force) {
|
|
196
|
-
|
|
216
|
+
(0, ui_1.logWarn)(`Exists ${configPath}`);
|
|
197
217
|
}
|
|
198
218
|
else if (configExists && force) {
|
|
199
|
-
|
|
219
|
+
(0, ui_1.logSuccess)(`Overwritten ${configPath}`);
|
|
200
220
|
}
|
|
201
221
|
else {
|
|
202
|
-
|
|
222
|
+
(0, ui_1.logSuccess)(`Created ${configPath}`);
|
|
203
223
|
}
|
|
204
224
|
if (schemaExists && !force) {
|
|
205
|
-
|
|
225
|
+
(0, ui_1.logWarn)(`Exists ${schemaPath}`);
|
|
206
226
|
}
|
|
207
227
|
else if (schemaExists && force) {
|
|
208
|
-
|
|
228
|
+
(0, ui_1.logSuccess)(`Overwritten ${schemaPath}`);
|
|
209
229
|
}
|
|
210
230
|
else {
|
|
211
|
-
|
|
231
|
+
(0, ui_1.logSuccess)(`Created ${schemaPath}`);
|
|
212
232
|
}
|
|
213
233
|
if (runtimeConfigExists && !force) {
|
|
214
|
-
|
|
234
|
+
(0, ui_1.logWarn)(`Exists ${runtimeConfigPath}`);
|
|
215
235
|
}
|
|
216
236
|
else if (runtimeConfigExists && force) {
|
|
217
|
-
|
|
237
|
+
(0, ui_1.logSuccess)(`Overwritten ${runtimeConfigPath}`);
|
|
218
238
|
}
|
|
219
239
|
else {
|
|
220
|
-
|
|
240
|
+
(0, ui_1.logSuccess)(`Created ${runtimeConfigPath}`);
|
|
221
241
|
}
|
|
222
242
|
if (wizardAnswers.generateSampleSchemas) {
|
|
223
243
|
if (userSchemaStatus === "exists") {
|
|
224
|
-
|
|
244
|
+
(0, ui_1.logWarn)(`Exists ${userSchemaPath}`);
|
|
225
245
|
}
|
|
226
246
|
else if (userSchemaStatus === "overwritten") {
|
|
227
|
-
|
|
247
|
+
(0, ui_1.logSuccess)(`Overwritten ${userSchemaPath}`);
|
|
228
248
|
}
|
|
229
249
|
else if (userSchemaStatus === "created") {
|
|
230
|
-
|
|
250
|
+
(0, ui_1.logSuccess)(`Created ${userSchemaPath}`);
|
|
231
251
|
}
|
|
232
252
|
if (postSchemaStatus === "exists") {
|
|
233
|
-
|
|
253
|
+
(0, ui_1.logWarn)(`Exists ${postSchemaPath}`);
|
|
234
254
|
}
|
|
235
255
|
else if (postSchemaStatus === "overwritten") {
|
|
236
|
-
|
|
256
|
+
(0, ui_1.logSuccess)(`Overwritten ${postSchemaPath}`);
|
|
237
257
|
}
|
|
238
258
|
else if (postSchemaStatus === "created") {
|
|
239
|
-
|
|
259
|
+
(0, ui_1.logSuccess)(`Created ${postSchemaPath}`);
|
|
240
260
|
}
|
|
241
261
|
}
|
|
242
262
|
else {
|
|
243
|
-
|
|
263
|
+
(0, ui_1.logWarn)("Sample schemas were skipped.");
|
|
244
264
|
}
|
|
245
265
|
if (detectedProject.primaryFramework === "unknown") {
|
|
246
|
-
|
|
266
|
+
(0, ui_1.logWarn)("No known framework dependency found. Update fexapi.config.json and schema.fexapi if needed.");
|
|
247
267
|
}
|
|
268
|
+
(0, ui_1.printSpacer)();
|
|
269
|
+
(0, ui_1.logInfo)(`Next: ${(0, ui_1.formatCommand)("fexapi generate")} then ${(0, ui_1.formatCommand)("fexapi serve")}`);
|
|
248
270
|
return 0;
|
|
249
271
|
};
|
|
250
272
|
exports.initializeProject = initializeProject;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,eAAO,MAAM,mBAAmB,GAAI,6BAIjC;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,KAAG,MAAM,GAAG,SA2DZ,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,6BAI1B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,KAAG,MAsBH,CAAC"}
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serveProject = exports.createProjectServer = void 0;
|
|
4
|
+
const ui_1 = require("../cli/ui");
|
|
4
5
|
const constants_1 = require("../constants");
|
|
5
6
|
const generated_spec_1 = require("../config/generated-spec");
|
|
6
7
|
const runtime_config_1 = require("../config/runtime-config");
|
|
@@ -10,7 +11,7 @@ const server_1 = require("../server");
|
|
|
10
11
|
const createProjectServer = ({ host, port, logEnabled = false, }) => {
|
|
11
12
|
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
12
13
|
if (!projectRoot) {
|
|
13
|
-
|
|
14
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
14
15
|
return undefined;
|
|
15
16
|
}
|
|
16
17
|
const runtimeConfig = projectRoot
|
|
@@ -24,18 +25,18 @@ const createProjectServer = ({ host, port, logEnabled = false, }) => {
|
|
|
24
25
|
: undefined;
|
|
25
26
|
const effectivePort = port ?? runtimeConfig?.port ?? generatedSpec?.port ?? 4000;
|
|
26
27
|
if (runtimeConfig?.routes && Object.keys(runtimeConfig.routes).length > 0) {
|
|
27
|
-
|
|
28
|
+
(0, ui_1.logInfo)(`Using routes from fexapi.config.js (${Object.keys(runtimeConfig.routes).length})`);
|
|
28
29
|
}
|
|
29
30
|
if (Object.keys(schemaDefinitions).length > 0) {
|
|
30
|
-
|
|
31
|
+
(0, ui_1.logInfo)(`Loaded custom schemas from fexapi/schemas (${Object.keys(schemaDefinitions).length})`);
|
|
31
32
|
}
|
|
32
33
|
if (generatedSpec &&
|
|
33
34
|
!(runtimeConfig?.routes && Object.keys(runtimeConfig.routes).length > 0)) {
|
|
34
|
-
|
|
35
|
+
(0, ui_1.logInfo)(`Using generated schema routes (${generatedSpec.routes.length}) from ${constants_1.GENERATED_SPEC_RELATIVE_PATH}`);
|
|
35
36
|
}
|
|
36
37
|
else if (!runtimeConfig?.routes ||
|
|
37
38
|
Object.keys(runtimeConfig.routes).length === 0) {
|
|
38
|
-
|
|
39
|
+
(0, ui_1.logWarn)("No generated schema found. Run `fexapi generate` to serve schema-defined endpoints.");
|
|
39
40
|
}
|
|
40
41
|
return (0, server_1.startServer)({
|
|
41
42
|
host,
|
|
@@ -55,7 +56,7 @@ const serveProject = ({ host, port, logEnabled, }) => {
|
|
|
55
56
|
const shutdown = () => {
|
|
56
57
|
server.close((error) => {
|
|
57
58
|
if (error) {
|
|
58
|
-
|
|
59
|
+
(0, ui_1.logError)(`Error while shutting down server: ${String(error)}`);
|
|
59
60
|
process.exit(1);
|
|
60
61
|
}
|
|
61
62
|
process.exit(0);
|
|
@@ -20,23 +20,23 @@ const isRecord = (value) => {
|
|
|
20
20
|
};
|
|
21
21
|
const parseSchemaDefinition = (schemaName, rawValue) => {
|
|
22
22
|
if (!isRecord(rawValue)) {
|
|
23
|
-
console.error(`Invalid schemas/${schemaName}.yaml: expected root object of field definitions.`);
|
|
23
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml: expected root object of field definitions.`);
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
const result = {};
|
|
27
27
|
for (const [fieldName, rawFieldConfig] of Object.entries(rawValue)) {
|
|
28
28
|
if (!isRecord(rawFieldConfig)) {
|
|
29
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: expected object with type/faker/min/max.`);
|
|
29
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: expected object with type/faker/min/max.`);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
32
|
const rawType = rawFieldConfig.type;
|
|
33
33
|
if (typeof rawType !== "string") {
|
|
34
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: missing string \`type\`.`);
|
|
34
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: missing string \`type\`.`);
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
37
|
const normalizedType = rawType.trim().toLowerCase();
|
|
38
38
|
if (!VALID_TYPES.has(normalizedType)) {
|
|
39
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: unknown type \`${rawType}\`.`);
|
|
39
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: unknown type \`${rawType}\`.`);
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
const minValue = rawFieldConfig.min;
|
|
@@ -57,7 +57,7 @@ const parseSchemaDefinition = (schemaName, rawValue) => {
|
|
|
57
57
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
58
58
|
};
|
|
59
59
|
const loadSchemaDefinitions = (projectRoot) => {
|
|
60
|
-
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "schemas");
|
|
60
|
+
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "schemas");
|
|
61
61
|
if (!(0, node_fs_1.existsSync)(schemasDirectoryPath)) {
|
|
62
62
|
return {};
|
|
63
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const args_1 = require("./cli/args");
|
|
5
5
|
const dev_1 = require("./commands/dev");
|
|
6
6
|
const help_1 = require("./cli/help");
|
|
7
|
+
const ui_1 = require("./cli/ui");
|
|
7
8
|
const format_1 = require("./commands/format");
|
|
8
9
|
const generate_1 = require("./commands/generate");
|
|
9
10
|
const init_1 = require("./commands/init");
|
|
@@ -13,61 +14,69 @@ const [firstArg, ...restArgs] = args;
|
|
|
13
14
|
const main = async () => {
|
|
14
15
|
if (firstArg === "init") {
|
|
15
16
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
16
|
-
|
|
17
|
+
(0, ui_1.printBanner)();
|
|
18
|
+
(0, ui_1.printSpacer)();
|
|
19
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi init [--force]")}`);
|
|
17
20
|
console.log("Runs an interactive setup wizard and creates fexapi config/schema files.");
|
|
18
21
|
console.log("Use --force to overwrite existing files.");
|
|
19
22
|
process.exit(0);
|
|
20
23
|
}
|
|
21
24
|
const initOptions = (0, args_1.parseInitOptions)(restArgs);
|
|
22
25
|
if ("error" in initOptions) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
(0, ui_1.logError)(initOptions.error);
|
|
27
|
+
(0, ui_1.printSpacer)();
|
|
28
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi init [--force]")}`);
|
|
26
29
|
process.exit(1);
|
|
27
30
|
}
|
|
28
31
|
process.exit(await (0, init_1.initializeProject)({ force: initOptions.force }));
|
|
29
32
|
}
|
|
30
33
|
else if (firstArg === "generate") {
|
|
31
34
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
32
|
-
|
|
35
|
+
(0, ui_1.printBanner)();
|
|
36
|
+
(0, ui_1.printSpacer)();
|
|
37
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi generate")}`);
|
|
33
38
|
console.log("Reads fexapi/schema.fexapi and updates generated API artifacts + migration.");
|
|
34
39
|
process.exit(0);
|
|
35
40
|
}
|
|
36
41
|
const generateOptions = (0, args_1.parseGenerateOptions)(restArgs);
|
|
37
42
|
if (generateOptions.error) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
(0, ui_1.logError)(generateOptions.error);
|
|
44
|
+
(0, ui_1.printSpacer)();
|
|
45
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi generate")}`);
|
|
41
46
|
process.exit(1);
|
|
42
47
|
}
|
|
43
48
|
process.exit((0, generate_1.generateFromSchema)());
|
|
44
49
|
}
|
|
45
50
|
else if (firstArg === "format") {
|
|
46
51
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
47
|
-
|
|
52
|
+
(0, ui_1.printBanner)();
|
|
53
|
+
(0, ui_1.printSpacer)();
|
|
54
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi format")}`);
|
|
48
55
|
console.log("Reformats fexapi/schema.fexapi to use readable multi-line field formatting.");
|
|
49
56
|
process.exit(0);
|
|
50
57
|
}
|
|
51
58
|
const formatOptions = (0, args_1.parseFormatOptions)(restArgs);
|
|
52
59
|
if (formatOptions.error) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
(0, ui_1.logError)(formatOptions.error);
|
|
61
|
+
(0, ui_1.printSpacer)();
|
|
62
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi format")}`);
|
|
56
63
|
process.exit(1);
|
|
57
64
|
}
|
|
58
65
|
process.exit((0, format_1.formatSchema)());
|
|
59
66
|
}
|
|
60
67
|
else if (firstArg === "dev") {
|
|
61
68
|
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
62
|
-
|
|
69
|
+
(0, ui_1.printBanner)();
|
|
70
|
+
(0, ui_1.printSpacer)();
|
|
71
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi dev [--watch] [--host <host>] [--port <number>] [--log]")}`);
|
|
63
72
|
console.log("Starts development server and optionally auto-reloads when config/schema files change.");
|
|
64
73
|
process.exit(0);
|
|
65
74
|
}
|
|
66
75
|
const devOptions = (0, args_1.parseDevOptions)(restArgs);
|
|
67
76
|
if ("error" in devOptions) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
(0, ui_1.logError)(devOptions.error);
|
|
78
|
+
(0, ui_1.printSpacer)();
|
|
79
|
+
(0, ui_1.logInfo)(`Usage: ${(0, ui_1.formatCommand)("fexapi dev [--watch] [--host <host>] [--port <number>] [--log]")}`);
|
|
71
80
|
process.exit(1);
|
|
72
81
|
}
|
|
73
82
|
const exitCode = (0, dev_1.runDevCommand)(devOptions);
|
|
@@ -86,8 +95,8 @@ const main = async () => {
|
|
|
86
95
|
}
|
|
87
96
|
const options = (0, args_1.parseServeOptions)(serveArgs);
|
|
88
97
|
if ("error" in options) {
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
(0, ui_1.logError)(options.error);
|
|
99
|
+
(0, ui_1.printSpacer)();
|
|
91
100
|
(0, help_1.printHelp)();
|
|
92
101
|
process.exit(1);
|
|
93
102
|
}
|
|
@@ -101,14 +110,14 @@ const main = async () => {
|
|
|
101
110
|
process.exit(0);
|
|
102
111
|
}
|
|
103
112
|
else {
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
(0, ui_1.logError)(`Unknown command: ${firstArg}`);
|
|
114
|
+
(0, ui_1.printSpacer)();
|
|
106
115
|
(0, help_1.printHelp)();
|
|
107
116
|
process.exit(1);
|
|
108
117
|
}
|
|
109
118
|
};
|
|
110
119
|
void main().catch((error) => {
|
|
111
120
|
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
-
|
|
121
|
+
(0, ui_1.logError)(`Unexpected error: ${message}`);
|
|
113
122
|
process.exit(1);
|
|
114
123
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/project/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAyD5E,eAAO,MAAM,aAAa,GACxB,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,eAwDF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,kBAAkB,EAC7B,aAAW,KACV,
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/project/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAyD5E,eAAO,MAAM,aAAa,GACxB,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,eAwDF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,kBAAkB,EAC7B,aAAW,KACV,MAmGF,CAAC"}
|
package/dist/project/detect.js
CHANGED
|
@@ -94,33 +94,101 @@ const detectProject = (packageJsonPath, projectRoot) => {
|
|
|
94
94
|
};
|
|
95
95
|
exports.detectProject = detectProject;
|
|
96
96
|
const getSchemaTemplate = (framework, port = 4000) => {
|
|
97
|
-
const
|
|
98
|
-
? "
|
|
97
|
+
const frameworkLabel = framework === "nextjs"
|
|
98
|
+
? "Next.js"
|
|
99
99
|
: framework === "reactjs"
|
|
100
|
-
? "
|
|
101
|
-
:
|
|
100
|
+
? "React"
|
|
101
|
+
: framework === "vue"
|
|
102
|
+
? "Vue"
|
|
103
|
+
: framework === "nuxt"
|
|
104
|
+
? "Nuxt"
|
|
105
|
+
: framework === "svelte"
|
|
106
|
+
? "Svelte"
|
|
107
|
+
: framework === "sveltekit"
|
|
108
|
+
? "SvelteKit"
|
|
109
|
+
: framework === "angular"
|
|
110
|
+
? "Angular"
|
|
111
|
+
: framework === "solid"
|
|
112
|
+
? "Solid"
|
|
113
|
+
: framework === "remix"
|
|
114
|
+
? "Remix"
|
|
115
|
+
: framework === "astro"
|
|
116
|
+
? "Astro"
|
|
117
|
+
: "unknown";
|
|
102
118
|
return [
|
|
103
|
-
|
|
119
|
+
`# Framework: ${frameworkLabel}`,
|
|
120
|
+
"",
|
|
121
|
+
"# ──────────────────────────────────────────────",
|
|
104
122
|
"# Server",
|
|
123
|
+
"# ──────────────────────────────────────────────",
|
|
105
124
|
`port: ${port}`,
|
|
106
125
|
"",
|
|
126
|
+
"# ──────────────────────────────────────────────",
|
|
127
|
+
"# Available types",
|
|
128
|
+
"# string → random words",
|
|
129
|
+
"# number → random integer",
|
|
130
|
+
"# boolean → true / false",
|
|
131
|
+
"# uuid → unique id",
|
|
132
|
+
"# email → fake email",
|
|
133
|
+
"# name → full name",
|
|
134
|
+
"# url → fake URL",
|
|
135
|
+
"# phone → phone number",
|
|
136
|
+
"# date → ISO date string",
|
|
137
|
+
"# ──────────────────────────────────────────────",
|
|
138
|
+
"",
|
|
139
|
+
"# ──────────────────────────────────────────────",
|
|
107
140
|
"# Routes",
|
|
108
|
-
"#
|
|
109
|
-
"#
|
|
110
|
-
"#
|
|
111
|
-
"#
|
|
112
|
-
"#
|
|
141
|
+
"#",
|
|
142
|
+
"# Single-line: GET /items: id:uuid, name:string",
|
|
143
|
+
"# Multi-line:",
|
|
144
|
+
"# GET /items:",
|
|
145
|
+
"# id:uuid",
|
|
146
|
+
"# name:string",
|
|
147
|
+
"# ──────────────────────────────────────────────",
|
|
148
|
+
"",
|
|
149
|
+
"# ── Users ────────────────────────────────────",
|
|
150
|
+
"",
|
|
113
151
|
"GET /users:",
|
|
114
152
|
" id:uuid",
|
|
115
153
|
" fullName:name",
|
|
116
154
|
" username:string",
|
|
117
155
|
" email:email",
|
|
156
|
+
" phone:phone",
|
|
118
157
|
" avatarUrl:url",
|
|
158
|
+
" joinedAt:date",
|
|
159
|
+
"",
|
|
160
|
+
"POST /users:",
|
|
161
|
+
" id:uuid",
|
|
162
|
+
" fullName:name",
|
|
163
|
+
" username:string",
|
|
164
|
+
" email:email",
|
|
165
|
+
"",
|
|
166
|
+
"# ── Posts ────────────────────────────────────",
|
|
167
|
+
"",
|
|
119
168
|
"GET /posts:",
|
|
120
169
|
" id:uuid",
|
|
121
170
|
" title:string",
|
|
122
171
|
" body:string",
|
|
172
|
+
" authorId:uuid",
|
|
173
|
+
" published:boolean",
|
|
123
174
|
" createdAt:date",
|
|
175
|
+
"",
|
|
176
|
+
"POST /posts:",
|
|
177
|
+
" id:uuid",
|
|
178
|
+
" title:string",
|
|
179
|
+
" body:string",
|
|
180
|
+
" authorId:uuid",
|
|
181
|
+
"",
|
|
182
|
+
"PUT /posts:",
|
|
183
|
+
" id:uuid",
|
|
184
|
+
" title:string",
|
|
185
|
+
" body:string",
|
|
186
|
+
"",
|
|
187
|
+
"DELETE /posts: id:uuid",
|
|
188
|
+
"",
|
|
189
|
+
"# ── Comments (single-line example) ───────────",
|
|
190
|
+
"",
|
|
191
|
+
"GET /comments: id:uuid, body:string, postId:uuid, authorId:uuid, createdAt:date",
|
|
124
192
|
].join("\n");
|
|
125
193
|
};
|
|
126
194
|
exports.getSchemaTemplate = getSchemaTemplate;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAqKF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,MAAM,KACjB;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAmH3C,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -12,10 +12,27 @@ const VALID_TYPES = [
|
|
|
12
12
|
"name",
|
|
13
13
|
"phone",
|
|
14
14
|
];
|
|
15
|
+
const VALID_METHODS = new Set([
|
|
16
|
+
"GET",
|
|
17
|
+
"POST",
|
|
18
|
+
"PUT",
|
|
19
|
+
"PATCH",
|
|
20
|
+
"DELETE",
|
|
21
|
+
"HEAD",
|
|
22
|
+
"OPTIONS",
|
|
23
|
+
]);
|
|
15
24
|
const DEFAULT_PORT = 4000;
|
|
16
25
|
const ROUTE_FORMAT_ERROR_MESSAGE = "Invalid route definition. Expected format: " +
|
|
17
26
|
"METHOD /endpoint: field:type,field:type (or multiline fields under METHOD /endpoint:)";
|
|
27
|
+
const FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
28
|
const isPortLine = (line) => /^port\s*:/i.test(line.trim());
|
|
29
|
+
const stripInlineComment = (line) => {
|
|
30
|
+
const commentIndex = line.indexOf("#");
|
|
31
|
+
if (commentIndex === -1) {
|
|
32
|
+
return line.trim();
|
|
33
|
+
}
|
|
34
|
+
return line.slice(0, commentIndex).trim();
|
|
35
|
+
};
|
|
19
36
|
const parseRouteHeader = (line) => {
|
|
20
37
|
const separatorIndex = line.indexOf(":");
|
|
21
38
|
if (separatorIndex === -1) {
|
|
@@ -36,12 +53,23 @@ const parseRouteHeader = (line) => {
|
|
|
36
53
|
};
|
|
37
54
|
};
|
|
38
55
|
const parseField = (rawField) => {
|
|
39
|
-
const
|
|
56
|
+
const parts = rawField.split(":");
|
|
57
|
+
if (parts.length !== 2) {
|
|
58
|
+
return {
|
|
59
|
+
error: `Invalid field "${rawField}". Expected format name:type.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const [rawName, rawType] = parts;
|
|
40
63
|
const name = rawName?.trim();
|
|
41
64
|
const type = rawType?.trim().toLowerCase();
|
|
42
65
|
if (!name) {
|
|
43
66
|
return { error: `Invalid field "${rawField}". Missing field name.` };
|
|
44
67
|
}
|
|
68
|
+
if (!FIELD_NAME_PATTERN.test(name)) {
|
|
69
|
+
return {
|
|
70
|
+
error: `Invalid field name "${name}". Use letters, numbers, and underscore only (must not start with a number).`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
45
73
|
if (!type) {
|
|
46
74
|
return { error: `Invalid field "${rawField}". Missing field type.` };
|
|
47
75
|
}
|
|
@@ -59,10 +87,16 @@ const parseRoute = ({ method, path, rawFields, }) => {
|
|
|
59
87
|
if (!method || !path) {
|
|
60
88
|
return { error: ROUTE_FORMAT_ERROR_MESSAGE };
|
|
61
89
|
}
|
|
90
|
+
if (!VALID_METHODS.has(method)) {
|
|
91
|
+
return {
|
|
92
|
+
error: `Unsupported HTTP method "${method}" for route ${method} ${path}. Valid methods: ${Array.from(VALID_METHODS).join(", ")}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
62
95
|
const fields = [];
|
|
96
|
+
const seenFieldNames = new Set();
|
|
63
97
|
for (const rawFieldLine of rawFields) {
|
|
64
98
|
for (const part of rawFieldLine.split(",")) {
|
|
65
|
-
const trimmedPart = part
|
|
99
|
+
const trimmedPart = stripInlineComment(part);
|
|
66
100
|
if (!trimmedPart) {
|
|
67
101
|
continue;
|
|
68
102
|
}
|
|
@@ -70,6 +104,12 @@ const parseRoute = ({ method, path, rawFields, }) => {
|
|
|
70
104
|
if ("error" in parsedField) {
|
|
71
105
|
return { error: parsedField.error };
|
|
72
106
|
}
|
|
107
|
+
if (seenFieldNames.has(parsedField.name)) {
|
|
108
|
+
return {
|
|
109
|
+
error: `Duplicate field "${parsedField.name}" in route ${method} ${path}.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
seenFieldNames.add(parsedField.name);
|
|
73
113
|
fields.push(parsedField);
|
|
74
114
|
}
|
|
75
115
|
}
|
|
@@ -83,9 +123,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
83
123
|
const routes = [];
|
|
84
124
|
const errors = [];
|
|
85
125
|
const lines = schemaText.split(/\r?\n/);
|
|
126
|
+
const seenRoutes = new Set();
|
|
86
127
|
for (let index = 0; index < lines.length; index += 1) {
|
|
87
128
|
const rawLine = lines[index] ?? "";
|
|
88
|
-
const trimmedLine = rawLine
|
|
129
|
+
const trimmedLine = stripInlineComment(rawLine);
|
|
89
130
|
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
90
131
|
continue;
|
|
91
132
|
}
|
|
@@ -104,12 +145,18 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
104
145
|
}
|
|
105
146
|
const header = parseRouteHeader(trimmedLine);
|
|
106
147
|
if (!header) {
|
|
107
|
-
errors.push(ROUTE_FORMAT_ERROR_MESSAGE);
|
|
148
|
+
errors.push(`${ROUTE_FORMAT_ERROR_MESSAGE} (line ${index + 1})`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const routeKey = `${header.method} ${header.path}`;
|
|
152
|
+
if (seenRoutes.has(routeKey)) {
|
|
153
|
+
errors.push(`Duplicate route definition: ${routeKey} (line ${index + 1})`);
|
|
108
154
|
continue;
|
|
109
155
|
}
|
|
110
156
|
const rawFields = [];
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
const inlineFields = stripInlineComment(header.inlineFields);
|
|
158
|
+
if (inlineFields) {
|
|
159
|
+
rawFields.push(inlineFields);
|
|
113
160
|
}
|
|
114
161
|
let lookaheadIndex = index + 1;
|
|
115
162
|
while (lookaheadIndex < lines.length) {
|
|
@@ -124,7 +171,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
124
171
|
break;
|
|
125
172
|
}
|
|
126
173
|
if (/^\s+/.test(lookaheadRawLine)) {
|
|
127
|
-
|
|
174
|
+
const normalizedFieldLine = stripInlineComment(lookaheadTrimmedLine.replace(/^-+\s*/, ""));
|
|
175
|
+
if (normalizedFieldLine) {
|
|
176
|
+
rawFields.push(normalizedFieldLine);
|
|
177
|
+
}
|
|
128
178
|
lookaheadIndex += 1;
|
|
129
179
|
continue;
|
|
130
180
|
}
|
|
@@ -136,9 +186,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
136
186
|
rawFields,
|
|
137
187
|
});
|
|
138
188
|
if ("error" in parsedRoute) {
|
|
139
|
-
errors.push(parsedRoute.error);
|
|
189
|
+
errors.push(`${parsedRoute.error} (line ${index + 1})`);
|
|
140
190
|
continue;
|
|
141
191
|
}
|
|
192
|
+
seenRoutes.add(routeKey);
|
|
142
193
|
routes.push(parsedRoute);
|
|
143
194
|
index = lookaheadIndex - 1;
|
|
144
195
|
}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAgMF,eAAO,MAAM,WAAW,GAAI,0EAOzB,aAAkB,wFAwJpB,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.startServer = void 0;
|
|
4
4
|
const faker_1 = require("@faker-js/faker");
|
|
5
5
|
const node_http_1 = require("node:http");
|
|
6
|
+
const ui_1 = require("./cli/ui");
|
|
6
7
|
const DEFAULT_HOST = "127.0.0.1";
|
|
7
8
|
const DEFAULT_PORT = 4000;
|
|
8
9
|
const sendJson = (response, statusCode, payload, options) => {
|
|
@@ -132,16 +133,20 @@ const createRecordFromSchemaName = (schemaName, schemaDefinitions) => {
|
|
|
132
133
|
createdAt: faker_1.faker.date.recent({ days: 7 }).toISOString(),
|
|
133
134
|
};
|
|
134
135
|
};
|
|
135
|
-
const
|
|
136
|
+
const getCountOverrideFromUrl = (urlText) => {
|
|
136
137
|
if (!urlText) {
|
|
137
|
-
return
|
|
138
|
+
return undefined;
|
|
138
139
|
}
|
|
139
140
|
const url = new URL(urlText, "http://localhost");
|
|
140
|
-
const rawCount =
|
|
141
|
-
if (
|
|
142
|
-
return
|
|
141
|
+
const rawCount = url.searchParams.get("count");
|
|
142
|
+
if (rawCount === null) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const parsedCount = Number(rawCount);
|
|
146
|
+
if (!Number.isFinite(parsedCount)) {
|
|
147
|
+
return undefined;
|
|
143
148
|
}
|
|
144
|
-
return Math.min(Math.max(Math.floor(
|
|
149
|
+
return Math.min(Math.max(Math.floor(parsedCount), 1), 50);
|
|
145
150
|
};
|
|
146
151
|
const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtimeConfig, schemaDefinitions = {}, logRequests = false, } = {}) => {
|
|
147
152
|
const corsEnabled = runtimeConfig?.cors ?? false;
|
|
@@ -162,7 +167,19 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
162
167
|
const method = request.method ?? "UNKNOWN";
|
|
163
168
|
const durationMs = Date.now() - requestStartedAt;
|
|
164
169
|
const statusCode = response.statusCode;
|
|
165
|
-
|
|
170
|
+
const statusLabel = statusCode >= 500
|
|
171
|
+
? ui_1.ui.red(String(statusCode))
|
|
172
|
+
: statusCode >= 400
|
|
173
|
+
? ui_1.ui.yellow(String(statusCode))
|
|
174
|
+
: ui_1.ui.green(String(statusCode));
|
|
175
|
+
const methodLabel = method === "GET"
|
|
176
|
+
? ui_1.ui.cyan(method)
|
|
177
|
+
: method === "POST"
|
|
178
|
+
? ui_1.ui.green(method)
|
|
179
|
+
: method === "DELETE"
|
|
180
|
+
? ui_1.ui.red(method)
|
|
181
|
+
: ui_1.ui.blue(method);
|
|
182
|
+
console.log(`${ui_1.ui.gray("req")} ${methodLabel} ${pathname} ${statusLabel} ${ui_1.ui.dim(`(${durationMs}ms)`)}`);
|
|
166
183
|
});
|
|
167
184
|
}
|
|
168
185
|
if (corsEnabled && request.method === "OPTIONS") {
|
|
@@ -177,9 +194,10 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
177
194
|
if (request.method === "GET") {
|
|
178
195
|
const configuredRoute = configuredRoutes[pathname];
|
|
179
196
|
if (configuredRoute) {
|
|
197
|
+
const count = getCountOverrideFromUrl(request.url) ?? configuredRoute.count;
|
|
180
198
|
const payloadKey = toCollectionKey(pathname);
|
|
181
199
|
sendJson(response, 200, {
|
|
182
|
-
[payloadKey]: Array.from({ length:
|
|
200
|
+
[payloadKey]: Array.from({ length: count }, () => createRecordFromSchemaName(configuredRoute.schema, schemaDefinitions)),
|
|
183
201
|
}, { cors: corsEnabled, delay: responseDelay });
|
|
184
202
|
return;
|
|
185
203
|
}
|
|
@@ -187,11 +205,38 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
187
205
|
if (apiSpec) {
|
|
188
206
|
const matchedRoute = apiSpec.routes.find((route) => route.method === request.method && route.path === pathname);
|
|
189
207
|
if (matchedRoute) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
const method = request.method ?? "GET";
|
|
209
|
+
if (method === "GET") {
|
|
210
|
+
const count = getCountOverrideFromUrl(request.url) ?? 5;
|
|
211
|
+
const payloadKey = toCollectionKey(matchedRoute.path);
|
|
212
|
+
sendJson(response, 200, {
|
|
213
|
+
[payloadKey]: Array.from({ length: count }, () => createRecordFromRoute(matchedRoute)),
|
|
214
|
+
}, { cors: corsEnabled, delay: responseDelay });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (method === "DELETE") {
|
|
218
|
+
sendJson(response, 200, { message: `Deleted resource at ${matchedRoute.path}` }, { cors: corsEnabled, delay: responseDelay });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const bodyChunks = [];
|
|
222
|
+
request.on("data", (chunk) => bodyChunks.push(chunk));
|
|
223
|
+
request.on("end", () => {
|
|
224
|
+
let requestBody = {};
|
|
225
|
+
try {
|
|
226
|
+
const raw = Buffer.concat(bodyChunks).toString("utf-8");
|
|
227
|
+
if (raw.trim()) {
|
|
228
|
+
requestBody = JSON.parse(raw);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch { }
|
|
232
|
+
const generatedRecord = createRecordFromRoute(matchedRoute);
|
|
233
|
+
const merged = { ...generatedRecord, ...requestBody };
|
|
234
|
+
const statusCode = method === "POST" ? 201 : 200;
|
|
235
|
+
sendJson(response, statusCode, merged, {
|
|
236
|
+
cors: corsEnabled,
|
|
237
|
+
delay: responseDelay,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
195
240
|
return;
|
|
196
241
|
}
|
|
197
242
|
}
|
|
@@ -201,7 +246,7 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
201
246
|
}, { cors: corsEnabled, delay: responseDelay });
|
|
202
247
|
});
|
|
203
248
|
server.listen(port, host, () => {
|
|
204
|
-
console.log(
|
|
249
|
+
console.log(`${ui_1.ui.green("ready")} Mock API running at ${ui_1.ui.bold(`http://${host}:${port}`)}`);
|
|
205
250
|
});
|
|
206
251
|
return server;
|
|
207
252
|
};
|