counterfact 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import { program } from "commander";
9
9
  import createDebug from "debug";
10
10
  import open from "open";
11
11
 
12
- import { counterfact } from "../dist/server/app.js";
12
+ import { counterfact } from "../dist/app.js";
13
13
 
14
14
  const MIN_NODE_VERSION = 17;
15
15
 
@@ -151,8 +151,8 @@ async function main(source, destination) {
151
151
  includeSwaggerUi: true,
152
152
  openApiPath: source,
153
153
  port: options.port,
154
- proxyEnabled: Boolean(options.proxyUrl),
155
- proxyUrl: options.proxyUrl,
154
+ proxyPaths: new Map([["", Boolean(options.proxyUrl)]]),
155
+ proxyUrl: options.proxyUrl ?? "",
156
156
  routePrefix: options.prefix,
157
157
  startRepl: options.repl,
158
158
  startServer: options.serve,
@@ -3,16 +3,16 @@ import nodePath from "node:path";
3
3
  import { createHttpTerminator } from "http-terminator";
4
4
  import yaml from "js-yaml";
5
5
  import $RefParser from "json-schema-ref-parser";
6
- import { CodeGenerator } from "../typescript-generator/code-generator.js";
7
- import { readFile } from "../util/read-file.js";
8
- import { ContextRegistry } from "./context-registry.js";
9
- import { createKoaApp } from "./create-koa-app.js";
10
- import { Dispatcher } from "./dispatcher.js";
11
- import { koaMiddleware } from "./koa-middleware.js";
12
- import { ModuleLoader } from "./module-loader.js";
13
- import { Registry } from "./registry.js";
14
- import { startRepl } from "./repl.js";
15
- import { Transpiler } from "./transpiler.js";
6
+ import { startRepl } from "./repl/repl.js";
7
+ import { ContextRegistry } from "./server/context-registry.js";
8
+ import { createKoaApp } from "./server/create-koa-app.js";
9
+ import { Dispatcher } from "./server/dispatcher.js";
10
+ import { koaMiddleware } from "./server/koa-middleware.js";
11
+ import { ModuleLoader } from "./server/module-loader.js";
12
+ import { Registry } from "./server/registry.js";
13
+ import { Transpiler } from "./server/transpiler.js";
14
+ import { CodeGenerator } from "./typescript-generator/code-generator.js";
15
+ import { readFile } from "./util/read-file.js";
16
16
  async function loadOpenApiDocument(source) {
17
17
  try {
18
18
  const text = await readFile(source);
@@ -0,0 +1,87 @@
1
+ import repl from "node:repl";
2
+ function printToStdout(line) {
3
+ process.stdout.write(`${line}\n`);
4
+ }
5
+ export function startRepl(contextRegistry, config, print = printToStdout) {
6
+ // eslint-disable-next-line max-statements
7
+ function printProxyStatus() {
8
+ if (config.proxyUrl === "") {
9
+ print("The proxy URL is not set.");
10
+ print('To set it, type ".proxy url <url>');
11
+ return;
12
+ }
13
+ print("Proxy Configuration:");
14
+ print("");
15
+ print(`The proxy URL is ${config.proxyUrl}`);
16
+ print("");
17
+ print("Paths prefixed with [+] will be proxied.");
18
+ print("Paths prefixed with [-] will not be proxied.");
19
+ print("");
20
+ // eslint-disable-next-line array-func/prefer-array-from
21
+ const entries = [...config.proxyPaths.entries()].sort(([path1], [path2]) => path1 < path2 ? -1 : 1);
22
+ for (const [path, state] of entries) {
23
+ print(`${state ? "[+]" : "[-]"} ${path}/`);
24
+ }
25
+ }
26
+ function setProxyUrl(url) {
27
+ if (url === undefined) {
28
+ print("usage: .proxy url <url>");
29
+ return;
30
+ }
31
+ config.proxyUrl = url;
32
+ print(`proxy URL is set to ${url}`);
33
+ }
34
+ function turnProxyOnOrOff(text) {
35
+ const [command, endpoint] = text.split(" ");
36
+ const printEndpoint = endpoint === undefined || endpoint === "" ? "/" : endpoint;
37
+ config.proxyPaths.set((endpoint ?? "").replace(/\/$/u, ""), command === "on");
38
+ if (command === "on") {
39
+ print(`Requests to ${printEndpoint} will be proxied to ${config.proxyUrl || "<proxy URL>"}${printEndpoint}`);
40
+ }
41
+ if (command === "off") {
42
+ print(`Requests to ${printEndpoint} will be handled by local code`);
43
+ }
44
+ }
45
+ const replServer = repl.start({ prompt: "🤖> " });
46
+ replServer.defineCommand("counterfact", {
47
+ action() {
48
+ print("This is a read-eval-print loop (REPL), the same as the one you get when you run node with no arguments.");
49
+ print("Except that it's connected to the running server, which you can access with the following globals:");
50
+ print("");
51
+ print("- loadContext('/some/path'): to access the context object for a given path");
52
+ print("- context: the root context ( same as loadContext('/') )");
53
+ print("");
54
+ print("For more information, see https://counterfact.dev/docs/usage.html");
55
+ print("");
56
+ this.clearBufferedCommand();
57
+ this.displayPrompt();
58
+ },
59
+ help: "Get help with Counterfact",
60
+ });
61
+ replServer.defineCommand("proxy", {
62
+ action(text) {
63
+ if (text === "help" || text === "") {
64
+ print(".proxy [on|off] - turn the proxy on/off at the root level");
65
+ print(".proxy [on|off] <path-prefix> - turn the proxy on for a path");
66
+ print(".proxy status - show the proxy status");
67
+ print(".proxy help - show this message");
68
+ }
69
+ else if (text.startsWith("url")) {
70
+ setProxyUrl(text.split(" ")[1]);
71
+ }
72
+ else if (text === "status") {
73
+ printProxyStatus();
74
+ }
75
+ else {
76
+ turnProxyOnOrOff(text);
77
+ }
78
+ this.clearBufferedCommand();
79
+ this.displayPrompt();
80
+ },
81
+ help: 'proxy configuration (".proxy help" for details)',
82
+ });
83
+ replServer.context.loadContext = (path) => contextRegistry.find(path);
84
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
85
+ replServer.context.context = replServer.context.loadContext("/");
86
+ return replServer;
87
+ }
@@ -17,7 +17,9 @@ export function convertFileExtensionsToCjs(code) {
17
17
  typeof node.arguments[0].value === "string" &&
18
18
  node.arguments[0].value.startsWith(".")) {
19
19
  // Change the module string from "foo.js" to "foo.cjs"
20
- node.arguments[0].value = node.arguments[0].value.replace(/\.js$/u, ".cjs");
20
+ node.arguments[0].value = node.arguments[0].value.replace(
21
+ // eslint-disable-next-line prefer-named-capture-group, regexp/no-unused-capturing-group, regexp/prefer-named-capture-group
22
+ /(\.js|\.ts)?$/u, ".cjs");
21
23
  }
22
24
  // Continue traversing the AST
23
25
  this.traverse(path);
@@ -0,0 +1,10 @@
1
+ export function isProxyEnabledForPath(path, config) {
2
+ if (config.proxyPaths.has(path)) {
3
+ return config.proxyPaths.get(path) ?? false;
4
+ }
5
+ if (path === "") {
6
+ return false;
7
+ }
8
+ const parentPath = path.slice(0, Math.max(0, path.lastIndexOf("/")));
9
+ return isProxyEnabledForPath(parentPath, config);
10
+ }
@@ -1,5 +1,6 @@
1
1
  import createDebug from "debug";
2
2
  import koaProxy from "koa-proxy";
3
+ import { isProxyEnabledForPath } from "./is-proxy-enabled-for-path.js";
3
4
  const debug = createDebug("counterfact:server:create-koa-app");
4
5
  const HTTP_STATUS_CODE_OK = 200;
5
6
  function addCors(ctx, headers) {
@@ -26,7 +27,7 @@ function getAuthObject(ctx) {
26
27
  export function koaMiddleware(dispatcher, config, proxy = koaProxy) {
27
28
  // eslint-disable-next-line max-statements
28
29
  return async function middleware(ctx, next) {
29
- const { proxyEnabled, proxyUrl, routePrefix } = config;
30
+ const { proxyUrl, routePrefix } = config;
30
31
  debug("middleware running for path: %s", ctx.request.path);
31
32
  debug("routePrefix: %s", routePrefix);
32
33
  if (!ctx.request.path.startsWith(routePrefix)) {
@@ -39,7 +40,7 @@ export function koaMiddleware(dispatcher, config, proxy = koaProxy) {
39
40
  const path = ctx.request.path.slice(routePrefix.length);
40
41
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
41
42
  const method = ctx.request.method;
42
- if (proxyEnabled && proxyUrl) {
43
+ if (isProxyEnabledForPath(path, config) && proxyUrl) {
43
44
  /* @ts-expect-error the body comes from koa-bodyparser, not sure how to fix this */
44
45
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
45
46
  return proxy({ host: proxyUrl })(ctx, next);
@@ -46,7 +46,11 @@ export function createResponseBuilder(operation) {
46
46
  return this.match("text/html", body);
47
47
  },
48
48
  json(body) {
49
- return this.match("application/json", body);
49
+ return this.match("application/json", body)
50
+ .match("text/json", body)
51
+ .match("text/x-json", body)
52
+ .match("application/xml", body)
53
+ .match("text/xml", body);
50
54
  },
51
55
  match(contentType, body) {
52
56
  return {
@@ -99,7 +99,7 @@ type GenericResponseBuilderInner<
99
99
  ? never
100
100
  : HeaderFunction<Response>;
101
101
  html: MaybeShortcut<"text/html", Response>;
102
- json: MaybeShortcut<"application/json", Response>;
102
+ json: MaybeShortcut<"application/json" | "text/json" | "text/x-json" | "application/xml" | "text/xml", Response>;
103
103
  match: [keyof Response["content"]] extends [never]
104
104
  ? never
105
105
  : MatchFunction<Response>;
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  /**
2
3
  * Creates a promise that resolves when a specified event is fired on the given EventTarget.
3
4
  * @param {EventTarget | EventEmitter} target - The target to listen for the event on.
@@ -13,11 +14,11 @@ export async function waitForEvent(target, eventName) {
13
14
  }
14
15
  resolve(event);
15
16
  };
16
- if (target instanceof EventTarget) {
17
- target.addEventListener(eventName, handler);
17
+ if (target instanceof EventEmitter) {
18
+ target.once(eventName, handler);
18
19
  }
19
20
  else {
20
- target.once(eventName, handler);
21
+ target.addEventListener(eventName, handler);
21
22
  }
22
23
  });
23
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "a library for building a fake REST API for testing",
5
5
  "type": "module",
6
6
  "main": "./src/server/counterfact.js",
@@ -44,11 +44,11 @@
44
44
  "postinstall": "patch-package"
45
45
  },
46
46
  "devDependencies": {
47
- "@changesets/cli": "2.27.1",
47
+ "@changesets/cli": "2.27.3",
48
48
  "@stryker-mutator/core": "8.2.6",
49
49
  "@stryker-mutator/jest-runner": "8.2.6",
50
50
  "@stryker-mutator/typescript-checker": "8.2.6",
51
- "@swc/core": "1.5.3",
51
+ "@swc/core": "1.5.7",
52
52
  "@swc/jest": "0.2.36",
53
53
  "@testing-library/dom": "10.1.0",
54
54
  "@types/jest": "29.5.12",
@@ -63,17 +63,17 @@
63
63
  "eslint-formatter-github-annotations": "0.1.0",
64
64
  "eslint-import-resolver-typescript": "3.6.1",
65
65
  "eslint-plugin-etc": "2.0.3",
66
- "eslint-plugin-file-progress": "1.3.0",
66
+ "eslint-plugin-file-progress": "1.4.0",
67
67
  "eslint-plugin-import": "2.29.1",
68
68
  "eslint-plugin-jest": "28.5.0",
69
69
  "eslint-plugin-jest-dom": "5.4.0",
70
70
  "eslint-plugin-no-explicit-type-exports": "0.12.1",
71
- "eslint-plugin-unused-imports": "3.2.0",
71
+ "eslint-plugin-unused-imports": "4.0.0",
72
72
  "husky": "9.0.11",
73
73
  "jest": "29.7.0",
74
74
  "node-mocks-http": "1.14.1",
75
- "nodemon": "3.1.0",
76
- "rimraf": "5.0.5",
75
+ "nodemon": "3.1.1",
76
+ "rimraf": "5.0.7",
77
77
  "stryker-cli": "1.0.2",
78
78
  "supertest": "7.0.0",
79
79
  "using-temporary-files": "2.2.1"
@@ -83,7 +83,7 @@
83
83
  "@types/json-schema": "7.0.15",
84
84
  "ast-types": "0.14.2",
85
85
  "chokidar": "3.6.0",
86
- "commander": "12.0.0",
86
+ "commander": "12.1.0",
87
87
  "debug": "4.3.4",
88
88
  "fetch": "1.1.0",
89
89
  "fs-extra": "11.2.0",
@@ -102,7 +102,7 @@
102
102
  "patch-package": "8.0.0",
103
103
  "precinct": "12.1.1",
104
104
  "prettier": "3.2.5",
105
- "recast": "0.23.6",
105
+ "recast": "0.23.7",
106
106
  "typescript": "5.4.5"
107
107
  }
108
108
  }
@@ -1,34 +0,0 @@
1
- import repl from "node:repl";
2
- export function startRepl(contextRegistry, config) {
3
- const replServer = repl.start("🤖> ");
4
- replServer.defineCommand("counterfact", {
5
- action() {
6
- process.stdout.write("This is a read-eval-print loop (REPL), the same as the one you get when you run node with no arguments.\n");
7
- process.stdout.write("Except that it's connected to the running server, which you can access with the following globals:\n\n");
8
- process.stdout.write("- loadContext('/some/path'): to access the context object for a given path\n");
9
- process.stdout.write("- context: the root context ( same as loadContext('/') )\n");
10
- process.stdout.write("\nFor more information, see https://counterfact.dev/docs/usage.html\n\n");
11
- this.clearBufferedCommand();
12
- this.displayPrompt();
13
- },
14
- help: "Get help with Counterfact",
15
- });
16
- replServer.defineCommand("proxy", {
17
- action(state) {
18
- if (state === "on") {
19
- config.proxyEnabled = true;
20
- }
21
- if (state === "off") {
22
- config.proxyEnabled = false;
23
- }
24
- process.stdout.write(`Proxy is ${config.proxyEnabled ? "on" : "off"}: ${config.proxyUrl}\n`);
25
- this.clearBufferedCommand();
26
- this.displayPrompt();
27
- },
28
- help: "proxy [on|off] - turn the proxy on or off; proxy - print proxy info",
29
- });
30
- replServer.context.loadContext = (path) => contextRegistry.find(path);
31
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
32
- replServer.context.context = replServer.context.loadContext("/");
33
- return replServer;
34
- }