@testsmith/api-spector 0.3.0 → 0.3.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/out/main/mock.js CHANGED
@@ -1,74 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- const promises = require("fs/promises");
4
- const path = require("path");
5
3
  const mockServer = require("./chunks/mock-server-DSLR2ulH.js");
4
+ const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
6
5
  require("http");
7
6
  require("crypto");
8
7
  require("vm");
9
8
  require("dayjs");
10
9
  require("@xmldom/xmldom");
11
- const C = {
12
- reset: "\x1B[0m",
13
- bold: "\x1B[1m",
14
- green: "\x1B[32m",
15
- red: "\x1B[31m",
16
- yellow: "\x1B[33m",
17
- cyan: "\x1B[36m",
18
- gray: "\x1B[90m",
19
- white: "\x1B[97m"
20
- };
21
- function color(str, ...codes) {
22
- return process.stdout.isTTY ? codes.join("") + str + C.reset : str;
23
- }
24
- function parseArgs(argv) {
25
- const args = {};
26
- for (let i = 0; i < argv.length; i++) {
27
- const arg = argv[i];
28
- if (!arg.startsWith("--")) continue;
29
- const key = arg.slice(2);
30
- const next = argv[i + 1];
31
- if (!next || next.startsWith("--")) {
32
- args[key] = true;
33
- } else {
34
- if (key === "name") {
35
- const prev = args[key];
36
- args[key] = Array.isArray(prev) ? [...prev, next] : [next];
37
- } else {
38
- args[key] = next;
39
- }
40
- i++;
41
- }
42
- }
43
- return args;
44
- }
45
- async function resolveWorkspacePath(wsPath) {
46
- const s = await promises.stat(wsPath);
47
- if (!s.isDirectory()) return wsPath;
48
- const entries = await promises.readdir(wsPath);
49
- const spector = entries.find((e) => e.endsWith(".spector"));
50
- if (!spector) throw new Error(`No .spector workspace file found in directory: ${wsPath}`);
51
- return path.join(wsPath, spector);
52
- }
53
- async function loadWorkspace(wsPath) {
54
- const resolved = await resolveWorkspacePath(wsPath);
55
- const raw = await promises.readFile(resolved, "utf8");
56
- return { workspace: JSON.parse(raw), dir: path.dirname(path.resolve(resolved)) };
57
- }
58
- async function loadMocks(workspace, dir) {
59
- const mocks = [];
60
- for (const relPath of workspace.mocks ?? []) {
61
- try {
62
- const raw = await promises.readFile(path.join(dir, relPath), "utf8");
63
- mocks.push(JSON.parse(raw));
64
- } catch {
65
- console.warn(color(` [warn] Could not load mock: ${relPath}`, C.yellow));
66
- }
67
- }
68
- return mocks;
69
- }
10
+ require("fs/promises");
11
+ require("path");
70
12
  async function main() {
71
- const args = parseArgs(process.argv.slice(2));
13
+ const args = cliCommon.parseArgs(process.argv.slice(2), ["name"]);
72
14
  if (args.help) {
73
15
  console.log(
74
16
  "\nUsage:\n api-spector mock --workspace <path> [--name <server-name>] [--log]\n\nOptions:\n --workspace <path> Path to workspace.json (required)\n --name <name> Start only the named server (repeat for multiple)\n --log Print each incoming request (matched and unmatched)\n --help Show this message\n"
@@ -77,50 +19,54 @@ async function main() {
77
19
  }
78
20
  const wsPath = args.workspace;
79
21
  if (!wsPath) {
80
- console.error(color("Error: --workspace is required", C.red));
22
+ console.error(cliCommon.color("Error: --workspace is required", cliCommon.C.red));
81
23
  process.exit(1);
82
24
  }
83
25
  let workspace, wsDir;
84
26
  try {
85
27
  ;
86
- ({ workspace, dir: wsDir } = await loadWorkspace(wsPath));
28
+ ({ workspace, dir: wsDir } = await cliCommon.loadWorkspace(wsPath));
87
29
  } catch {
88
- console.error(color(`Error: could not read workspace: ${wsPath}`, C.red));
30
+ console.error(cliCommon.color(`Error: could not read workspace: ${wsPath}`, cliCommon.C.red));
89
31
  process.exit(1);
90
32
  }
91
- const allMocks = await loadMocks(workspace, wsDir);
33
+ const allMocks = await cliCommon.loadMocks(
34
+ workspace,
35
+ wsDir,
36
+ (relPath) => console.warn(cliCommon.color(` [warn] Could not load mock: ${relPath}`, cliCommon.C.yellow))
37
+ );
92
38
  if (allMocks.length === 0) {
93
- console.error(color(" No mock servers defined in this workspace.", C.yellow));
39
+ console.error(cliCommon.color(" No mock servers defined in this workspace.", cliCommon.C.yellow));
94
40
  process.exit(0);
95
41
  }
96
42
  const nameFilter = args.name ? (Array.isArray(args.name) ? args.name : [args.name]).map((n) => n.toLowerCase()) : null;
97
43
  const toStart = nameFilter ? allMocks.filter((m) => nameFilter.includes(m.name.toLowerCase())) : allMocks;
98
44
  if (toStart.length === 0) {
99
- console.error(color(` No mock servers matched the given --name filter.`, C.yellow));
100
- console.log(color(` Available: ${allMocks.map((m) => `"${m.name}"`).join(", ")}`, C.gray));
45
+ console.error(cliCommon.color(` No mock servers matched the given --name filter.`, cliCommon.C.yellow));
46
+ console.log(cliCommon.color(` Available: ${allMocks.map((m) => `"${m.name}"`).join(", ")}`, cliCommon.C.gray));
101
47
  process.exit(1);
102
48
  }
103
49
  console.log("");
104
- console.log(color(" Mock Servers", C.bold, C.white));
105
- console.log(color(` Workspace: ${wsPath}`, C.gray));
50
+ console.log(cliCommon.color(" Mock Servers", cliCommon.C.bold, cliCommon.C.white));
51
+ console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
106
52
  console.log("");
107
53
  let started = 0;
108
54
  for (const mock of toStart) {
109
55
  try {
110
56
  await mockServer.startMock(mock);
111
57
  console.log(
112
- color(" ✓", C.green, C.bold) + ` ${color(mock.name, C.white)} ` + color(`http://127.0.0.1:${mock.port}`, C.cyan) + color(` (${mock.routes.length} route${mock.routes.length !== 1 ? "s" : ""})`, C.gray)
58
+ cliCommon.color(" ✓", cliCommon.C.green, cliCommon.C.bold) + ` ${cliCommon.color(mock.name, cliCommon.C.white)} ` + cliCommon.color(`http://127.0.0.1:${mock.port}`, cliCommon.C.cyan) + cliCommon.color(` (${mock.routes.length} route${mock.routes.length !== 1 ? "s" : ""})`, cliCommon.C.gray)
113
59
  );
114
60
  for (const route of mock.routes) {
115
- const delay = route.delay ? color(` ${route.delay}ms`, C.gray) : "";
61
+ const delay = route.delay ? cliCommon.color(` ${route.delay}ms`, cliCommon.C.gray) : "";
116
62
  console.log(
117
- color(` ${route.method.padEnd(7)} ${route.path}`, C.gray) + color(` → ${route.statusCode}`, route.statusCode < 400 ? C.green : C.red) + delay
63
+ cliCommon.color(` ${route.method.padEnd(7)} ${route.path}`, cliCommon.C.gray) + cliCommon.color(` → ${route.statusCode}`, route.statusCode < 400 ? cliCommon.C.green : cliCommon.C.red) + delay
118
64
  );
119
65
  }
120
66
  started++;
121
67
  } catch (e) {
122
68
  console.error(
123
- color(" ✗", C.red, C.bold) + ` ${mock.name} ` + color(e instanceof Error ? e.message : String(e), C.red)
69
+ cliCommon.color(" ✗", cliCommon.C.red, cliCommon.C.bold) + ` ${mock.name} ` + cliCommon.color(e instanceof Error ? e.message : String(e), cliCommon.C.red)
124
70
  );
125
71
  }
126
72
  }
@@ -138,27 +84,27 @@ async function main() {
138
84
  }
139
85
  mockServer.setHitCallback((hit) => {
140
86
  const ts = new Date(hit.timestamp).toISOString().slice(11, 23);
141
- const server = color(serverNames[hit.serverId] ?? hit.serverId, C.white);
87
+ const server = cliCommon.color(serverNames[hit.serverId] ?? hit.serverId, cliCommon.C.white);
142
88
  const method = hit.method.padEnd(7);
143
89
  const matched = hit.matchedRouteId !== null;
144
- const status = color(String(hit.status), hit.status < 400 ? C.green : C.red);
145
- const dur = color(`${hit.durationMs}ms`, C.gray);
90
+ const status = cliCommon.color(String(hit.status), hit.status < 400 ? cliCommon.C.green : cliCommon.C.red);
91
+ const dur = cliCommon.color(`${hit.durationMs}ms`, cliCommon.C.gray);
146
92
  if (matched) {
147
- const route = color(routePaths[hit.matchedRouteId] ?? hit.matchedRouteId, C.gray);
93
+ const route = cliCommon.color(routePaths[hit.matchedRouteId] ?? hit.matchedRouteId, cliCommon.C.gray);
148
94
  console.log(
149
- color(` ${ts}`, C.gray) + ` ${server} ` + color(method, C.cyan) + color(hit.path, C.white) + ` ${status} ${dur}` + color(` → ${route}`, C.gray)
95
+ cliCommon.color(` ${ts}`, cliCommon.C.gray) + ` ${server} ` + cliCommon.color(method, cliCommon.C.cyan) + cliCommon.color(hit.path, cliCommon.C.white) + ` ${status} ${dur}` + cliCommon.color(` → ${route}`, cliCommon.C.gray)
150
96
  );
151
97
  } else {
152
98
  console.log(
153
- color(` ${ts}`, C.gray) + ` ${server} ` + color(method, C.yellow) + color(hit.path, C.yellow) + ` ${status} ${dur}` + color(" (no match)", C.yellow)
99
+ cliCommon.color(` ${ts}`, cliCommon.C.gray) + ` ${server} ` + cliCommon.color(method, cliCommon.C.yellow) + cliCommon.color(hit.path, cliCommon.C.yellow) + ` ${status} ${dur}` + cliCommon.color(" (no match)", cliCommon.C.yellow)
154
100
  );
155
101
  }
156
102
  });
157
103
  }
158
104
  console.log("");
159
- console.log(color(" Press Ctrl+C to stop all servers.\n", C.gray));
105
+ console.log(cliCommon.color(" Press Ctrl+C to stop all servers.\n", cliCommon.C.gray));
160
106
  async function shutdown() {
161
- console.log(color("\n Stopping mock servers…", C.gray));
107
+ console.log(cliCommon.color("\n Stopping mock servers…", cliCommon.C.gray));
162
108
  await mockServer.stopAll();
163
109
  process.exit(0);
164
110
  }
@@ -168,6 +114,6 @@ async function main() {
168
114
  }, 1 << 30);
169
115
  }
170
116
  main().catch((err) => {
171
- console.error(color(`Fatal: ${err.message}`, C.red));
117
+ console.error(cliCommon.color(`Fatal: ${err.message}`, cliCommon.C.red));
172
118
  process.exit(2);
173
119
  });
@@ -3,51 +3,17 @@
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const recorder = require("./chunks/recorder-DFxJgn9c.js");
6
+ const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
6
7
  require("http");
7
8
  require("crypto");
8
9
  require("undici");
9
- const C = {
10
- reset: "\x1B[0m",
11
- bold: "\x1B[1m",
12
- dim: "\x1B[2m",
13
- green: "\x1B[32m",
14
- red: "\x1B[31m",
15
- yellow: "\x1B[33m",
16
- cyan: "\x1B[36m",
17
- gray: "\x1B[90m",
18
- white: "\x1B[97m"
19
- };
20
- function color(str, ...codes) {
21
- return process.stdout.isTTY ? codes.join("") + str + C.reset : str;
22
- }
23
10
  function methodBadge(method) {
24
11
  const m = method.toUpperCase();
25
- const c = m === "GET" ? C.green : m === "POST" ? C.cyan : m === "PUT" || m === "PATCH" ? C.yellow : m === "DELETE" ? C.red : C.white;
26
- return color(m.padEnd(7), c, C.bold);
27
- }
28
- function parseArgs(argv) {
29
- const args = {};
30
- for (let i = 0; i < argv.length; i++) {
31
- const arg = argv[i];
32
- if (!arg.startsWith("--")) continue;
33
- const key = arg.slice(2);
34
- const next = argv[i + 1];
35
- if (!next || next.startsWith("--")) {
36
- args[key] = true;
37
- } else {
38
- if (key === "mask" || key === "ignore") {
39
- const existing = args[key];
40
- args[key] = Array.isArray(existing) ? [...existing, next] : [next];
41
- } else {
42
- args[key] = next;
43
- }
44
- i++;
45
- }
46
- }
47
- return args;
12
+ const c = m === "GET" ? cliCommon.C.green : m === "POST" ? cliCommon.C.cyan : m === "PUT" || m === "PATCH" ? cliCommon.C.yellow : m === "DELETE" ? cliCommon.C.red : cliCommon.C.white;
13
+ return cliCommon.color(m.padEnd(7), c, cliCommon.C.bold);
48
14
  }
49
15
  async function main() {
50
- const args = parseArgs(process.argv.slice(2));
16
+ const args = cliCommon.parseArgs(process.argv.slice(2), ["mask", "ignore"]);
51
17
  if (args.help) {
52
18
  console.log(
53
19
  "\nUsage:\n api-spector record --upstream <url> [options]\n\nOptions:\n --upstream <url> Real API base URL (required)\n --port <n> Local port (default: 4001)\n --output <path> Output directory (default: ./recordings)\n --mask <header> Mask header value with *** (repeatable)\n --ignore <header> Omit header from recordings (repeatable)\n"
@@ -56,7 +22,7 @@ async function main() {
56
22
  }
57
23
  const upstream = args.upstream?.replace(/\/$/, "");
58
24
  if (!upstream) {
59
- console.error(color("Error: --upstream <url> is required", C.red));
25
+ console.error(cliCommon.color("Error: --upstream <url> is required", cliCommon.C.red));
60
26
  process.exit(1);
61
27
  }
62
28
  const port = parseInt(args.port ?? "4001", 10);
@@ -66,24 +32,24 @@ async function main() {
66
32
  recorder.setRecorderHitCallback((entry) => {
67
33
  const { method, path: path2 } = entry.request;
68
34
  const { status, binary } = entry.response;
69
- const sc = status < 300 ? C.green : status < 400 ? C.cyan : C.red;
70
- const st = status > 0 ? color(String(status), sc, C.bold) : color("ERR", C.red, C.bold);
71
- const bin = binary ? color(" [binary]", C.yellow) : "";
72
- console.log(` ${methodBadge(method)} ${color(path2, C.white)} ${st} ${color(`${entry.durationMs}ms`, C.gray)}${bin}`);
35
+ const sc = status < 300 ? cliCommon.C.green : status < 400 ? cliCommon.C.cyan : cliCommon.C.red;
36
+ const st = status > 0 ? cliCommon.color(String(status), sc, cliCommon.C.bold) : cliCommon.color("ERR", cliCommon.C.red, cliCommon.C.bold);
37
+ const bin = binary ? cliCommon.color(" [binary]", cliCommon.C.yellow) : "";
38
+ console.log(` ${methodBadge(method)} ${cliCommon.color(path2, cliCommon.C.white)} ${st} ${cliCommon.color(`${entry.durationMs}ms`, cliCommon.C.gray)}${bin}`);
73
39
  });
74
40
  await recorder.startRecorder({ upstream, port, maskHeaders: extraMask, ignoreHeaders: extraIgnore });
75
41
  console.log("");
76
- console.log(color(" API Spector — Record Proxy", C.bold, C.white));
77
- console.log(color(` Upstream: ${upstream}`, C.gray));
78
- console.log(color(` Listening: http://localhost:${port}`, C.cyan));
79
- console.log(color(` Output: ${outputDir}`, C.gray));
80
- console.log(color(" Press Ctrl+C to stop and save recordings.\n", C.dim));
42
+ console.log(cliCommon.color(" API Spector — Record Proxy", cliCommon.C.bold, cliCommon.C.white));
43
+ console.log(cliCommon.color(` Upstream: ${upstream}`, cliCommon.C.gray));
44
+ console.log(cliCommon.color(` Listening: http://localhost:${port}`, cliCommon.C.cyan));
45
+ console.log(cliCommon.color(` Output: ${outputDir}`, cliCommon.C.gray));
46
+ console.log(cliCommon.color(" Press Ctrl+C to stop and save recordings.\n", cliCommon.C.dim));
81
47
  async function shutdown() {
82
48
  console.log("");
83
49
  const session = recorder.stopRecorder();
84
50
  recorder.setRecorderHitCallback(null);
85
51
  if (session.entries.length === 0) {
86
- console.log(color(" No requests recorded.", C.yellow));
52
+ console.log(cliCommon.color(" No requests recorded.", cliCommon.C.yellow));
87
53
  process.exit(0);
88
54
  }
89
55
  await promises.mkdir(outputDir, { recursive: true });
@@ -94,9 +60,9 @@ async function main() {
94
60
  const mockServer = recorder.entriesToMockServer(session.entries, upstream, mockName, port);
95
61
  const mockPath = path.join(outputDir, `session-${slug}.mock.json`);
96
62
  await promises.writeFile(mockPath, JSON.stringify(mockServer, null, 2), "utf8");
97
- console.log(color(` Recorded ${session.entries.length} request${session.entries.length !== 1 ? "s" : ""}.`, C.green, C.bold));
98
- console.log(color(` Session: ${sessionPath}`, C.gray));
99
- console.log(color(` Mock stubs: ${mockPath}`, C.gray));
63
+ console.log(cliCommon.color(` Recorded ${session.entries.length} request${session.entries.length !== 1 ? "s" : ""}.`, cliCommon.C.green, cliCommon.C.bold));
64
+ console.log(cliCommon.color(` Session: ${sessionPath}`, cliCommon.C.gray));
65
+ console.log(cliCommon.color(` Mock stubs: ${mockPath}`, cliCommon.C.gray));
100
66
  console.log("");
101
67
  process.exit(0);
102
68
  }
@@ -104,6 +70,6 @@ async function main() {
104
70
  process.on("SIGTERM", shutdown);
105
71
  }
106
72
  main().catch((err) => {
107
- console.error(color(`Fatal: ${err instanceof Error ? err.message : String(err)}`, "\x1B[31m"));
73
+ console.error(cliCommon.color(`Fatal: ${err instanceof Error ? err.message : String(err)}`, "\x1B[31m"));
108
74
  process.exit(2);
109
75
  });