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 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 ?? "127.0.0.1";
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 ?? "127.0.0.1";
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)) {
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,YAkDrB,CAAC"}
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
- console.log("fexapi-cli");
6
- console.log("");
7
- console.log("Usage:");
8
- console.log(" fexapi init [--force]");
9
- console.log(" fexapi generate");
10
- console.log(" fexapi format");
11
- console.log(" fexapi dev [--watch] [--host <host>] [--port <number>] [--log]");
12
- console.log(" fexapi serve [--host <host>] [--port <number>] [--log]");
13
- console.log(" fexapi run [--host <host>] [--port <number>] [--log]");
14
- console.log(" fexapi [--host <host>] [--port <number>] [--log]");
15
- console.log(" fexapi --help");
16
- console.log("");
17
- console.log("Examples:");
18
- console.log(" fexapi init");
19
- console.log(" fexapi init --force");
20
- console.log(" fexapi generate");
21
- console.log(" fexapi format");
22
- console.log(" fexapi dev --watch");
23
- console.log(" fexapi dev --watch --log");
24
- console.log(" fexapi serve --log");
25
- console.log(" fexapi serve --host 127.0.0.1 --port 5000");
26
- console.log(" fexapi --port 4000");
27
- console.log("");
28
- console.log("Package manager usage:");
29
- console.log(" npx fexapi init");
30
- console.log(" pnpm dlx fexapi init");
31
- console.log(" yarn dlx fexapi init");
32
- console.log("");
33
- console.log("`fexapi init` creates:");
34
- console.log(" fexapi.config.json");
35
- console.log(" fexapi.config.js");
36
- console.log(" fexapi/schema.fexapi");
37
- console.log(" fexapi/schemas/*.yaml (optional, via wizard)");
38
- console.log("");
39
- console.log("Init wizard asks:");
40
- console.log(" What port? (default: 3000)");
41
- console.log(" Enable CORS? (Y/n)");
42
- console.log(" Generate sample schemas? (Y/n)");
43
- console.log("");
44
- console.log("Then run:");
45
- console.log(" # edit fexapi/schema.fexapi");
46
- console.log(" fexapi generate");
47
- console.log(" fexapi run");
48
- console.log("");
49
- console.log("Generate output:");
50
- console.log(" fexapi/generated.api.json");
51
- console.log(" fexapi/migrations/schema.json");
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;
@@ -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":"AAgCA,eAAO,MAAM,aAAa,GAAI,2CAK3B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,KAAG,MAsHH,CAAC"}
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"}
@@ -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
- console.error("Could not find package.json in this directory or parent directories.");
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
- console.log("Watch mode enabled. Restarting on config/schema changes...");
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
- console.log(`\n[watch] change detected (${reason})`);
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":"AAaA,eAAO,MAAM,kBAAkB,QAAO,MAyGrC,CAAC"}
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
- console.error("Could not find package.json in this directory or parent directories.");
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
- console.error(`Schema file not found: ${schemaPath}`);
21
- console.error("Run `fexapi init` first.");
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
- console.error("Failed to generate API from schema.fexapi");
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
- console.error(`- ${error}`);
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
- (0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
58
- (0, node_fs_1.writeFileSync)(migrationPath, `${JSON.stringify(migration, null, 2)}\n`, "utf-8");
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
- (0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
75
- console.log(`Generated API spec at ${generatedPath}`);
76
- console.log(`Migration updated at ${migrationPath}`);
77
- console.log(`Routes generated: ${parsed.schema.routes.length}`);
78
- console.log(`Configured server port: ${parsed.schema.port}`);
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":"AA0LA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CAmJjB,CAAC"}
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"}
@@ -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 = 3000;
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
- console.error("Could not find package.json in this directory or parent directories.");
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
- console.log(`Initialized Fexapi in ${projectRoot}`);
208
- console.log(`Detected framework: ${detectedProject.primaryFramework}`);
209
- console.log(`Detected frameworks: ${detectedProject.frameworks.join(", ")}`);
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
- console.log(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
220
+ (0, ui_1.logInfo)(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
212
221
  }
222
+ (0, ui_1.printSpacer)();
213
223
  if (configExists && !force) {
214
- console.log(`Exists ${configPath}`);
224
+ (0, ui_1.logWarn)(`Exists ${configPath}`);
215
225
  }
216
226
  else if (configExists && force) {
217
- console.log(`Overwritten ${configPath}`);
227
+ (0, ui_1.logSuccess)(`Overwritten ${configPath}`);
218
228
  }
219
229
  else {
220
- console.log(`Created ${configPath}`);
230
+ (0, ui_1.logSuccess)(`Created ${configPath}`);
221
231
  }
222
232
  if (schemaExists && !force) {
223
- console.log(`Exists ${schemaPath}`);
233
+ (0, ui_1.logWarn)(`Exists ${schemaPath}`);
224
234
  }
225
235
  else if (schemaExists && force) {
226
- console.log(`Overwritten ${schemaPath}`);
236
+ (0, ui_1.logSuccess)(`Overwritten ${schemaPath}`);
227
237
  }
228
238
  else {
229
- console.log(`Created ${schemaPath}`);
239
+ (0, ui_1.logSuccess)(`Created ${schemaPath}`);
230
240
  }
231
241
  if (runtimeConfigExists && !force) {
232
- console.log(`Exists ${runtimeConfigPath}`);
242
+ (0, ui_1.logWarn)(`Exists ${runtimeConfigPath}`);
233
243
  }
234
244
  else if (runtimeConfigExists && force) {
235
- console.log(`Overwritten ${runtimeConfigPath}`);
245
+ (0, ui_1.logSuccess)(`Overwritten ${runtimeConfigPath}`);
236
246
  }
237
247
  else {
238
- console.log(`Created ${runtimeConfigPath}`);
248
+ (0, ui_1.logSuccess)(`Created ${runtimeConfigPath}`);
239
249
  }
240
250
  if (wizardAnswers.generateSampleSchemas) {
241
251
  if (userSchemaStatus === "exists") {
242
- console.log(`Exists ${userSchemaPath}`);
252
+ (0, ui_1.logWarn)(`Exists ${userSchemaPath}`);
243
253
  }
244
254
  else if (userSchemaStatus === "overwritten") {
245
- console.log(`Overwritten ${userSchemaPath}`);
255
+ (0, ui_1.logSuccess)(`Overwritten ${userSchemaPath}`);
246
256
  }
247
257
  else if (userSchemaStatus === "created") {
248
- console.log(`Created ${userSchemaPath}`);
258
+ (0, ui_1.logSuccess)(`Created ${userSchemaPath}`);
249
259
  }
250
260
  if (postSchemaStatus === "exists") {
251
- console.log(`Exists ${postSchemaPath}`);
261
+ (0, ui_1.logWarn)(`Exists ${postSchemaPath}`);
252
262
  }
253
263
  else if (postSchemaStatus === "overwritten") {
254
- console.log(`Overwritten ${postSchemaPath}`);
264
+ (0, ui_1.logSuccess)(`Overwritten ${postSchemaPath}`);
255
265
  }
256
266
  else if (postSchemaStatus === "created") {
257
- console.log(`Created ${postSchemaPath}`);
267
+ (0, ui_1.logSuccess)(`Created ${postSchemaPath}`);
258
268
  }
259
269
  }
260
270
  else {
261
- console.log("Sample schemas were skipped.");
271
+ (0, ui_1.logWarn)("Sample schemas were skipped.");
262
272
  }
263
273
  if (detectedProject.primaryFramework === "unknown") {
264
- console.log("No known framework dependency found. Update fexapi.config.json and schema.fexapi if needed.");
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;AAQxC,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"}
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"}
@@ -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
- console.error("Could not find package.json in this directory or parent directories.");
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
- console.log(`Using routes from fexapi.config.js (${Object.keys(runtimeConfig.routes).length})`);
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
- console.log(`Loaded custom schemas from /schemas (${Object.keys(schemaDefinitions).length})`);
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
- console.log(`Using generated schema routes (${generatedSpec.routes.length}) from ${constants_1.GENERATED_SPEC_RELATIVE_PATH}`);
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
- console.log("No generated schema found. Run `fexapi generate` to serve schema-defined endpoints.");
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
- console.error("Error while shutting down server", error);
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
- console.log("Usage: fexapi init [--force]");
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
- console.error(initOptions.error);
24
- console.log("");
25
- console.log("Usage: fexapi init [--force]");
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
- console.log("Usage: fexapi generate");
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
- console.error(generateOptions.error);
39
- console.log("");
40
- console.log("Usage: fexapi generate");
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
- console.log("Usage: fexapi format");
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
- console.error(formatOptions.error);
54
- console.log("");
55
- console.log("Usage: fexapi format");
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
- console.log("Usage: fexapi dev [--watch] [--host <host>] [--port <number>] [--log]");
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
- console.error(devOptions.error);
69
- console.log("");
70
- console.log("Usage: fexapi dev [--watch] [--host <host>] [--port <number>] [--log]");
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
- console.error(options.error);
90
- console.log("");
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
- console.error(`Unknown command: ${firstArg}`);
105
- console.log("");
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
- console.error(`Unexpected error: ${message}`);
121
+ (0, ui_1.logError)(`Unexpected error: ${message}`);
113
122
  process.exit(1);
114
123
  });
@@ -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;AAExB,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;AAyLF,eAAO,MAAM,WAAW,GAAI,0EAOzB,aAAkB,wFAuIpB,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 DEFAULT_HOST = "127.0.0.1";
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 getCountFromUrl = (urlText, fallback = 5) => {
136
+ const getCountOverrideFromUrl = (urlText) => {
136
137
  if (!urlText) {
137
- return fallback;
138
+ return undefined;
138
139
  }
139
140
  const url = new URL(urlText, "http://localhost");
140
- const rawCount = Number(url.searchParams.get("count") ?? fallback);
141
- if (!Number.isFinite(rawCount)) {
142
- return fallback;
141
+ const rawCount = url.searchParams.get("count");
142
+ if (rawCount === null) {
143
+ return undefined;
143
144
  }
144
- return Math.min(Math.max(Math.floor(rawCount), 1), 50);
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
- console.log(`[${method}] ${pathname} ${statusCode} (${durationMs}ms)`);
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: configuredRoute.count }, () => createRecordFromSchemaName(configuredRoute.schema, schemaDefinitions)),
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 = getCountFromUrl(request.url, 5);
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(`Mock API running at http://${host}:${port}`);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fexapi",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "Mock API generation CLI tool for local development and testing",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",