fexapi 0.1.5 → 0.2.1
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.js +2 -2
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +48 -47
- package/dist/cli/ui.d.ts +34 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +137 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +5 -7
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +91 -23
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +71 -23
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +7 -6
- package/dist/index.js +30 -21
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +30 -13
- 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.js
CHANGED
|
@@ -90,7 +90,7 @@ const parseServeOptions = (serveArgs) => {
|
|
|
90
90
|
if (positionalArgs.length > 0) {
|
|
91
91
|
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
92
92
|
}
|
|
93
|
-
const host = hostValue ?? "
|
|
93
|
+
const host = hostValue ?? "localhost";
|
|
94
94
|
const port = portValue ? Number(portValue) : undefined;
|
|
95
95
|
if (port !== undefined &&
|
|
96
96
|
(!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
@@ -150,7 +150,7 @@ const parseDevOptions = (devArgs) => {
|
|
|
150
150
|
if (positionalArgs.length > 0) {
|
|
151
151
|
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
152
152
|
}
|
|
153
|
-
const host = hostValue ?? "
|
|
153
|
+
const host = hostValue ?? "localhost";
|
|
154
154
|
const port = portValue ? Number(portValue) : undefined;
|
|
155
155
|
if (port !== undefined &&
|
|
156
156
|
(!Number.isInteger(port) || port < 1 || port > 65535)) {
|
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,34 @@
|
|
|
1
|
+
export declare const ui: {
|
|
2
|
+
bold: (text: string) => string;
|
|
3
|
+
dim: (text: string) => string;
|
|
4
|
+
cyan: (text: string) => string;
|
|
5
|
+
magenta: (text: string) => string;
|
|
6
|
+
blue: (text: string) => string;
|
|
7
|
+
green: (text: string) => string;
|
|
8
|
+
yellow: (text: string) => string;
|
|
9
|
+
red: (text: string) => string;
|
|
10
|
+
gray: (text: string) => string;
|
|
11
|
+
};
|
|
12
|
+
type SpinnerController = {
|
|
13
|
+
update: (text: string) => void;
|
|
14
|
+
succeed: (text: string) => void;
|
|
15
|
+
fail: (text: string) => void;
|
|
16
|
+
stop: () => void;
|
|
17
|
+
};
|
|
18
|
+
export declare const startSpinner: (initialText: string) => SpinnerController;
|
|
19
|
+
export declare const nowMs: () => number;
|
|
20
|
+
export declare const formatDuration: (startMs: number) => string;
|
|
21
|
+
export declare const printSummaryCard: (title: string, rows: Array<{
|
|
22
|
+
label: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}>) => void;
|
|
25
|
+
export declare const printBanner: () => void;
|
|
26
|
+
export declare const printSpacer: () => void;
|
|
27
|
+
export declare const logInfo: (message: string) => void;
|
|
28
|
+
export declare const logSuccess: (message: string) => void;
|
|
29
|
+
export declare const logWarn: (message: string) => void;
|
|
30
|
+
export declare const logError: (message: string) => void;
|
|
31
|
+
export declare const logStep: (message: string) => void;
|
|
32
|
+
export declare const formatCommand: (command: string) => string;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,EAAE;iBACA,MAAM,KAAG,MAAM;gBAChB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;oBACZ,MAAM,KAAG,MAAM;iBAClB,MAAM,KAAG,MAAM;kBACd,MAAM,KAAG,MAAM;mBACd,MAAM,KAAG,MAAM;gBAClB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;CAC7B,CAAC;AAYF,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,aAAa,MAAM,KAAG,iBA0ClD,CAAC;AAEF,eAAO,MAAM,KAAK,QAAO,MAAoB,CAAC;AAE9C,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,MAQhD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,MAAM,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,KAC5C,IAyBF,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAO9B,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,137 @@
|
|
|
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.printSummaryCard = exports.formatDuration = exports.nowMs = exports.startSpinner = exports.ui = void 0;
|
|
4
|
+
const shouldUseColor = () => {
|
|
5
|
+
return Boolean(process.stdout.isTTY);
|
|
6
|
+
};
|
|
7
|
+
const colorEnabled = shouldUseColor();
|
|
8
|
+
const interactive = Boolean(process.stdout.isTTY);
|
|
9
|
+
const paint = (code, text) => {
|
|
10
|
+
if (!colorEnabled) {
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
return `\u001b[${code}m${text}\u001b[0m`;
|
|
14
|
+
};
|
|
15
|
+
exports.ui = {
|
|
16
|
+
bold: (text) => paint("1", text),
|
|
17
|
+
dim: (text) => paint("2", text),
|
|
18
|
+
cyan: (text) => paint("36", text),
|
|
19
|
+
magenta: (text) => paint("35", text),
|
|
20
|
+
blue: (text) => paint("94", text),
|
|
21
|
+
green: (text) => paint("32", text),
|
|
22
|
+
yellow: (text) => paint("33", text),
|
|
23
|
+
red: (text) => paint("31", text),
|
|
24
|
+
gray: (text) => paint("90", text),
|
|
25
|
+
};
|
|
26
|
+
const SPINNER_FRAMES = ["-", "\\", "|", "/"];
|
|
27
|
+
const clearCurrentLine = () => {
|
|
28
|
+
if (!interactive) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write("\r\u001b[2K");
|
|
32
|
+
};
|
|
33
|
+
const startSpinner = (initialText) => {
|
|
34
|
+
if (!interactive) {
|
|
35
|
+
(0, exports.logStep)(initialText);
|
|
36
|
+
return {
|
|
37
|
+
update: (text) => (0, exports.logStep)(text),
|
|
38
|
+
succeed: (text) => (0, exports.logSuccess)(text),
|
|
39
|
+
fail: (text) => (0, exports.logError)(text),
|
|
40
|
+
stop: () => undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let text = initialText;
|
|
44
|
+
let frameIndex = 0;
|
|
45
|
+
const render = () => {
|
|
46
|
+
const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length] ?? "-";
|
|
47
|
+
frameIndex += 1;
|
|
48
|
+
process.stdout.write(`\r${exports.ui.cyan(frame)} ${exports.ui.bold(text)}`);
|
|
49
|
+
};
|
|
50
|
+
render();
|
|
51
|
+
const timer = setInterval(render, 80);
|
|
52
|
+
return {
|
|
53
|
+
update: (nextText) => {
|
|
54
|
+
text = nextText;
|
|
55
|
+
},
|
|
56
|
+
succeed: (finalText) => {
|
|
57
|
+
clearInterval(timer);
|
|
58
|
+
clearCurrentLine();
|
|
59
|
+
console.log(`${exports.ui.green("ok")} ${finalText}`);
|
|
60
|
+
},
|
|
61
|
+
fail: (finalText) => {
|
|
62
|
+
clearInterval(timer);
|
|
63
|
+
clearCurrentLine();
|
|
64
|
+
console.log(`${exports.ui.red("err")} ${finalText}`);
|
|
65
|
+
},
|
|
66
|
+
stop: () => {
|
|
67
|
+
clearInterval(timer);
|
|
68
|
+
clearCurrentLine();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
exports.startSpinner = startSpinner;
|
|
73
|
+
const nowMs = () => Date.now();
|
|
74
|
+
exports.nowMs = nowMs;
|
|
75
|
+
const formatDuration = (startMs) => {
|
|
76
|
+
const elapsedMs = Date.now() - startMs;
|
|
77
|
+
if (elapsedMs < 1000) {
|
|
78
|
+
return `${elapsedMs}ms`;
|
|
79
|
+
}
|
|
80
|
+
return `${(elapsedMs / 1000).toFixed(2)}s`;
|
|
81
|
+
};
|
|
82
|
+
exports.formatDuration = formatDuration;
|
|
83
|
+
const printSummaryCard = (title, rows) => {
|
|
84
|
+
const contentRows = [{ label: "", value: title }, ...rows];
|
|
85
|
+
const maxLabel = Math.max(...contentRows.map((row) => row.label.length));
|
|
86
|
+
const maxValue = Math.max(...contentRows.map((row) => row.value.length));
|
|
87
|
+
const cardWidth = Math.max(40, maxLabel + maxValue + 7);
|
|
88
|
+
const border = `+${"-".repeat(cardWidth - 2)}+`;
|
|
89
|
+
console.log(exports.ui.gray(border));
|
|
90
|
+
const titleText = exports.ui.bold(exports.ui.cyan(title));
|
|
91
|
+
const titleLine = `| ${titleText}${" ".repeat(Math.max(0, cardWidth - 4 - title.length))} |`;
|
|
92
|
+
console.log(titleLine);
|
|
93
|
+
console.log(exports.ui.gray(`|${"-".repeat(cardWidth - 2)}|`));
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
const label = row.label.padEnd(maxLabel, " ");
|
|
96
|
+
const value = row.value;
|
|
97
|
+
const spaces = " ".repeat(Math.max(1, cardWidth - 4 - label.length - 3 - value.length));
|
|
98
|
+
console.log(`| ${exports.ui.dim(label)} : ${exports.ui.bold(value)}${spaces}|`);
|
|
99
|
+
}
|
|
100
|
+
console.log(exports.ui.gray(border));
|
|
101
|
+
};
|
|
102
|
+
exports.printSummaryCard = printSummaryCard;
|
|
103
|
+
const printBanner = () => {
|
|
104
|
+
console.log(exports.ui.bold(exports.ui.cyan("fexapi")) +
|
|
105
|
+
exports.ui.gray(" ") +
|
|
106
|
+
exports.ui.magenta("mock") +
|
|
107
|
+
exports.ui.gray(" api toolkit"));
|
|
108
|
+
};
|
|
109
|
+
exports.printBanner = printBanner;
|
|
110
|
+
const printSpacer = () => {
|
|
111
|
+
console.log("");
|
|
112
|
+
};
|
|
113
|
+
exports.printSpacer = printSpacer;
|
|
114
|
+
const logInfo = (message) => {
|
|
115
|
+
console.log(`${exports.ui.blue("info")} ${message}`);
|
|
116
|
+
};
|
|
117
|
+
exports.logInfo = logInfo;
|
|
118
|
+
const logSuccess = (message) => {
|
|
119
|
+
console.log(`${exports.ui.green("ok ")} ${message}`);
|
|
120
|
+
};
|
|
121
|
+
exports.logSuccess = logSuccess;
|
|
122
|
+
const logWarn = (message) => {
|
|
123
|
+
console.log(`${exports.ui.yellow("warn")} ${message}`);
|
|
124
|
+
};
|
|
125
|
+
exports.logWarn = logWarn;
|
|
126
|
+
const logError = (message) => {
|
|
127
|
+
console.error(`${exports.ui.red("err ")} ${message}`);
|
|
128
|
+
};
|
|
129
|
+
exports.logError = logError;
|
|
130
|
+
const logStep = (message) => {
|
|
131
|
+
console.log(`${exports.ui.cyan("->")} ${message}`);
|
|
132
|
+
};
|
|
133
|
+
exports.logStep = logStep;
|
|
134
|
+
const formatCommand = (command) => {
|
|
135
|
+
return exports.ui.bold(command);
|
|
136
|
+
};
|
|
137
|
+
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;
|
|
@@ -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,10 +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
|
-
projectRoot,
|
|
75
|
-
];
|
|
73
|
+
const watchTargets = [(0, node_path_1.join)(projectRoot, "fexapi"), projectRoot];
|
|
76
74
|
for (const watchTarget of watchTargets) {
|
|
77
75
|
if (!(0, node_fs_1.existsSync)(watchTarget)) {
|
|
78
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":"AAyBA,eAAO,MAAM,kBAAkB,QAAO,MA8KrC,CAAC"}
|
|
@@ -4,12 +4,20 @@ 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");
|
|
10
|
+
const createRouteSignature = (value) => {
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
port: value.port,
|
|
13
|
+
routes: value.routes,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
9
16
|
const generateFromSchema = () => {
|
|
17
|
+
const startedAtMs = (0, ui_1.nowMs)();
|
|
10
18
|
const projectRoot = (0, paths_1.resolveProjectRoot)();
|
|
11
19
|
if (!projectRoot) {
|
|
12
|
-
|
|
20
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
13
21
|
return 1;
|
|
14
22
|
}
|
|
15
23
|
const schemaPath = (0, node_path_1.join)(projectRoot, "fexapi", "schema.fexapi");
|
|
@@ -17,19 +25,41 @@ const generateFromSchema = () => {
|
|
|
17
25
|
const migrationsDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "migrations");
|
|
18
26
|
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
19
27
|
if (!(0, node_fs_1.existsSync)(schemaPath)) {
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
(0, ui_1.logError)(`Schema file not found: ${schemaPath}`);
|
|
29
|
+
(0, ui_1.logError)("Run `fexapi init` first.");
|
|
22
30
|
return 1;
|
|
23
31
|
}
|
|
32
|
+
const generationSpinner = (0, ui_1.startSpinner)("Reading schema");
|
|
24
33
|
const schemaText = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
|
|
34
|
+
generationSpinner.update("Parsing schema routes");
|
|
25
35
|
const parsed = (0, schema_1.parseFexapiSchema)(schemaText);
|
|
26
36
|
if (parsed.errors.length > 0 || !parsed.schema) {
|
|
27
|
-
|
|
37
|
+
generationSpinner.fail("Schema parsing failed");
|
|
38
|
+
(0, ui_1.logError)("Failed to generate API from schema.fexapi");
|
|
28
39
|
for (const error of parsed.errors) {
|
|
29
|
-
|
|
40
|
+
(0, ui_1.logError)(`- ${error}`);
|
|
30
41
|
}
|
|
31
42
|
return 1;
|
|
32
43
|
}
|
|
44
|
+
generationSpinner.update("Resolving cache state");
|
|
45
|
+
const previousGenerated = (0, node_fs_1.existsSync)(generatedPath)
|
|
46
|
+
? (() => {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse((0, node_fs_1.readFileSync)(generatedPath, "utf-8"));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
})()
|
|
54
|
+
: undefined;
|
|
55
|
+
const nextSignature = createRouteSignature({
|
|
56
|
+
port: parsed.schema.port,
|
|
57
|
+
routes: parsed.schema.routes,
|
|
58
|
+
});
|
|
59
|
+
const previousSignature = previousGenerated
|
|
60
|
+
? createRouteSignature(previousGenerated)
|
|
61
|
+
: undefined;
|
|
62
|
+
const schemaChanged = nextSignature !== previousSignature;
|
|
33
63
|
const generated = {
|
|
34
64
|
schemaVersion: 1,
|
|
35
65
|
generatedAt: new Date().toISOString(),
|
|
@@ -37,25 +67,26 @@ const generateFromSchema = () => {
|
|
|
37
67
|
routes: parsed.schema.routes,
|
|
38
68
|
};
|
|
39
69
|
(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
70
|
const migrationPath = (0, node_path_1.join)(migrationsDirectoryPath, "schema.json");
|
|
50
71
|
const migration = {
|
|
51
|
-
migrationId,
|
|
72
|
+
migrationId: new Date().toISOString().replace(/[.:]/g, "-"),
|
|
52
73
|
sourceSchema: "fexapi/schema.fexapi",
|
|
53
74
|
createdAt: generated.generatedAt,
|
|
54
75
|
port: parsed.schema.port,
|
|
55
76
|
routes: parsed.schema.routes,
|
|
56
77
|
};
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
let generatedStatus = "cached";
|
|
79
|
+
let migrationStatus = "cached";
|
|
80
|
+
if (schemaChanged || !(0, node_fs_1.existsSync)(generatedPath)) {
|
|
81
|
+
generationSpinner.update("Writing generated API spec");
|
|
82
|
+
(0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
|
|
83
|
+
generatedStatus = "changed";
|
|
84
|
+
}
|
|
85
|
+
if (schemaChanged || !(0, node_fs_1.existsSync)(migrationPath)) {
|
|
86
|
+
generationSpinner.update("Updating migration snapshot");
|
|
87
|
+
(0, node_fs_1.writeFileSync)(migrationPath, `${JSON.stringify(migration, null, 2)}\n`, "utf-8");
|
|
88
|
+
migrationStatus = "changed";
|
|
89
|
+
}
|
|
59
90
|
let existingConfig = {};
|
|
60
91
|
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
61
92
|
try {
|
|
@@ -69,13 +100,50 @@ const generateFromSchema = () => {
|
|
|
69
100
|
...existingConfig,
|
|
70
101
|
schemaPath: "fexapi/schema.fexapi",
|
|
71
102
|
generatedPath: constants_1.GENERATED_SPEC_RELATIVE_PATH,
|
|
72
|
-
lastGeneratedAt: new Date().toISOString(),
|
|
103
|
+
...(schemaChanged ? { lastGeneratedAt: new Date().toISOString() } : {}),
|
|
73
104
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
105
|
+
generationSpinner.update("Syncing project config");
|
|
106
|
+
const nextConfigText = `${JSON.stringify(updatedConfig, null, 2)}\n`;
|
|
107
|
+
const previousConfigText = (0, node_fs_1.existsSync)(configPath)
|
|
108
|
+
? (0, node_fs_1.readFileSync)(configPath, "utf-8")
|
|
109
|
+
: undefined;
|
|
110
|
+
let configStatus = "cached";
|
|
111
|
+
if (previousConfigText !== nextConfigText) {
|
|
112
|
+
(0, node_fs_1.writeFileSync)(configPath, nextConfigText, "utf-8");
|
|
113
|
+
configStatus = "changed";
|
|
114
|
+
}
|
|
115
|
+
generationSpinner.succeed(`Generate complete (${schemaChanged ? "changed" : "cached"})`);
|
|
116
|
+
(0, ui_1.printSpacer)();
|
|
117
|
+
(0, ui_1.printSummaryCard)("Generate Summary", [
|
|
118
|
+
{
|
|
119
|
+
label: "routes",
|
|
120
|
+
value: ui_1.ui.cyan(String(parsed.schema.routes.length)),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: "port",
|
|
124
|
+
value: ui_1.ui.cyan(String(parsed.schema.port)),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "schema source",
|
|
128
|
+
value: "fexapi/schema.fexapi",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: "generated.api.json",
|
|
132
|
+
value: generatedStatus === "changed" ? ui_1.ui.green("changed") : ui_1.ui.gray("cached"),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: "migration",
|
|
136
|
+
value: migrationStatus === "changed" ? ui_1.ui.green("changed") : ui_1.ui.gray("cached"),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
label: "config",
|
|
140
|
+
value: configStatus === "changed" ? ui_1.ui.green("changed") : ui_1.ui.gray("cached"),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
label: "time",
|
|
144
|
+
value: ui_1.ui.bold((0, ui_1.formatDuration)(startedAtMs)),
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
79
147
|
return 0;
|
|
80
148
|
};
|
|
81
149
|
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":"AAuMA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CA0MjB,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -6,9 +6,10 @@ 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
|
-
const DEFAULT_INIT_PORT =
|
|
12
|
+
const DEFAULT_INIT_PORT = 4000;
|
|
12
13
|
const parseYesNo = (value, defaultValue) => {
|
|
13
14
|
const normalized = value.trim().toLowerCase();
|
|
14
15
|
if (!normalized) {
|
|
@@ -140,9 +141,10 @@ const SAMPLE_POST_SCHEMA = [
|
|
|
140
141
|
" type: date",
|
|
141
142
|
].join("\n");
|
|
142
143
|
const initializeProject = async ({ force, }) => {
|
|
144
|
+
const initStartedAtMs = (0, ui_1.nowMs)();
|
|
143
145
|
const packageJsonPath = (0, paths_1.findClosestPackageJson)(process.cwd());
|
|
144
146
|
if (!packageJsonPath) {
|
|
145
|
-
|
|
147
|
+
(0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
|
|
146
148
|
return 1;
|
|
147
149
|
}
|
|
148
150
|
const projectRoot = (0, node_path_1.dirname)(packageJsonPath);
|
|
@@ -155,6 +157,7 @@ const initializeProject = async ({ force, }) => {
|
|
|
155
157
|
const userSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "user.yaml");
|
|
156
158
|
const postSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "post.yaml");
|
|
157
159
|
const wizardAnswers = await askInitWizardQuestions();
|
|
160
|
+
const initSpinner = (0, ui_1.startSpinner)("Scaffolding fexapi project files");
|
|
158
161
|
(0, node_fs_1.mkdirSync)(fexapiDirectoryPath, { recursive: true });
|
|
159
162
|
const configExists = (0, node_fs_1.existsSync)(configPath);
|
|
160
163
|
const schemaExists = (0, node_fs_1.existsSync)(schemaPath);
|
|
@@ -170,13 +173,16 @@ const initializeProject = async ({ force, }) => {
|
|
|
170
173
|
createdAt: new Date().toISOString(),
|
|
171
174
|
};
|
|
172
175
|
if (!configExists || force) {
|
|
176
|
+
initSpinner.update("Writing fexapi.config.json");
|
|
173
177
|
(0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
174
178
|
}
|
|
175
179
|
if (!schemaExists || force) {
|
|
180
|
+
initSpinner.update("Writing fexapi/schema.fexapi");
|
|
176
181
|
(0, node_fs_1.writeFileSync)(schemaPath, `${(0, detect_1.getSchemaTemplate)(detectedProject.primaryFramework, wizardAnswers.port)}\n`, "utf-8");
|
|
177
182
|
}
|
|
178
183
|
const runtimeConfigExists = (0, node_fs_1.existsSync)(runtimeConfigPath);
|
|
179
184
|
if (!runtimeConfigExists || force) {
|
|
185
|
+
initSpinner.update("Writing fexapi.config.js");
|
|
180
186
|
(0, node_fs_1.writeFileSync)(runtimeConfigPath, `${getRuntimeConfigTemplate({
|
|
181
187
|
port: wizardAnswers.port,
|
|
182
188
|
cors: wizardAnswers.cors,
|
|
@@ -189,6 +195,7 @@ const initializeProject = async ({ force, }) => {
|
|
|
189
195
|
(0, node_fs_1.mkdirSync)(schemasDirectoryPath, { recursive: true });
|
|
190
196
|
const userSchemaExists = (0, node_fs_1.existsSync)(userSchemaPath);
|
|
191
197
|
if (!userSchemaExists || force) {
|
|
198
|
+
initSpinner.update("Writing sample user schema");
|
|
192
199
|
(0, node_fs_1.writeFileSync)(userSchemaPath, `${SAMPLE_USER_SCHEMA}\n`, "utf-8");
|
|
193
200
|
userSchemaStatus = userSchemaExists ? "overwritten" : "created";
|
|
194
201
|
}
|
|
@@ -197,6 +204,7 @@ const initializeProject = async ({ force, }) => {
|
|
|
197
204
|
}
|
|
198
205
|
const postSchemaExists = (0, node_fs_1.existsSync)(postSchemaPath);
|
|
199
206
|
if (!postSchemaExists || force) {
|
|
207
|
+
initSpinner.update("Writing sample post schema");
|
|
200
208
|
(0, node_fs_1.writeFileSync)(postSchemaPath, `${SAMPLE_POST_SCHEMA}\n`, "utf-8");
|
|
201
209
|
postSchemaStatus = postSchemaExists ? "overwritten" : "created";
|
|
202
210
|
}
|
|
@@ -204,65 +212,105 @@ const initializeProject = async ({ force, }) => {
|
|
|
204
212
|
postSchemaStatus = "exists";
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
initSpinner.succeed("Project scaffolding complete");
|
|
216
|
+
(0, ui_1.logSuccess)(`Initialized fexapi in ${projectRoot}`);
|
|
217
|
+
(0, ui_1.logInfo)(`Detected framework: ${detectedProject.primaryFramework}`);
|
|
218
|
+
(0, ui_1.logInfo)(`Detected frameworks: ${detectedProject.frameworks.join(", ")}`);
|
|
210
219
|
if (detectedProject.tooling.length > 0) {
|
|
211
|
-
|
|
220
|
+
(0, ui_1.logInfo)(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
|
|
212
221
|
}
|
|
222
|
+
(0, ui_1.printSpacer)();
|
|
213
223
|
if (configExists && !force) {
|
|
214
|
-
|
|
224
|
+
(0, ui_1.logWarn)(`Exists ${configPath}`);
|
|
215
225
|
}
|
|
216
226
|
else if (configExists && force) {
|
|
217
|
-
|
|
227
|
+
(0, ui_1.logSuccess)(`Overwritten ${configPath}`);
|
|
218
228
|
}
|
|
219
229
|
else {
|
|
220
|
-
|
|
230
|
+
(0, ui_1.logSuccess)(`Created ${configPath}`);
|
|
221
231
|
}
|
|
222
232
|
if (schemaExists && !force) {
|
|
223
|
-
|
|
233
|
+
(0, ui_1.logWarn)(`Exists ${schemaPath}`);
|
|
224
234
|
}
|
|
225
235
|
else if (schemaExists && force) {
|
|
226
|
-
|
|
236
|
+
(0, ui_1.logSuccess)(`Overwritten ${schemaPath}`);
|
|
227
237
|
}
|
|
228
238
|
else {
|
|
229
|
-
|
|
239
|
+
(0, ui_1.logSuccess)(`Created ${schemaPath}`);
|
|
230
240
|
}
|
|
231
241
|
if (runtimeConfigExists && !force) {
|
|
232
|
-
|
|
242
|
+
(0, ui_1.logWarn)(`Exists ${runtimeConfigPath}`);
|
|
233
243
|
}
|
|
234
244
|
else if (runtimeConfigExists && force) {
|
|
235
|
-
|
|
245
|
+
(0, ui_1.logSuccess)(`Overwritten ${runtimeConfigPath}`);
|
|
236
246
|
}
|
|
237
247
|
else {
|
|
238
|
-
|
|
248
|
+
(0, ui_1.logSuccess)(`Created ${runtimeConfigPath}`);
|
|
239
249
|
}
|
|
240
250
|
if (wizardAnswers.generateSampleSchemas) {
|
|
241
251
|
if (userSchemaStatus === "exists") {
|
|
242
|
-
|
|
252
|
+
(0, ui_1.logWarn)(`Exists ${userSchemaPath}`);
|
|
243
253
|
}
|
|
244
254
|
else if (userSchemaStatus === "overwritten") {
|
|
245
|
-
|
|
255
|
+
(0, ui_1.logSuccess)(`Overwritten ${userSchemaPath}`);
|
|
246
256
|
}
|
|
247
257
|
else if (userSchemaStatus === "created") {
|
|
248
|
-
|
|
258
|
+
(0, ui_1.logSuccess)(`Created ${userSchemaPath}`);
|
|
249
259
|
}
|
|
250
260
|
if (postSchemaStatus === "exists") {
|
|
251
|
-
|
|
261
|
+
(0, ui_1.logWarn)(`Exists ${postSchemaPath}`);
|
|
252
262
|
}
|
|
253
263
|
else if (postSchemaStatus === "overwritten") {
|
|
254
|
-
|
|
264
|
+
(0, ui_1.logSuccess)(`Overwritten ${postSchemaPath}`);
|
|
255
265
|
}
|
|
256
266
|
else if (postSchemaStatus === "created") {
|
|
257
|
-
|
|
267
|
+
(0, ui_1.logSuccess)(`Created ${postSchemaPath}`);
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
270
|
else {
|
|
261
|
-
|
|
271
|
+
(0, ui_1.logWarn)("Sample schemas were skipped.");
|
|
262
272
|
}
|
|
263
273
|
if (detectedProject.primaryFramework === "unknown") {
|
|
264
|
-
|
|
274
|
+
(0, ui_1.logWarn)("No known framework dependency found. Update fexapi.config.json and schema.fexapi if needed.");
|
|
265
275
|
}
|
|
276
|
+
(0, ui_1.printSpacer)();
|
|
277
|
+
const createdFiles = [
|
|
278
|
+
!configExists || force,
|
|
279
|
+
!schemaExists || force,
|
|
280
|
+
!runtimeConfigExists || force,
|
|
281
|
+
userSchemaStatus === "created" || userSchemaStatus === "overwritten",
|
|
282
|
+
postSchemaStatus === "created" || postSchemaStatus === "overwritten",
|
|
283
|
+
].filter(Boolean).length;
|
|
284
|
+
(0, ui_1.printSummaryCard)("Init Summary", [
|
|
285
|
+
{
|
|
286
|
+
label: "framework",
|
|
287
|
+
value: detectedProject.primaryFramework,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
label: "port",
|
|
291
|
+
value: ui_1.ui.cyan(String(wizardAnswers.port)),
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
label: "cors",
|
|
295
|
+
value: wizardAnswers.cors ? ui_1.ui.green("enabled") : ui_1.ui.gray("disabled"),
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
label: "sample schemas",
|
|
299
|
+
value: wizardAnswers.generateSampleSchemas
|
|
300
|
+
? ui_1.ui.green("enabled")
|
|
301
|
+
: ui_1.ui.gray("disabled"),
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
label: "files changed",
|
|
305
|
+
value: ui_1.ui.bold(String(createdFiles)),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
label: "time",
|
|
309
|
+
value: ui_1.ui.bold((0, ui_1.formatDuration)(initStartedAtMs)),
|
|
310
|
+
},
|
|
311
|
+
]);
|
|
312
|
+
(0, ui_1.printSpacer)();
|
|
313
|
+
(0, ui_1.logInfo)(`Next: ${(0, ui_1.formatCommand)("fexapi generate")} then ${(0, ui_1.formatCommand)("fexapi serve")}`);
|
|
266
314
|
return 0;
|
|
267
315
|
};
|
|
268
316
|
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);
|
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
|
});
|
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,7 +3,8 @@ 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
|
|
6
|
+
const ui_1 = require("./cli/ui");
|
|
7
|
+
const DEFAULT_HOST = "localhost";
|
|
7
8
|
const DEFAULT_PORT = 4000;
|
|
8
9
|
const sendJson = (response, statusCode, payload, options) => {
|
|
9
10
|
const send = () => {
|
|
@@ -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;
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
const parsedCount = Number(rawCount);
|
|
146
|
+
if (!Number.isFinite(parsedCount)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
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
|
}
|
|
@@ -189,7 +207,7 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
189
207
|
if (matchedRoute) {
|
|
190
208
|
const method = request.method ?? "GET";
|
|
191
209
|
if (method === "GET") {
|
|
192
|
-
const count =
|
|
210
|
+
const count = getCountOverrideFromUrl(request.url) ?? 5;
|
|
193
211
|
const payloadKey = toCollectionKey(matchedRoute.path);
|
|
194
212
|
sendJson(response, 200, {
|
|
195
213
|
[payloadKey]: Array.from({ length: count }, () => createRecordFromRoute(matchedRoute)),
|
|
@@ -210,8 +228,7 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
210
228
|
requestBody = JSON.parse(raw);
|
|
211
229
|
}
|
|
212
230
|
}
|
|
213
|
-
catch {
|
|
214
|
-
}
|
|
231
|
+
catch { }
|
|
215
232
|
const generatedRecord = createRecordFromRoute(matchedRoute);
|
|
216
233
|
const merged = { ...generatedRecord, ...requestBody };
|
|
217
234
|
const statusCode = method === "POST" ? 201 : 200;
|
|
@@ -229,7 +246,7 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
229
246
|
}, { cors: corsEnabled, delay: responseDelay });
|
|
230
247
|
});
|
|
231
248
|
server.listen(port, host, () => {
|
|
232
|
-
console.log(
|
|
249
|
+
console.log(`${ui_1.ui.green("ready")} Mock API running at ${ui_1.ui.bold(`http://${host}:${port}`)}`);
|
|
233
250
|
});
|
|
234
251
|
return server;
|
|
235
252
|
};
|