@schmock/cli 1.13.0 → 2.0.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.
Files changed (3) hide show
  1. package/dist/bin.js +177 -4
  2. package/dist/index.js +177 -4
  3. package/package.json +3 -3
package/dist/bin.js CHANGED
@@ -17735,6 +17735,7 @@ import { parseArgs } from "node:util";
17735
17735
  import { createServer } from "node:http";
17736
17736
 
17737
17737
  // ../core/src/constants.ts
17738
+ var ROUTE_NOT_FOUND_CODE = "ROUTE_NOT_FOUND";
17738
17739
  var HTTP_METHODS = [
17739
17740
  "GET",
17740
17741
  "POST",
@@ -17761,6 +17762,10 @@ function toRouteKey(method, path) {
17761
17762
  const key = `${method} ${path}`;
17762
17763
  return key;
17763
17764
  }
17765
+ function isRouteNotFound(response) {
17766
+ const { status, body } = response;
17767
+ return status === 404 && body !== null && typeof body === "object" && "code" in body && body.code === ROUTE_NOT_FOUND_CODE;
17768
+ }
17764
17769
  function isStatusTuple(value) {
17765
17770
  return Array.isArray(value) && (value.length === 2 || value.length === 3) && typeof value[0] === "number" && value[0] >= 100 && value[0] <= 599;
17766
17771
  }
@@ -17894,6 +17899,165 @@ function writeSchmockResponse(res, response, extraHeaders) {
17894
17899
  res.end(responseBody);
17895
17900
  }
17896
17901
 
17902
+ // ../core/src/interceptor.ts
17903
+ function extractPathname(url) {
17904
+ const queryStart = url.indexOf("?");
17905
+ const urlWithoutQuery = queryStart === -1 ? url : url.slice(0, queryStart);
17906
+ if (urlWithoutQuery.includes("://")) {
17907
+ try {
17908
+ return new URL(urlWithoutQuery).pathname;
17909
+ } catch {}
17910
+ }
17911
+ if (!urlWithoutQuery.startsWith("/")) {
17912
+ return `/${urlWithoutQuery}`;
17913
+ }
17914
+ return urlWithoutQuery;
17915
+ }
17916
+ function extractQuery(url) {
17917
+ const queryStart = url.indexOf("?");
17918
+ if (queryStart === -1)
17919
+ return {};
17920
+ const params = new URLSearchParams(url.slice(queryStart + 1));
17921
+ const result = {};
17922
+ params.forEach((value, key) => {
17923
+ result[key] = value;
17924
+ });
17925
+ return result;
17926
+ }
17927
+ function extractHeaders(input, init) {
17928
+ const headers = {};
17929
+ const raw = input instanceof Request ? input.headers : init?.headers;
17930
+ if (!raw)
17931
+ return headers;
17932
+ if (raw instanceof Headers) {
17933
+ raw.forEach((value, key) => {
17934
+ headers[key] = value;
17935
+ });
17936
+ } else if (Array.isArray(raw)) {
17937
+ for (const [key, value] of raw) {
17938
+ headers[key] = value;
17939
+ }
17940
+ } else {
17941
+ Object.assign(headers, raw);
17942
+ }
17943
+ return headers;
17944
+ }
17945
+ async function extractBody(input, init) {
17946
+ const rawBody = input instanceof Request ? input.body : init?.body;
17947
+ if (rawBody === null || rawBody === undefined)
17948
+ return;
17949
+ const bodyInit = init?.body;
17950
+ if (typeof bodyInit === "string") {
17951
+ try {
17952
+ return JSON.parse(bodyInit);
17953
+ } catch {
17954
+ return bodyInit;
17955
+ }
17956
+ }
17957
+ if (input instanceof Request && input.body) {
17958
+ try {
17959
+ return await input.clone().json();
17960
+ } catch {
17961
+ try {
17962
+ return await input.clone().text();
17963
+ } catch {
17964
+ return;
17965
+ }
17966
+ }
17967
+ }
17968
+ return;
17969
+ }
17970
+ function createFetchInterceptor(handle, options = {}) {
17971
+ const {
17972
+ baseUrl,
17973
+ passthrough = true,
17974
+ beforeRequest,
17975
+ beforeResponse,
17976
+ errorFormatter
17977
+ } = options;
17978
+ const originalFetch = globalThis.fetch;
17979
+ let active = true;
17980
+ globalThis.fetch = async (input, init) => {
17981
+ const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17982
+ const path = extractPathname(urlString);
17983
+ if (baseUrl && !path.startsWith(baseUrl)) {
17984
+ return originalFetch(input, init);
17985
+ }
17986
+ const method = input instanceof Request ? input.method : init?.method ?? "GET";
17987
+ const headers = extractHeaders(input, init);
17988
+ const query = extractQuery(urlString);
17989
+ const body = await extractBody(input, init);
17990
+ let adapterRequest = {
17991
+ method,
17992
+ path,
17993
+ headers,
17994
+ body,
17995
+ query
17996
+ };
17997
+ try {
17998
+ if (beforeRequest) {
17999
+ const modified = await beforeRequest(adapterRequest);
18000
+ if (modified) {
18001
+ adapterRequest = modified;
18002
+ }
18003
+ }
18004
+ const schmockResponse = await handle(toHttpMethod(adapterRequest.method), adapterRequest.path, {
18005
+ headers: adapterRequest.headers,
18006
+ body: adapterRequest.body,
18007
+ query: adapterRequest.query
18008
+ });
18009
+ if (isRouteNotFound(schmockResponse)) {
18010
+ if (passthrough) {
18011
+ return originalFetch(input, init);
18012
+ }
18013
+ return new Response(JSON.stringify({
18014
+ error: "No matching mock route found",
18015
+ code: "ROUTE_NOT_FOUND"
18016
+ }), {
18017
+ status: 404,
18018
+ headers: { "content-type": "application/json" }
18019
+ });
18020
+ }
18021
+ let response = schmockResponse;
18022
+ if (beforeResponse) {
18023
+ const modified = await beforeResponse(response, adapterRequest);
18024
+ if (modified) {
18025
+ response = modified;
18026
+ }
18027
+ }
18028
+ const responseHeaders = new Headers(response.headers);
18029
+ if (!responseHeaders.has("content-type") && response.body !== null && response.body !== undefined) {
18030
+ responseHeaders.set("content-type", "application/json");
18031
+ }
18032
+ const responseBody = response.body === null || response.body === undefined ? null : typeof response.body === "string" ? response.body : JSON.stringify(response.body);
18033
+ return new Response(responseBody, {
18034
+ status: response.status,
18035
+ headers: responseHeaders
18036
+ });
18037
+ } catch (error) {
18038
+ if (errorFormatter) {
18039
+ const formatted = errorFormatter(error instanceof Error ? error : new Error(String(error)));
18040
+ return new Response(JSON.stringify(formatted), {
18041
+ status: 500,
18042
+ headers: { "content-type": "application/json" }
18043
+ });
18044
+ }
18045
+ throw error;
18046
+ }
18047
+ };
18048
+ return {
18049
+ restore() {
18050
+ if (active) {
18051
+ globalThis.fetch = originalFetch;
18052
+ active = false;
18053
+ }
18054
+ },
18055
+ get active() {
18056
+ return active;
18057
+ }
18058
+ };
18059
+ }
18060
+
17897
18061
  // ../core/src/parser.ts
17898
18062
  function parseRouteKey(routeKey) {
17899
18063
  const match = routeKey.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/);
@@ -17928,7 +18092,7 @@ async function runPluginPipeline(plugins, context, initialResponse, logger) {
17928
18092
  logger.log("pipeline", `Processing plugin: ${plugin.name}`);
17929
18093
  try {
17930
18094
  const result = await plugin.process(currentContext, response);
17931
- if (!result || !result.context) {
18095
+ if (!result?.context) {
17932
18096
  throw new Error(`Plugin ${plugin.name} didn't return valid result`);
17933
18097
  }
17934
18098
  currentContext = result.context;
@@ -18078,6 +18242,7 @@ class CallableMockInstance {
18078
18242
  callableRef;
18079
18243
  server;
18080
18244
  serverInfo;
18245
+ interceptHandle = null;
18081
18246
  listeners = new Map;
18082
18247
  constructor(globalConfig = {}) {
18083
18248
  this.globalConfig = globalConfig;
@@ -18285,6 +18450,13 @@ class CallableMockInstance {
18285
18450
  this.serverInfo = undefined;
18286
18451
  this.logger.log("server", "Server stopped");
18287
18452
  }
18453
+ intercept(options) {
18454
+ if (this.interceptHandle?.active) {
18455
+ throw new SchmockError("Already intercepting. Call restore() first.", "ALREADY_INTERCEPTING");
18456
+ }
18457
+ this.interceptHandle = createFetchInterceptor((method, path, opts) => this.handle(method, path, opts), options);
18458
+ return this.interceptHandle;
18459
+ }
18288
18460
  async handle(method, path, options) {
18289
18461
  const handleStart = performance.now();
18290
18462
  const requestId = this.globalConfig.debug ? crypto.randomUUID() : "";
@@ -18475,13 +18647,14 @@ function schmock(config) {
18475
18647
  getRoutes: instance.getRoutes.bind(instance),
18476
18648
  getState: instance.getState.bind(instance),
18477
18649
  listen: instance.listen.bind(instance),
18478
- close: instance.close.bind(instance)
18650
+ close: instance.close.bind(instance),
18651
+ intercept: (options) => instance.intercept(options)
18479
18652
  });
18480
18653
  instance.setCallableRef(callableInstance);
18481
18654
  return callableInstance;
18482
18655
  }
18483
18656
  // ../openapi/package.json
18484
- var version = "1.13.0";
18657
+ var version = "2.0.0";
18485
18658
 
18486
18659
  // ../openapi/src/utils.ts
18487
18660
  function isRecord(value) {
@@ -18499,7 +18672,7 @@ function getRouteCallbacks(route) {
18499
18672
  function fireCallbacks(callbacks, context, response) {
18500
18673
  for (const callback of callbacks) {
18501
18674
  const url = resolveCallbackUrl(callback.urlExpression, context, response);
18502
- if (!url || !url.startsWith("http"))
18675
+ if (!url?.startsWith("http"))
18503
18676
  continue;
18504
18677
  const body = Array.isArray(response) ? response[1] : response;
18505
18678
  fetch(url, {
package/dist/index.js CHANGED
@@ -17734,6 +17734,7 @@ import { parseArgs } from "node:util";
17734
17734
  import { createServer } from "node:http";
17735
17735
 
17736
17736
  // ../core/src/constants.ts
17737
+ var ROUTE_NOT_FOUND_CODE = "ROUTE_NOT_FOUND";
17737
17738
  var HTTP_METHODS = [
17738
17739
  "GET",
17739
17740
  "POST",
@@ -17760,6 +17761,10 @@ function toRouteKey(method, path) {
17760
17761
  const key = `${method} ${path}`;
17761
17762
  return key;
17762
17763
  }
17764
+ function isRouteNotFound(response) {
17765
+ const { status, body } = response;
17766
+ return status === 404 && body !== null && typeof body === "object" && "code" in body && body.code === ROUTE_NOT_FOUND_CODE;
17767
+ }
17763
17768
  function isStatusTuple(value) {
17764
17769
  return Array.isArray(value) && (value.length === 2 || value.length === 3) && typeof value[0] === "number" && value[0] >= 100 && value[0] <= 599;
17765
17770
  }
@@ -17893,6 +17898,165 @@ function writeSchmockResponse(res, response, extraHeaders) {
17893
17898
  res.end(responseBody);
17894
17899
  }
17895
17900
 
17901
+ // ../core/src/interceptor.ts
17902
+ function extractPathname(url) {
17903
+ const queryStart = url.indexOf("?");
17904
+ const urlWithoutQuery = queryStart === -1 ? url : url.slice(0, queryStart);
17905
+ if (urlWithoutQuery.includes("://")) {
17906
+ try {
17907
+ return new URL(urlWithoutQuery).pathname;
17908
+ } catch {}
17909
+ }
17910
+ if (!urlWithoutQuery.startsWith("/")) {
17911
+ return `/${urlWithoutQuery}`;
17912
+ }
17913
+ return urlWithoutQuery;
17914
+ }
17915
+ function extractQuery(url) {
17916
+ const queryStart = url.indexOf("?");
17917
+ if (queryStart === -1)
17918
+ return {};
17919
+ const params = new URLSearchParams(url.slice(queryStart + 1));
17920
+ const result = {};
17921
+ params.forEach((value, key) => {
17922
+ result[key] = value;
17923
+ });
17924
+ return result;
17925
+ }
17926
+ function extractHeaders(input, init) {
17927
+ const headers = {};
17928
+ const raw = input instanceof Request ? input.headers : init?.headers;
17929
+ if (!raw)
17930
+ return headers;
17931
+ if (raw instanceof Headers) {
17932
+ raw.forEach((value, key) => {
17933
+ headers[key] = value;
17934
+ });
17935
+ } else if (Array.isArray(raw)) {
17936
+ for (const [key, value] of raw) {
17937
+ headers[key] = value;
17938
+ }
17939
+ } else {
17940
+ Object.assign(headers, raw);
17941
+ }
17942
+ return headers;
17943
+ }
17944
+ async function extractBody(input, init) {
17945
+ const rawBody = input instanceof Request ? input.body : init?.body;
17946
+ if (rawBody === null || rawBody === undefined)
17947
+ return;
17948
+ const bodyInit = init?.body;
17949
+ if (typeof bodyInit === "string") {
17950
+ try {
17951
+ return JSON.parse(bodyInit);
17952
+ } catch {
17953
+ return bodyInit;
17954
+ }
17955
+ }
17956
+ if (input instanceof Request && input.body) {
17957
+ try {
17958
+ return await input.clone().json();
17959
+ } catch {
17960
+ try {
17961
+ return await input.clone().text();
17962
+ } catch {
17963
+ return;
17964
+ }
17965
+ }
17966
+ }
17967
+ return;
17968
+ }
17969
+ function createFetchInterceptor(handle, options = {}) {
17970
+ const {
17971
+ baseUrl,
17972
+ passthrough = true,
17973
+ beforeRequest,
17974
+ beforeResponse,
17975
+ errorFormatter
17976
+ } = options;
17977
+ const originalFetch = globalThis.fetch;
17978
+ let active = true;
17979
+ globalThis.fetch = async (input, init) => {
17980
+ const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17981
+ const path = extractPathname(urlString);
17982
+ if (baseUrl && !path.startsWith(baseUrl)) {
17983
+ return originalFetch(input, init);
17984
+ }
17985
+ const method = input instanceof Request ? input.method : init?.method ?? "GET";
17986
+ const headers = extractHeaders(input, init);
17987
+ const query = extractQuery(urlString);
17988
+ const body = await extractBody(input, init);
17989
+ let adapterRequest = {
17990
+ method,
17991
+ path,
17992
+ headers,
17993
+ body,
17994
+ query
17995
+ };
17996
+ try {
17997
+ if (beforeRequest) {
17998
+ const modified = await beforeRequest(adapterRequest);
17999
+ if (modified) {
18000
+ adapterRequest = modified;
18001
+ }
18002
+ }
18003
+ const schmockResponse = await handle(toHttpMethod(adapterRequest.method), adapterRequest.path, {
18004
+ headers: adapterRequest.headers,
18005
+ body: adapterRequest.body,
18006
+ query: adapterRequest.query
18007
+ });
18008
+ if (isRouteNotFound(schmockResponse)) {
18009
+ if (passthrough) {
18010
+ return originalFetch(input, init);
18011
+ }
18012
+ return new Response(JSON.stringify({
18013
+ error: "No matching mock route found",
18014
+ code: "ROUTE_NOT_FOUND"
18015
+ }), {
18016
+ status: 404,
18017
+ headers: { "content-type": "application/json" }
18018
+ });
18019
+ }
18020
+ let response = schmockResponse;
18021
+ if (beforeResponse) {
18022
+ const modified = await beforeResponse(response, adapterRequest);
18023
+ if (modified) {
18024
+ response = modified;
18025
+ }
18026
+ }
18027
+ const responseHeaders = new Headers(response.headers);
18028
+ if (!responseHeaders.has("content-type") && response.body !== null && response.body !== undefined) {
18029
+ responseHeaders.set("content-type", "application/json");
18030
+ }
18031
+ const responseBody = response.body === null || response.body === undefined ? null : typeof response.body === "string" ? response.body : JSON.stringify(response.body);
18032
+ return new Response(responseBody, {
18033
+ status: response.status,
18034
+ headers: responseHeaders
18035
+ });
18036
+ } catch (error) {
18037
+ if (errorFormatter) {
18038
+ const formatted = errorFormatter(error instanceof Error ? error : new Error(String(error)));
18039
+ return new Response(JSON.stringify(formatted), {
18040
+ status: 500,
18041
+ headers: { "content-type": "application/json" }
18042
+ });
18043
+ }
18044
+ throw error;
18045
+ }
18046
+ };
18047
+ return {
18048
+ restore() {
18049
+ if (active) {
18050
+ globalThis.fetch = originalFetch;
18051
+ active = false;
18052
+ }
18053
+ },
18054
+ get active() {
18055
+ return active;
18056
+ }
18057
+ };
18058
+ }
18059
+
17896
18060
  // ../core/src/parser.ts
17897
18061
  function parseRouteKey(routeKey) {
17898
18062
  const match = routeKey.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/);
@@ -17927,7 +18091,7 @@ async function runPluginPipeline(plugins, context, initialResponse, logger) {
17927
18091
  logger.log("pipeline", `Processing plugin: ${plugin.name}`);
17928
18092
  try {
17929
18093
  const result = await plugin.process(currentContext, response);
17930
- if (!result || !result.context) {
18094
+ if (!result?.context) {
17931
18095
  throw new Error(`Plugin ${plugin.name} didn't return valid result`);
17932
18096
  }
17933
18097
  currentContext = result.context;
@@ -18077,6 +18241,7 @@ class CallableMockInstance {
18077
18241
  callableRef;
18078
18242
  server;
18079
18243
  serverInfo;
18244
+ interceptHandle = null;
18080
18245
  listeners = new Map;
18081
18246
  constructor(globalConfig = {}) {
18082
18247
  this.globalConfig = globalConfig;
@@ -18284,6 +18449,13 @@ class CallableMockInstance {
18284
18449
  this.serverInfo = undefined;
18285
18450
  this.logger.log("server", "Server stopped");
18286
18451
  }
18452
+ intercept(options) {
18453
+ if (this.interceptHandle?.active) {
18454
+ throw new SchmockError("Already intercepting. Call restore() first.", "ALREADY_INTERCEPTING");
18455
+ }
18456
+ this.interceptHandle = createFetchInterceptor((method, path, opts) => this.handle(method, path, opts), options);
18457
+ return this.interceptHandle;
18458
+ }
18287
18459
  async handle(method, path, options) {
18288
18460
  const handleStart = performance.now();
18289
18461
  const requestId = this.globalConfig.debug ? crypto.randomUUID() : "";
@@ -18474,13 +18646,14 @@ function schmock(config) {
18474
18646
  getRoutes: instance.getRoutes.bind(instance),
18475
18647
  getState: instance.getState.bind(instance),
18476
18648
  listen: instance.listen.bind(instance),
18477
- close: instance.close.bind(instance)
18649
+ close: instance.close.bind(instance),
18650
+ intercept: (options) => instance.intercept(options)
18478
18651
  });
18479
18652
  instance.setCallableRef(callableInstance);
18480
18653
  return callableInstance;
18481
18654
  }
18482
18655
  // ../openapi/package.json
18483
- var version = "1.13.0";
18656
+ var version = "2.0.0";
18484
18657
 
18485
18658
  // ../openapi/src/utils.ts
18486
18659
  function isRecord(value) {
@@ -18498,7 +18671,7 @@ function getRouteCallbacks(route) {
18498
18671
  function fireCallbacks(callbacks, context, response) {
18499
18672
  for (const callback of callbacks) {
18500
18673
  const url = resolveCallbackUrl(callback.urlExpression, context, response);
18501
- if (!url || !url.startsWith("http"))
18674
+ if (!url?.startsWith("http"))
18502
18675
  continue;
18503
18676
  const body = Array.isArray(response) ? response[1] : response;
18504
18677
  fetch(url, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/cli",
3
3
  "description": "CLI for running Schmock mock servers from OpenAPI specs",
4
- "version": "1.13.0",
4
+ "version": "2.0.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@schmock/core": "^1.13.0",
36
- "@schmock/openapi": "^1.13.0"
35
+ "@schmock/core": "^2.0.0",
36
+ "@schmock/openapi": "^2.0.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@amiceli/vitest-cucumber": "^6.3.0",