fexapi 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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)) {
package/dist/cli/ui.d.ts CHANGED
@@ -2,12 +2,28 @@ export declare const ui: {
2
2
  bold: (text: string) => string;
3
3
  dim: (text: string) => string;
4
4
  cyan: (text: string) => string;
5
+ magenta: (text: string) => string;
5
6
  blue: (text: string) => string;
6
7
  green: (text: string) => string;
7
8
  yellow: (text: string) => string;
8
9
  red: (text: string) => string;
9
10
  gray: (text: string) => string;
11
+ white: (text: string) => string;
10
12
  };
13
+ type SpinnerController = {
14
+ update: (text: string) => void;
15
+ succeed: (text: string) => void;
16
+ fail: (text: string) => void;
17
+ stop: () => void;
18
+ };
19
+ export declare const startSpinner: (initialText: string) => SpinnerController;
20
+ export declare const nowMs: () => number;
21
+ export declare const formatDuration: (startMs: number) => string;
22
+ export declare const printSummaryCard: (title: string, rows: Array<{
23
+ label: string;
24
+ value: string;
25
+ }>) => void;
26
+ export declare const printGroupHeader: (title: string) => void;
11
27
  export declare const printBanner: () => void;
12
28
  export declare const printSpacer: () => void;
13
29
  export declare const logInfo: (message: string) => void;
@@ -16,4 +32,5 @@ export declare const logWarn: (message: string) => void;
16
32
  export declare const logError: (message: string) => void;
17
33
  export declare const logStep: (message: string) => void;
18
34
  export declare const formatCommand: (command: string) => string;
35
+ export {};
19
36
  //# sourceMappingURL=ui.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,EAAE;iBACA,MAAM,KAAG,MAAM;gBAChB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;iBACf,MAAM,KAAG,MAAM;kBACd,MAAM,KAAG,MAAM;mBACd,MAAM,KAAG,MAAM;gBAClB,MAAM,KAAG,MAAM;iBACd,MAAM,KAAG,MAAM;CAC7B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,KAAG,IAEzC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,MAE/C,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAgBA,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;kBACd,MAAM,KAAG,MAAM;CAC9B,CAAC;AA4DF,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,IAqDF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,IAQhD,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 CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatCommand = exports.logStep = exports.logError = exports.logWarn = exports.logSuccess = exports.logInfo = exports.printSpacer = exports.printBanner = exports.ui = void 0;
3
+ exports.formatCommand = exports.logStep = exports.logError = exports.logWarn = exports.logSuccess = exports.logInfo = exports.printSpacer = exports.printBanner = exports.printGroupHeader = exports.printSummaryCard = exports.formatDuration = exports.nowMs = exports.startSpinner = exports.ui = void 0;
4
4
  const shouldUseColor = () => {
5
5
  return Boolean(process.stdout.isTTY);
6
6
  };
7
7
  const colorEnabled = shouldUseColor();
8
+ const interactive = Boolean(process.stdout.isTTY);
9
+ const DEFAULT_TERMINAL_WIDTH = 80;
8
10
  const paint = (code, text) => {
9
11
  if (!colorEnabled) {
10
12
  return text;
@@ -15,14 +17,158 @@ exports.ui = {
15
17
  bold: (text) => paint("1", text),
16
18
  dim: (text) => paint("2", text),
17
19
  cyan: (text) => paint("36", text),
20
+ magenta: (text) => paint("35", text),
18
21
  blue: (text) => paint("94", text),
19
22
  green: (text) => paint("32", text),
20
23
  yellow: (text) => paint("33", text),
21
24
  red: (text) => paint("31", text),
22
25
  gray: (text) => paint("90", text),
26
+ white: (text) => paint("97", text),
23
27
  };
28
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
29
+ const ANSI_REGEX = /\u001b\[[0-9;]*m/g;
30
+ const stripAnsi = (text) => text.replace(ANSI_REGEX, "");
31
+ const visibleLength = (text) => stripAnsi(text).length;
32
+ const getTerminalWidth = () => {
33
+ const columns = process.stdout.columns;
34
+ if (!columns || Number.isNaN(columns)) {
35
+ return DEFAULT_TERMINAL_WIDTH;
36
+ }
37
+ return Math.max(40, columns);
38
+ };
39
+ const truncateText = (text, maxLength) => {
40
+ if (text.length <= maxLength) {
41
+ return text;
42
+ }
43
+ if (maxLength <= 1) {
44
+ return text.slice(0, maxLength);
45
+ }
46
+ return `${text.slice(0, maxLength - 1)}…`;
47
+ };
48
+ const styleCardValue = (value) => {
49
+ const normalized = value.trim().toLowerCase();
50
+ if (normalized === "changed" ||
51
+ normalized === "enabled" ||
52
+ normalized === "running") {
53
+ return exports.ui.green(exports.ui.bold(value));
54
+ }
55
+ if (normalized === "cached" || normalized === "disabled") {
56
+ return exports.ui.gray(value);
57
+ }
58
+ if (normalized === "stopped" || normalized === "failed") {
59
+ return exports.ui.red(exports.ui.bold(value));
60
+ }
61
+ return exports.ui.white(exports.ui.bold(value));
62
+ };
63
+ const clearCurrentLine = () => {
64
+ if (!interactive) {
65
+ return;
66
+ }
67
+ process.stdout.write("\r\u001b[2K");
68
+ };
69
+ const startSpinner = (initialText) => {
70
+ if (!interactive) {
71
+ (0, exports.logStep)(initialText);
72
+ return {
73
+ update: (text) => (0, exports.logStep)(text),
74
+ succeed: (text) => (0, exports.logSuccess)(text),
75
+ fail: (text) => (0, exports.logError)(text),
76
+ stop: () => undefined,
77
+ };
78
+ }
79
+ let text = initialText;
80
+ let frameIndex = 0;
81
+ const render = () => {
82
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length] ?? "-";
83
+ frameIndex += 1;
84
+ process.stdout.write(`\r${exports.ui.cyan(frame)} ${exports.ui.white(exports.ui.bold(text))}`);
85
+ };
86
+ render();
87
+ const timer = setInterval(render, 80);
88
+ return {
89
+ update: (nextText) => {
90
+ text = nextText;
91
+ },
92
+ succeed: (finalText) => {
93
+ clearInterval(timer);
94
+ clearCurrentLine();
95
+ console.log(`${exports.ui.green("✓")} ${exports.ui.white(finalText)}`);
96
+ },
97
+ fail: (finalText) => {
98
+ clearInterval(timer);
99
+ clearCurrentLine();
100
+ console.log(`${exports.ui.red("✕")} ${exports.ui.white(finalText)}`);
101
+ },
102
+ stop: () => {
103
+ clearInterval(timer);
104
+ clearCurrentLine();
105
+ },
106
+ };
107
+ };
108
+ exports.startSpinner = startSpinner;
109
+ const nowMs = () => Date.now();
110
+ exports.nowMs = nowMs;
111
+ const formatDuration = (startMs) => {
112
+ const elapsedMs = Date.now() - startMs;
113
+ if (elapsedMs < 1000) {
114
+ return `${elapsedMs}ms`;
115
+ }
116
+ return `${(elapsedMs / 1000).toFixed(2)}s`;
117
+ };
118
+ exports.formatDuration = formatDuration;
119
+ const printSummaryCard = (title, rows) => {
120
+ const terminalWidth = getTerminalWidth();
121
+ const compactMode = terminalWidth < 64;
122
+ if (compactMode) {
123
+ console.log(exports.ui.gray(`--- ${title} ---`));
124
+ for (const row of rows) {
125
+ console.log(`${exports.ui.dim(row.label)}: ${row.value}`);
126
+ }
127
+ console.log(exports.ui.gray("---------------"));
128
+ return;
129
+ }
130
+ const safeRows = rows.map((row) => ({
131
+ label: row.label,
132
+ value: row.value,
133
+ }));
134
+ const cardWidth = terminalWidth - 2;
135
+ const innerWidth = cardWidth - 2;
136
+ const labelWidth = Math.min(20, Math.max(10, ...safeRows.map((row) => visibleLength(row.label))));
137
+ const valueSpace = Math.max(8, innerWidth - 3 - labelWidth - 3);
138
+ const topBorder = `┌${"─".repeat(innerWidth)}┐`;
139
+ const divider = `├${"─".repeat(innerWidth)}┤`;
140
+ const bottomBorder = `└${"─".repeat(innerWidth)}┘`;
141
+ console.log(exports.ui.gray(topBorder));
142
+ const renderedTitle = truncateText(title, innerWidth - 2);
143
+ const titlePadding = " ".repeat(Math.max(0, innerWidth - 2 - visibleLength(renderedTitle)));
144
+ console.log(`│ ${exports.ui.bold(exports.ui.cyan(renderedTitle))}${titlePadding} │`);
145
+ console.log(exports.ui.gray(divider));
146
+ for (const row of safeRows) {
147
+ const rawValue = stripAnsi(row.value);
148
+ const value = truncateText(rawValue, valueSpace);
149
+ const label = row.label.padEnd(labelWidth, " ");
150
+ const styledValue = styleCardValue(value);
151
+ const spaces = " ".repeat(Math.max(1, innerWidth - 3 - visibleLength(label) - 3 - visibleLength(value)));
152
+ console.log(`│ ${exports.ui.dim(label)} ${exports.ui.gray("::")} ${styledValue}${spaces}│`);
153
+ }
154
+ console.log(exports.ui.gray(bottomBorder));
155
+ };
156
+ exports.printSummaryCard = printSummaryCard;
157
+ const printGroupHeader = (title) => {
158
+ const terminalWidth = getTerminalWidth();
159
+ const marker = exports.ui.gray("──");
160
+ const text = ` ${exports.ui.bold(title)} `;
161
+ const lineLength = Math.max(0, terminalWidth - visibleLength(title) - 4);
162
+ const left = marker;
163
+ const right = exports.ui.gray("─".repeat(Math.max(0, lineLength - 2)));
164
+ console.log(`${left}${text}${right}`);
165
+ };
166
+ exports.printGroupHeader = printGroupHeader;
24
167
  const printBanner = () => {
25
- console.log(exports.ui.bold(exports.ui.cyan("fexapi")) + exports.ui.gray(" mock api toolkit"));
168
+ console.log(exports.ui.bold(exports.ui.cyan("fexapi")) +
169
+ exports.ui.gray(" ") +
170
+ exports.ui.magenta("mock") +
171
+ exports.ui.gray(" api toolkit"));
26
172
  };
27
173
  exports.printBanner = printBanner;
28
174
  const printSpacer = () => {
@@ -30,23 +176,23 @@ const printSpacer = () => {
30
176
  };
31
177
  exports.printSpacer = printSpacer;
32
178
  const logInfo = (message) => {
33
- console.log(`${exports.ui.blue("info")} ${message}`);
179
+ console.log(`${exports.ui.blue("•")} ${exports.ui.blue("info")} ${exports.ui.white(message)}`);
34
180
  };
35
181
  exports.logInfo = logInfo;
36
182
  const logSuccess = (message) => {
37
- console.log(`${exports.ui.green("ok ")} ${message}`);
183
+ console.log(`${exports.ui.green("✓")} ${exports.ui.green("ok ")} ${exports.ui.white(message)}`);
38
184
  };
39
185
  exports.logSuccess = logSuccess;
40
186
  const logWarn = (message) => {
41
- console.log(`${exports.ui.yellow("warn")} ${message}`);
187
+ console.log(`${exports.ui.yellow("!")} ${exports.ui.yellow("warn")} ${exports.ui.white(message)}`);
42
188
  };
43
189
  exports.logWarn = logWarn;
44
190
  const logError = (message) => {
45
- console.error(`${exports.ui.red("err ")} ${message}`);
191
+ console.error(`${exports.ui.red("✕")} ${exports.ui.red("err ")} ${exports.ui.white(message)}`);
46
192
  };
47
193
  exports.logError = logError;
48
194
  const logStep = (message) => {
49
- console.log(`${exports.ui.cyan("->")} ${message}`);
195
+ console.log(`${exports.ui.cyan("")} ${exports.ui.white(message)}`);
50
196
  };
51
197
  exports.logStep = logStep;
52
198
  const formatCommand = (command) => {
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,kBAAkB,QAAO,MA0GrC,CAAC"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,kBAAkB,QAAO,MA6KrC,CAAC"}
@@ -7,7 +7,14 @@ const constants_1 = require("../constants");
7
7
  const ui_1 = require("../cli/ui");
8
8
  const paths_1 = require("../project/paths");
9
9
  const schema_1 = require("../schema");
10
+ const createRouteSignature = (value) => {
11
+ return JSON.stringify({
12
+ port: value.port,
13
+ routes: value.routes,
14
+ });
15
+ };
10
16
  const generateFromSchema = () => {
17
+ const startedAtMs = (0, ui_1.nowMs)();
11
18
  const projectRoot = (0, paths_1.resolveProjectRoot)();
12
19
  if (!projectRoot) {
13
20
  (0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
@@ -19,18 +26,41 @@ const generateFromSchema = () => {
19
26
  const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
20
27
  if (!(0, node_fs_1.existsSync)(schemaPath)) {
21
28
  (0, ui_1.logError)(`Schema file not found: ${schemaPath}`);
22
- (0, ui_1.logInfo)("Run `fexapi init` first.");
29
+ (0, ui_1.logError)("Run `fexapi init` first.");
23
30
  return 1;
24
31
  }
32
+ (0, ui_1.printGroupHeader)("Generate");
33
+ const generationSpinner = (0, ui_1.startSpinner)("Reading schema");
25
34
  const schemaText = (0, node_fs_1.readFileSync)(schemaPath, "utf-8");
35
+ generationSpinner.update("Parsing schema routes");
26
36
  const parsed = (0, schema_1.parseFexapiSchema)(schemaText);
27
37
  if (parsed.errors.length > 0 || !parsed.schema) {
38
+ generationSpinner.fail("Schema parsing failed");
28
39
  (0, ui_1.logError)("Failed to generate API from schema.fexapi");
29
40
  for (const error of parsed.errors) {
30
41
  (0, ui_1.logError)(`- ${error}`);
31
42
  }
32
43
  return 1;
33
44
  }
45
+ generationSpinner.update("Resolving cache state");
46
+ const previousGenerated = (0, node_fs_1.existsSync)(generatedPath)
47
+ ? (() => {
48
+ try {
49
+ return JSON.parse((0, node_fs_1.readFileSync)(generatedPath, "utf-8"));
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ })()
55
+ : undefined;
56
+ const nextSignature = createRouteSignature({
57
+ port: parsed.schema.port,
58
+ routes: parsed.schema.routes,
59
+ });
60
+ const previousSignature = previousGenerated
61
+ ? createRouteSignature(previousGenerated)
62
+ : undefined;
63
+ const schemaChanged = nextSignature !== previousSignature;
34
64
  const generated = {
35
65
  schemaVersion: 1,
36
66
  generatedAt: new Date().toISOString(),
@@ -38,25 +68,26 @@ const generateFromSchema = () => {
38
68
  routes: parsed.schema.routes,
39
69
  };
40
70
  (0, node_fs_1.mkdirSync)(migrationsDirectoryPath, { recursive: true });
41
- const existingMigrationFiles = (0, node_fs_1.readdirSync)(migrationsDirectoryPath, {
42
- withFileTypes: true,
43
- })
44
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
45
- .map((entry) => (0, node_path_1.join)(migrationsDirectoryPath, entry.name));
46
- for (const migrationFilePath of existingMigrationFiles) {
47
- (0, node_fs_1.unlinkSync)(migrationFilePath);
48
- }
49
- const migrationId = new Date().toISOString().replace(/[.:]/g, "-");
50
71
  const migrationPath = (0, node_path_1.join)(migrationsDirectoryPath, "schema.json");
51
72
  const migration = {
52
- migrationId,
73
+ migrationId: new Date().toISOString().replace(/[.:]/g, "-"),
53
74
  sourceSchema: "fexapi/schema.fexapi",
54
75
  createdAt: generated.generatedAt,
55
76
  port: parsed.schema.port,
56
77
  routes: parsed.schema.routes,
57
78
  };
58
- (0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
59
- (0, node_fs_1.writeFileSync)(migrationPath, `${JSON.stringify(migration, null, 2)}\n`, "utf-8");
79
+ let generatedStatus = "cached";
80
+ let migrationStatus = "cached";
81
+ if (schemaChanged || !(0, node_fs_1.existsSync)(generatedPath)) {
82
+ generationSpinner.update("Writing generated API spec");
83
+ (0, node_fs_1.writeFileSync)(generatedPath, `${JSON.stringify(generated, null, 2)}\n`, "utf-8");
84
+ generatedStatus = "changed";
85
+ }
86
+ if (schemaChanged || !(0, node_fs_1.existsSync)(migrationPath)) {
87
+ generationSpinner.update("Updating migration snapshot");
88
+ (0, node_fs_1.writeFileSync)(migrationPath, `${JSON.stringify(migration, null, 2)}\n`, "utf-8");
89
+ migrationStatus = "changed";
90
+ }
60
91
  let existingConfig = {};
61
92
  if ((0, node_fs_1.existsSync)(configPath)) {
62
93
  try {
@@ -70,14 +101,51 @@ const generateFromSchema = () => {
70
101
  ...existingConfig,
71
102
  schemaPath: "fexapi/schema.fexapi",
72
103
  generatedPath: constants_1.GENERATED_SPEC_RELATIVE_PATH,
73
- lastGeneratedAt: new Date().toISOString(),
104
+ ...(schemaChanged ? { lastGeneratedAt: new Date().toISOString() } : {}),
74
105
  };
75
- (0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(updatedConfig, null, 2)}\n`, "utf-8");
76
- (0, ui_1.logSuccess)(`Generated API spec at ${generatedPath}`);
77
- (0, ui_1.logSuccess)(`Migration updated at ${migrationPath}`);
106
+ generationSpinner.update("Syncing project config");
107
+ const nextConfigText = `${JSON.stringify(updatedConfig, null, 2)}\n`;
108
+ const previousConfigText = (0, node_fs_1.existsSync)(configPath)
109
+ ? (0, node_fs_1.readFileSync)(configPath, "utf-8")
110
+ : undefined;
111
+ let configStatus = "cached";
112
+ if (previousConfigText !== nextConfigText) {
113
+ (0, node_fs_1.writeFileSync)(configPath, nextConfigText, "utf-8");
114
+ configStatus = "changed";
115
+ }
116
+ generationSpinner.succeed(`Generate complete (${schemaChanged ? "changed" : "cached"})`);
78
117
  (0, ui_1.printSpacer)();
79
- (0, ui_1.logInfo)(`Routes generated: ${parsed.schema.routes.length}`);
80
- (0, ui_1.logInfo)(`Configured server port: ${parsed.schema.port}`);
118
+ (0, ui_1.printGroupHeader)("Summary");
119
+ (0, ui_1.printSummaryCard)("Generate Summary", [
120
+ {
121
+ label: "routes",
122
+ value: String(parsed.schema.routes.length),
123
+ },
124
+ {
125
+ label: "port",
126
+ value: String(parsed.schema.port),
127
+ },
128
+ {
129
+ label: "schema source",
130
+ value: "fexapi/schema.fexapi",
131
+ },
132
+ {
133
+ label: "generated.api.json",
134
+ value: generatedStatus,
135
+ },
136
+ {
137
+ label: "migration",
138
+ value: migrationStatus,
139
+ },
140
+ {
141
+ label: "config",
142
+ value: configStatus,
143
+ },
144
+ {
145
+ label: "time",
146
+ value: (0, ui_1.formatDuration)(startedAtMs),
147
+ },
148
+ ]);
81
149
  return 0;
82
150
  };
83
151
  exports.generateFromSchema = generateFromSchema;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAkMA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CA0JjB,CAAC"}
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,CA2MjB,CAAC"}
@@ -9,7 +9,7 @@ const constants_1 = require("../constants");
9
9
  const ui_1 = require("../cli/ui");
10
10
  const detect_1 = require("../project/detect");
11
11
  const paths_1 = require("../project/paths");
12
- const DEFAULT_INIT_PORT = 3000;
12
+ const DEFAULT_INIT_PORT = 4000;
13
13
  const parseYesNo = (value, defaultValue) => {
14
14
  const normalized = value.trim().toLowerCase();
15
15
  if (!normalized) {
@@ -141,6 +141,7 @@ const SAMPLE_POST_SCHEMA = [
141
141
  " type: date",
142
142
  ].join("\n");
143
143
  const initializeProject = async ({ force, }) => {
144
+ const initStartedAtMs = (0, ui_1.nowMs)();
144
145
  const packageJsonPath = (0, paths_1.findClosestPackageJson)(process.cwd());
145
146
  if (!packageJsonPath) {
146
147
  (0, ui_1.logError)("Could not find package.json in this directory or parent directories.");
@@ -156,6 +157,8 @@ const initializeProject = async ({ force, }) => {
156
157
  const userSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "user.yaml");
157
158
  const postSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "post.yaml");
158
159
  const wizardAnswers = await askInitWizardQuestions();
160
+ (0, ui_1.printGroupHeader)("Init");
161
+ const initSpinner = (0, ui_1.startSpinner)("Scaffolding fexapi project files");
159
162
  (0, node_fs_1.mkdirSync)(fexapiDirectoryPath, { recursive: true });
160
163
  const configExists = (0, node_fs_1.existsSync)(configPath);
161
164
  const schemaExists = (0, node_fs_1.existsSync)(schemaPath);
@@ -171,13 +174,16 @@ const initializeProject = async ({ force, }) => {
171
174
  createdAt: new Date().toISOString(),
172
175
  };
173
176
  if (!configExists || force) {
177
+ initSpinner.update("Writing fexapi.config.json");
174
178
  (0, node_fs_1.writeFileSync)(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
175
179
  }
176
180
  if (!schemaExists || force) {
181
+ initSpinner.update("Writing fexapi/schema.fexapi");
177
182
  (0, node_fs_1.writeFileSync)(schemaPath, `${(0, detect_1.getSchemaTemplate)(detectedProject.primaryFramework, wizardAnswers.port)}\n`, "utf-8");
178
183
  }
179
184
  const runtimeConfigExists = (0, node_fs_1.existsSync)(runtimeConfigPath);
180
185
  if (!runtimeConfigExists || force) {
186
+ initSpinner.update("Writing fexapi.config.js");
181
187
  (0, node_fs_1.writeFileSync)(runtimeConfigPath, `${getRuntimeConfigTemplate({
182
188
  port: wizardAnswers.port,
183
189
  cors: wizardAnswers.cors,
@@ -190,6 +196,7 @@ const initializeProject = async ({ force, }) => {
190
196
  (0, node_fs_1.mkdirSync)(schemasDirectoryPath, { recursive: true });
191
197
  const userSchemaExists = (0, node_fs_1.existsSync)(userSchemaPath);
192
198
  if (!userSchemaExists || force) {
199
+ initSpinner.update("Writing sample user schema");
193
200
  (0, node_fs_1.writeFileSync)(userSchemaPath, `${SAMPLE_USER_SCHEMA}\n`, "utf-8");
194
201
  userSchemaStatus = userSchemaExists ? "overwritten" : "created";
195
202
  }
@@ -198,6 +205,7 @@ const initializeProject = async ({ force, }) => {
198
205
  }
199
206
  const postSchemaExists = (0, node_fs_1.existsSync)(postSchemaPath);
200
207
  if (!postSchemaExists || force) {
208
+ initSpinner.update("Writing sample post schema");
201
209
  (0, node_fs_1.writeFileSync)(postSchemaPath, `${SAMPLE_POST_SCHEMA}\n`, "utf-8");
202
210
  postSchemaStatus = postSchemaExists ? "overwritten" : "created";
203
211
  }
@@ -205,6 +213,7 @@ const initializeProject = async ({ force, }) => {
205
213
  postSchemaStatus = "exists";
206
214
  }
207
215
  }
216
+ initSpinner.succeed("Project scaffolding complete");
208
217
  (0, ui_1.logSuccess)(`Initialized fexapi in ${projectRoot}`);
209
218
  (0, ui_1.logInfo)(`Detected framework: ${detectedProject.primaryFramework}`);
210
219
  (0, ui_1.logInfo)(`Detected frameworks: ${detectedProject.frameworks.join(", ")}`);
@@ -212,6 +221,7 @@ const initializeProject = async ({ force, }) => {
212
221
  (0, ui_1.logInfo)(`Detected tooling: ${detectedProject.tooling.join(", ")}`);
213
222
  }
214
223
  (0, ui_1.printSpacer)();
224
+ (0, ui_1.printGroupHeader)("Files");
215
225
  if (configExists && !force) {
216
226
  (0, ui_1.logWarn)(`Exists ${configPath}`);
217
227
  }
@@ -266,6 +276,41 @@ const initializeProject = async ({ force, }) => {
266
276
  (0, ui_1.logWarn)("No known framework dependency found. Update fexapi.config.json and schema.fexapi if needed.");
267
277
  }
268
278
  (0, ui_1.printSpacer)();
279
+ (0, ui_1.printGroupHeader)("Summary");
280
+ const createdFiles = [
281
+ !configExists || force,
282
+ !schemaExists || force,
283
+ !runtimeConfigExists || force,
284
+ userSchemaStatus === "created" || userSchemaStatus === "overwritten",
285
+ postSchemaStatus === "created" || postSchemaStatus === "overwritten",
286
+ ].filter(Boolean).length;
287
+ (0, ui_1.printSummaryCard)("Init Summary", [
288
+ {
289
+ label: "framework",
290
+ value: detectedProject.primaryFramework,
291
+ },
292
+ {
293
+ label: "port",
294
+ value: String(wizardAnswers.port),
295
+ },
296
+ {
297
+ label: "cors",
298
+ value: wizardAnswers.cors ? "enabled" : "disabled",
299
+ },
300
+ {
301
+ label: "sample schemas",
302
+ value: wizardAnswers.generateSampleSchemas ? "enabled" : "disabled",
303
+ },
304
+ {
305
+ label: "files changed",
306
+ value: String(createdFiles),
307
+ },
308
+ {
309
+ label: "time",
310
+ value: (0, ui_1.formatDuration)(initStartedAtMs),
311
+ },
312
+ ]);
313
+ (0, ui_1.printSpacer)();
269
314
  (0, ui_1.logInfo)(`Next: ${(0, ui_1.formatCommand)("fexapi generate")} then ${(0, ui_1.formatCommand)("fexapi serve")}`);
270
315
  return 0;
271
316
  };
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ exports.startServer = void 0;
4
4
  const faker_1 = require("@faker-js/faker");
5
5
  const node_http_1 = require("node:http");
6
6
  const ui_1 = require("./cli/ui");
7
- const DEFAULT_HOST = "127.0.0.1";
7
+ const DEFAULT_HOST = "localhost";
8
8
  const DEFAULT_PORT = 4000;
9
9
  const sendJson = (response, statusCode, payload, options) => {
10
10
  const send = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fexapi",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",