@schmock/cli 1.13.0 → 2.0.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/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,177 @@ 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 = init?.headers ?? (input instanceof Request ? input.headers : undefined);
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.toLowerCase()] = value;
17939
+ }
17940
+ } else {
17941
+ for (const key of Object.keys(raw)) {
17942
+ headers[key.toLowerCase()] = raw[key];
17943
+ }
17944
+ }
17945
+ return headers;
17946
+ }
17947
+ async function extractBody(input, init) {
17948
+ const bodyInit = init?.body ?? (input instanceof Request ? input.body : null);
17949
+ if (bodyInit === null || bodyInit === undefined)
17950
+ return;
17951
+ if (typeof bodyInit === "string") {
17952
+ try {
17953
+ return JSON.parse(bodyInit);
17954
+ } catch {
17955
+ return bodyInit;
17956
+ }
17957
+ }
17958
+ if (bodyInit instanceof URLSearchParams) {
17959
+ const result = {};
17960
+ bodyInit.forEach((value, key) => {
17961
+ result[key] = value;
17962
+ });
17963
+ return result;
17964
+ }
17965
+ if (input instanceof Request && !init?.body && input.body) {
17966
+ try {
17967
+ return await input.clone().json();
17968
+ } catch {
17969
+ try {
17970
+ return await input.clone().text();
17971
+ } catch {
17972
+ return;
17973
+ }
17974
+ }
17975
+ }
17976
+ return;
17977
+ }
17978
+ function createFetchInterceptor(handle, options = {}) {
17979
+ const {
17980
+ baseUrl,
17981
+ passthrough = true,
17982
+ beforeRequest,
17983
+ beforeResponse,
17984
+ errorFormatter
17985
+ } = options;
17986
+ const originalFetch = globalThis.fetch;
17987
+ let active = true;
17988
+ globalThis.fetch = async (input, init) => {
17989
+ const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17990
+ const path = extractPathname(urlString);
17991
+ if (baseUrl) {
17992
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
17993
+ const isMatch = path === normalizedBase || path.startsWith(`${normalizedBase}/`);
17994
+ if (!isMatch) {
17995
+ return originalFetch(input, init);
17996
+ }
17997
+ }
17998
+ const method = input instanceof Request ? input.method : init?.method ?? "GET";
17999
+ const headers = extractHeaders(input, init);
18000
+ const query = extractQuery(urlString);
18001
+ const body = await extractBody(input, init);
18002
+ let adapterRequest = {
18003
+ method,
18004
+ path,
18005
+ headers,
18006
+ body,
18007
+ query
18008
+ };
18009
+ try {
18010
+ if (beforeRequest) {
18011
+ const modified = await beforeRequest(adapterRequest);
18012
+ if (modified) {
18013
+ adapterRequest = modified;
18014
+ }
18015
+ }
18016
+ const schmockResponse = await handle(toHttpMethod(adapterRequest.method), adapterRequest.path, {
18017
+ headers: adapterRequest.headers,
18018
+ body: adapterRequest.body,
18019
+ query: adapterRequest.query
18020
+ });
18021
+ if (isRouteNotFound(schmockResponse)) {
18022
+ if (passthrough) {
18023
+ return originalFetch(input, init);
18024
+ }
18025
+ return new Response(JSON.stringify({
18026
+ error: "No matching mock route found",
18027
+ code: "ROUTE_NOT_FOUND"
18028
+ }), {
18029
+ status: 404,
18030
+ headers: { "content-type": "application/json" }
18031
+ });
18032
+ }
18033
+ let response = schmockResponse;
18034
+ if (beforeResponse) {
18035
+ const modified = await beforeResponse(response, adapterRequest);
18036
+ if (modified) {
18037
+ response = modified;
18038
+ }
18039
+ }
18040
+ const responseHeaders = new Headers(response.headers);
18041
+ if (!responseHeaders.has("content-type") && response.body !== null && response.body !== undefined) {
18042
+ responseHeaders.set("content-type", "application/json");
18043
+ }
18044
+ const responseBody = response.body === null || response.body === undefined ? null : typeof response.body === "string" ? response.body : JSON.stringify(response.body);
18045
+ return new Response(responseBody, {
18046
+ status: response.status,
18047
+ headers: responseHeaders
18048
+ });
18049
+ } catch (error) {
18050
+ if (errorFormatter) {
18051
+ const formatted = errorFormatter(error instanceof Error ? error : new Error(String(error)));
18052
+ return new Response(JSON.stringify(formatted), {
18053
+ status: 500,
18054
+ headers: { "content-type": "application/json" }
18055
+ });
18056
+ }
18057
+ throw error;
18058
+ }
18059
+ };
18060
+ return {
18061
+ restore() {
18062
+ if (active) {
18063
+ globalThis.fetch = originalFetch;
18064
+ active = false;
18065
+ }
18066
+ },
18067
+ get active() {
18068
+ return active;
18069
+ }
18070
+ };
18071
+ }
18072
+
17897
18073
  // ../core/src/parser.ts
17898
18074
  function parseRouteKey(routeKey) {
17899
18075
  const match = routeKey.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/);
@@ -17928,7 +18104,7 @@ async function runPluginPipeline(plugins, context, initialResponse, logger) {
17928
18104
  logger.log("pipeline", `Processing plugin: ${plugin.name}`);
17929
18105
  try {
17930
18106
  const result = await plugin.process(currentContext, response);
17931
- if (!result || !result.context) {
18107
+ if (!result?.context) {
17932
18108
  throw new Error(`Plugin ${plugin.name} didn't return valid result`);
17933
18109
  }
17934
18110
  currentContext = result.context;
@@ -18078,6 +18254,7 @@ class CallableMockInstance {
18078
18254
  callableRef;
18079
18255
  server;
18080
18256
  serverInfo;
18257
+ interceptHandle = null;
18081
18258
  listeners = new Map;
18082
18259
  constructor(globalConfig = {}) {
18083
18260
  this.globalConfig = globalConfig;
@@ -18210,6 +18387,8 @@ class CallableMockInstance {
18210
18387
  }
18211
18388
  }
18212
18389
  reset() {
18390
+ this.interceptHandle?.restore();
18391
+ this.interceptHandle = null;
18213
18392
  this.close();
18214
18393
  this.routes = [];
18215
18394
  this.staticRoutes.clear();
@@ -18280,11 +18459,19 @@ class CallableMockInstance {
18280
18459
  if (!this.server) {
18281
18460
  return;
18282
18461
  }
18462
+ this.server.closeAllConnections();
18283
18463
  this.server.close();
18284
18464
  this.server = undefined;
18285
18465
  this.serverInfo = undefined;
18286
18466
  this.logger.log("server", "Server stopped");
18287
18467
  }
18468
+ intercept(options) {
18469
+ if (this.interceptHandle?.active) {
18470
+ throw new SchmockError("Already intercepting. Call restore() first.", "ALREADY_INTERCEPTING");
18471
+ }
18472
+ this.interceptHandle = createFetchInterceptor((method, path, opts) => this.handle(method, path, opts), options);
18473
+ return this.interceptHandle;
18474
+ }
18288
18475
  async handle(method, path, options) {
18289
18476
  const handleStart = performance.now();
18290
18477
  const requestId = this.globalConfig.debug ? crypto.randomUUID() : "";
@@ -18475,13 +18662,14 @@ function schmock(config) {
18475
18662
  getRoutes: instance.getRoutes.bind(instance),
18476
18663
  getState: instance.getState.bind(instance),
18477
18664
  listen: instance.listen.bind(instance),
18478
- close: instance.close.bind(instance)
18665
+ close: instance.close.bind(instance),
18666
+ intercept: (options) => instance.intercept(options)
18479
18667
  });
18480
18668
  instance.setCallableRef(callableInstance);
18481
18669
  return callableInstance;
18482
18670
  }
18483
18671
  // ../openapi/package.json
18484
- var version = "1.13.0";
18672
+ var version = "2.0.1";
18485
18673
 
18486
18674
  // ../openapi/src/utils.ts
18487
18675
  function isRecord(value) {
@@ -18499,14 +18687,16 @@ function getRouteCallbacks(route) {
18499
18687
  function fireCallbacks(callbacks, context, response) {
18500
18688
  for (const callback of callbacks) {
18501
18689
  const url = resolveCallbackUrl(callback.urlExpression, context, response);
18502
- if (!url || !url.startsWith("http"))
18690
+ if (!url?.startsWith("http"))
18503
18691
  continue;
18504
18692
  const body = Array.isArray(response) ? response[1] : response;
18505
18693
  fetch(url, {
18506
18694
  method: callback.method,
18507
18695
  headers: { "content-type": "application/json" },
18508
18696
  body: body !== undefined ? JSON.stringify(body) : undefined
18509
- }).catch(() => {});
18697
+ }).catch((error) => {
18698
+ console.warn(`[@schmock/openapi] Callback ${callback.method} ${url} failed:`, error instanceof Error ? error.message : error);
18699
+ });
18510
18700
  }
18511
18701
  }
18512
18702
  function resolveCallbackUrl(expression, context, response) {
@@ -18552,7 +18742,6 @@ function resolveJsonPointer(obj, pointer) {
18552
18742
 
18553
18743
  // ../openapi/src/generators.ts
18554
18744
  import { randomUUID } from "node:crypto";
18555
-
18556
18745
  // ../faker/src/constants.ts
18557
18746
  var MAX_ARRAY_SIZE = 1e4;
18558
18747
  var MAX_NESTING_DEPTH = 10;
@@ -22437,7 +22626,9 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22437
22626
  return explicitCount;
22438
22627
  }
22439
22628
  if (schema.minItems !== undefined && schema.maxItems !== undefined) {
22440
- return Math.floor(random() * (schema.maxItems - schema.minItems + 1)) + schema.minItems;
22629
+ const min = schema.minItems;
22630
+ const max = Math.max(min, schema.maxItems);
22631
+ return Math.floor(random() * (max - min + 1)) + min;
22441
22632
  }
22442
22633
  if (schema.minItems !== undefined) {
22443
22634
  return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
@@ -22450,6 +22641,8 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22450
22641
  function applyOverrides(data, overrides, params, state, query) {
22451
22642
  if (!overrides)
22452
22643
  return data;
22644
+ if (typeof data !== "object" || data === null)
22645
+ return data;
22453
22646
  const result = structuredClone(data);
22454
22647
  for (const [key, value] of Object.entries(overrides)) {
22455
22648
  if (DANGEROUS_KEYS.has(key))
@@ -22458,10 +22651,11 @@ function applyOverrides(data, overrides, params, state, query) {
22458
22651
  setNestedProperty(result, key, value, { params, state, query });
22459
22652
  } else {
22460
22653
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
22654
+ const nested = value;
22461
22655
  if (result[key] && typeof result[key] === "object") {
22462
- result[key] = applyOverrides(result[key], value, params, state, query);
22656
+ result[key] = applyOverrides(result[key], nested, params, state, query);
22463
22657
  } else {
22464
- result[key] = applyOverrides({}, value, params, state, query);
22658
+ result[key] = applyOverrides({}, nested, params, state, query);
22465
22659
  }
22466
22660
  } else if (typeof value === "string" && value.includes("{{")) {
22467
22661
  result[key] = processTemplate(value, { params, state, query });
@@ -22493,34 +22687,28 @@ function setNestedProperty(obj, path, value, context) {
22493
22687
  current[finalKey] = value;
22494
22688
  }
22495
22689
  }
22690
+ function resolveTemplatePath(context, expression) {
22691
+ const parts = expression.trim().split(".");
22692
+ let result = context;
22693
+ for (const part of parts) {
22694
+ if (result && typeof result === "object") {
22695
+ result = result[part];
22696
+ } else {
22697
+ return;
22698
+ }
22699
+ }
22700
+ return result;
22701
+ }
22496
22702
  function processTemplate(template, context) {
22497
22703
  const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
22498
22704
  if (singleTemplateMatch) {
22499
- const expression = singleTemplateMatch[1];
22500
- const parts = expression.trim().split(".");
22501
- let result = context;
22502
- for (const part of parts) {
22503
- if (result && typeof result === "object") {
22504
- result = result[part];
22505
- } else {
22506
- return template;
22507
- }
22508
- }
22705
+ const result = resolveTemplatePath(context, singleTemplateMatch[1]);
22509
22706
  return result !== undefined ? result : template;
22510
22707
  }
22511
- const processed = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22512
- const parts = expression.trim().split(".");
22513
- let result = context;
22514
- for (const part of parts) {
22515
- if (result && typeof result === "object") {
22516
- result = result[part];
22517
- } else {
22518
- return match;
22519
- }
22520
- }
22708
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22709
+ const result = resolveTemplatePath(context, expression);
22521
22710
  return result !== undefined ? String(result) : match;
22522
22711
  });
22523
- return processed;
22524
22712
  }
22525
22713
 
22526
22714
  // ../faker/src/field-mappings.ts
@@ -23992,7 +24180,6 @@ function enhanceFieldSchema(fieldName, fieldSchema) {
23992
24180
  async function generateFromSchema(options) {
23993
24181
  const { schema, count, overrides, params, state, query, seed } = options;
23994
24182
  validateSchema(schema);
23995
- let generated;
23996
24183
  if (schema.type === "array" && schema.items) {
23997
24184
  const itemCount = determineArrayCount(schema, count);
23998
24185
  if (itemCount > MAX_ARRAY_SIZE) {
@@ -24004,38 +24191,39 @@ async function generateFromSchema(options) {
24004
24191
  }
24005
24192
  const itemSchema = rawItemSchema;
24006
24193
  const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
24007
- generated = [];
24194
+ const items = [];
24008
24195
  for (let i3 = 0;i3 < itemCount; i3++) {
24009
24196
  let item = await generateWithJsf(enhancedItemSchema, seed);
24010
24197
  item = postProcessGenerated(item, enhancedItemSchema);
24011
24198
  item = applyOverrides(item, overrides, params, state, query);
24012
- generated.push(item);
24199
+ items.push(item);
24013
24200
  }
24014
- } else {
24015
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24016
- generated = await generateWithJsf(enhancedSchema, seed);
24017
- generated = postProcessGenerated(generated, enhancedSchema);
24018
- generated = applyOverrides(generated, overrides, params, state, query);
24201
+ return items;
24019
24202
  }
24203
+ const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24204
+ let generated = await generateWithJsf(enhancedSchema, seed);
24205
+ generated = postProcessGenerated(generated, enhancedSchema);
24206
+ generated = applyOverrides(generated, overrides, params, state, query);
24020
24207
  return generated;
24021
24208
  }
24022
24209
  function postProcessGenerated(data, schema) {
24023
24210
  if (data === null || data === undefined || !schema || typeof schema !== "object") {
24024
24211
  return data;
24025
24212
  }
24026
- const schemaAny = schema;
24027
- if (schemaAny.schmockNullable === true) {
24213
+ const schemaExt = schema;
24214
+ if (schemaExt.schmockNullable === true) {
24028
24215
  if (Math.random() < NULLABLE_NULL_PROBABILITY) {
24029
24216
  return null;
24030
24217
  }
24031
24218
  }
24032
- if (schema.type === "boolean" && typeof schemaAny.schmockTrueProbability === "number") {
24033
- return Math.random() < schemaAny.schmockTrueProbability;
24219
+ if (schema.type === "boolean" && typeof schemaExt.schmockTrueProbability === "number") {
24220
+ return Math.random() < schemaExt.schmockTrueProbability;
24034
24221
  }
24035
24222
  if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
24223
+ const record = data;
24036
24224
  for (const [key, propSchema] of Object.entries(schema.properties)) {
24037
- if (key in data && isJSONSchema7(propSchema)) {
24038
- data[key] = postProcessGenerated(data[key], propSchema);
24225
+ if (key in record && isJSONSchema7(propSchema)) {
24226
+ record[key] = postProcessGenerated(record[key], propSchema);
24039
24227
  }
24040
24228
  }
24041
24229
  }
@@ -24296,7 +24484,9 @@ async function generateErrorResponse(status, meta) {
24296
24484
  try {
24297
24485
  const body = await generateFromSchema({ schema: errorSchema });
24298
24486
  return toTuple(status, body);
24299
- } catch {}
24487
+ } catch (error) {
24488
+ console.warn(`[@schmock/openapi] Error schema generation failed for status ${status}:`, error instanceof Error ? error.message : error);
24489
+ }
24300
24490
  }
24301
24491
  const defaults = {
24302
24492
  404: { error: "Not found", code: "NOT_FOUND" },
@@ -24308,7 +24498,8 @@ async function generateErrorResponse(status, meta) {
24308
24498
  async function generateWrapperSkeleton(schema) {
24309
24499
  try {
24310
24500
  return await generateFromSchema({ schema });
24311
- } catch {
24501
+ } catch (error) {
24502
+ console.warn("[@schmock/openapi] Wrapper skeleton generation failed:", error instanceof Error ? error.message : error);
24312
24503
  return {};
24313
24504
  }
24314
24505
  }
@@ -24887,7 +25078,7 @@ async function parseSpec(source) {
24887
25078
  }
24888
25079
  const isSwagger2 = "swagger" in api && typeof api.swagger === "string";
24889
25080
  const title = api.info?.title ?? "Untitled";
24890
- const version2 = api.info?.version ?? "0.0.0";
25081
+ const version3 = api.info?.version ?? "0.0.0";
24891
25082
  let basePath = "";
24892
25083
  if (isSwagger2 && "basePath" in api) {
24893
25084
  const bp = api.basePath;
@@ -24912,7 +25103,7 @@ async function parseSpec(source) {
24912
25103
  const paths = [];
24913
25104
  const rawPaths = "paths" in api && isRecord(api.paths) ? api.paths : undefined;
24914
25105
  if (!rawPaths) {
24915
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25106
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
24916
25107
  }
24917
25108
  for (const [pathTemplate, pathItemRaw] of Object.entries(rawPaths)) {
24918
25109
  if (!isRecord(pathItemRaw))
@@ -24953,7 +25144,7 @@ async function parseSpec(source) {
24953
25144
  });
24954
25145
  }
24955
25146
  }
24956
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25147
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
24957
25148
  }
24958
25149
  function isValidParamLocation(location2, isSwagger2) {
24959
25150
  const validLocations = isSwagger2 ? ["path", "query", "header", "body"] : ["path", "query", "header"];
@@ -25484,7 +25675,8 @@ async function generateResponseBody(schema, seed, onSchema, context) {
25484
25675
  }
25485
25676
  try {
25486
25677
  return await generateFromSchema({ schema: finalSchema, seed });
25487
- } catch {
25678
+ } catch (error) {
25679
+ console.warn("[@schmock/openapi] Response body generation failed:", error instanceof Error ? error.message : error);
25488
25680
  return {};
25489
25681
  }
25490
25682
  }
@@ -25698,7 +25890,15 @@ async function createCliServer(options) {
25698
25890
  collectBody(req, headers).then((body) => mock.handle(method, path, { headers, body, query }).then((schmockResponse) => {
25699
25891
  const extra = cors ? CORS_HEADERS : undefined;
25700
25892
  writeSchmockResponse(res, schmockResponse, extra);
25701
- }));
25893
+ })).catch((error) => {
25894
+ if (!res.headersSent) {
25895
+ res.writeHead(500, { "content-type": "application/json" });
25896
+ }
25897
+ res.end(JSON.stringify({
25898
+ error: error instanceof Error ? error.message : "Internal Server Error",
25899
+ code: "SERVER_ERROR"
25900
+ }));
25901
+ });
25702
25902
  });
25703
25903
  return new Promise((resolve, reject) => {
25704
25904
  httpServer.on("error", reject);
@@ -25716,6 +25916,13 @@ async function createCliServer(options) {
25716
25916
  });
25717
25917
  });
25718
25918
  }
25919
+ function validatePort(value) {
25920
+ const port = Number(value);
25921
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
25922
+ throw new Error(`Invalid port "${value}". Port must be an integer between 0 and 65535.`);
25923
+ }
25924
+ return port;
25925
+ }
25719
25926
  function parseCliArgs(args) {
25720
25927
  const { values, positionals } = parseArgs({
25721
25928
  args,
@@ -25738,7 +25945,7 @@ function parseCliArgs(args) {
25738
25945
  const spec = values.spec ?? positionals[0] ?? "";
25739
25946
  return {
25740
25947
  spec,
25741
- port: values.port ? Number(values.port) : undefined,
25948
+ port: values.port ? validatePort(values.port) : undefined,
25742
25949
  hostname: values.hostname,
25743
25950
  seed: values.seed,
25744
25951
  cors: values.cors,
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAaxC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf;AA0ED,wBAAsB,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA0E7E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAqC3E;AAqBD,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,SAAS,CAAC,CAQpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,EACnB,gBAAgB,EAAE,MAAM,SAAS,EACjC,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GACpC,WAAW,CA2Bb;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAaxC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf;AA0ED,wBAAsB,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAuF7E;AAYD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAqC3E;AAqBD,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,SAAS,CAAC,CAQpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,UAAU,EACnB,gBAAgB,EAAE,MAAM,SAAS,EACjC,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GACpC,WAAW,CA2Bb;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDvD"}
package/dist/cli.js CHANGED
@@ -92,12 +92,22 @@ export async function createCliServer(options) {
92
92
  const method = toHttpMethod(req.method ?? "GET");
93
93
  const headers = parseNodeHeaders(req);
94
94
  const query = parseNodeQuery(url);
95
- void collectBody(req, headers).then((body) => mock
95
+ void collectBody(req, headers)
96
+ .then((body) => mock
96
97
  .handle(method, path, { headers, body, query })
97
98
  .then((schmockResponse) => {
98
99
  const extra = cors ? CORS_HEADERS : undefined;
99
100
  writeSchmockResponse(res, schmockResponse, extra);
100
- }));
101
+ }))
102
+ .catch((error) => {
103
+ if (!res.headersSent) {
104
+ res.writeHead(500, { "content-type": "application/json" });
105
+ }
106
+ res.end(JSON.stringify({
107
+ error: error instanceof Error ? error.message : "Internal Server Error",
108
+ code: "SERVER_ERROR",
109
+ }));
110
+ });
101
111
  });
102
112
  return new Promise((resolve, reject) => {
103
113
  httpServer.on("error", reject);
@@ -115,6 +125,13 @@ export async function createCliServer(options) {
115
125
  });
116
126
  });
117
127
  }
128
+ function validatePort(value) {
129
+ const port = Number(value);
130
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
131
+ throw new Error(`Invalid port "${value}". Port must be an integer between 0 and 65535.`);
132
+ }
133
+ return port;
134
+ }
118
135
  export function parseCliArgs(args) {
119
136
  const { values, positionals } = parseArgs({
120
137
  args,
@@ -137,7 +154,7 @@ export function parseCliArgs(args) {
137
154
  const spec = values.spec ?? positionals[0] ?? "";
138
155
  return {
139
156
  spec,
140
- port: values.port ? Number(values.port) : undefined,
157
+ port: values.port ? validatePort(values.port) : undefined,
141
158
  hostname: values.hostname,
142
159
  seed: values.seed,
143
160
  cors: values.cors,
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,177 @@ 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 = init?.headers ?? (input instanceof Request ? input.headers : undefined);
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.toLowerCase()] = value;
17938
+ }
17939
+ } else {
17940
+ for (const key of Object.keys(raw)) {
17941
+ headers[key.toLowerCase()] = raw[key];
17942
+ }
17943
+ }
17944
+ return headers;
17945
+ }
17946
+ async function extractBody(input, init) {
17947
+ const bodyInit = init?.body ?? (input instanceof Request ? input.body : null);
17948
+ if (bodyInit === null || bodyInit === undefined)
17949
+ return;
17950
+ if (typeof bodyInit === "string") {
17951
+ try {
17952
+ return JSON.parse(bodyInit);
17953
+ } catch {
17954
+ return bodyInit;
17955
+ }
17956
+ }
17957
+ if (bodyInit instanceof URLSearchParams) {
17958
+ const result = {};
17959
+ bodyInit.forEach((value, key) => {
17960
+ result[key] = value;
17961
+ });
17962
+ return result;
17963
+ }
17964
+ if (input instanceof Request && !init?.body && input.body) {
17965
+ try {
17966
+ return await input.clone().json();
17967
+ } catch {
17968
+ try {
17969
+ return await input.clone().text();
17970
+ } catch {
17971
+ return;
17972
+ }
17973
+ }
17974
+ }
17975
+ return;
17976
+ }
17977
+ function createFetchInterceptor(handle, options = {}) {
17978
+ const {
17979
+ baseUrl,
17980
+ passthrough = true,
17981
+ beforeRequest,
17982
+ beforeResponse,
17983
+ errorFormatter
17984
+ } = options;
17985
+ const originalFetch = globalThis.fetch;
17986
+ let active = true;
17987
+ globalThis.fetch = async (input, init) => {
17988
+ const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17989
+ const path = extractPathname(urlString);
17990
+ if (baseUrl) {
17991
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
17992
+ const isMatch = path === normalizedBase || path.startsWith(`${normalizedBase}/`);
17993
+ if (!isMatch) {
17994
+ return originalFetch(input, init);
17995
+ }
17996
+ }
17997
+ const method = input instanceof Request ? input.method : init?.method ?? "GET";
17998
+ const headers = extractHeaders(input, init);
17999
+ const query = extractQuery(urlString);
18000
+ const body = await extractBody(input, init);
18001
+ let adapterRequest = {
18002
+ method,
18003
+ path,
18004
+ headers,
18005
+ body,
18006
+ query
18007
+ };
18008
+ try {
18009
+ if (beforeRequest) {
18010
+ const modified = await beforeRequest(adapterRequest);
18011
+ if (modified) {
18012
+ adapterRequest = modified;
18013
+ }
18014
+ }
18015
+ const schmockResponse = await handle(toHttpMethod(adapterRequest.method), adapterRequest.path, {
18016
+ headers: adapterRequest.headers,
18017
+ body: adapterRequest.body,
18018
+ query: adapterRequest.query
18019
+ });
18020
+ if (isRouteNotFound(schmockResponse)) {
18021
+ if (passthrough) {
18022
+ return originalFetch(input, init);
18023
+ }
18024
+ return new Response(JSON.stringify({
18025
+ error: "No matching mock route found",
18026
+ code: "ROUTE_NOT_FOUND"
18027
+ }), {
18028
+ status: 404,
18029
+ headers: { "content-type": "application/json" }
18030
+ });
18031
+ }
18032
+ let response = schmockResponse;
18033
+ if (beforeResponse) {
18034
+ const modified = await beforeResponse(response, adapterRequest);
18035
+ if (modified) {
18036
+ response = modified;
18037
+ }
18038
+ }
18039
+ const responseHeaders = new Headers(response.headers);
18040
+ if (!responseHeaders.has("content-type") && response.body !== null && response.body !== undefined) {
18041
+ responseHeaders.set("content-type", "application/json");
18042
+ }
18043
+ const responseBody = response.body === null || response.body === undefined ? null : typeof response.body === "string" ? response.body : JSON.stringify(response.body);
18044
+ return new Response(responseBody, {
18045
+ status: response.status,
18046
+ headers: responseHeaders
18047
+ });
18048
+ } catch (error) {
18049
+ if (errorFormatter) {
18050
+ const formatted = errorFormatter(error instanceof Error ? error : new Error(String(error)));
18051
+ return new Response(JSON.stringify(formatted), {
18052
+ status: 500,
18053
+ headers: { "content-type": "application/json" }
18054
+ });
18055
+ }
18056
+ throw error;
18057
+ }
18058
+ };
18059
+ return {
18060
+ restore() {
18061
+ if (active) {
18062
+ globalThis.fetch = originalFetch;
18063
+ active = false;
18064
+ }
18065
+ },
18066
+ get active() {
18067
+ return active;
18068
+ }
18069
+ };
18070
+ }
18071
+
17896
18072
  // ../core/src/parser.ts
17897
18073
  function parseRouteKey(routeKey) {
17898
18074
  const match = routeKey.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS) (.+)$/);
@@ -17927,7 +18103,7 @@ async function runPluginPipeline(plugins, context, initialResponse, logger) {
17927
18103
  logger.log("pipeline", `Processing plugin: ${plugin.name}`);
17928
18104
  try {
17929
18105
  const result = await plugin.process(currentContext, response);
17930
- if (!result || !result.context) {
18106
+ if (!result?.context) {
17931
18107
  throw new Error(`Plugin ${plugin.name} didn't return valid result`);
17932
18108
  }
17933
18109
  currentContext = result.context;
@@ -18077,6 +18253,7 @@ class CallableMockInstance {
18077
18253
  callableRef;
18078
18254
  server;
18079
18255
  serverInfo;
18256
+ interceptHandle = null;
18080
18257
  listeners = new Map;
18081
18258
  constructor(globalConfig = {}) {
18082
18259
  this.globalConfig = globalConfig;
@@ -18209,6 +18386,8 @@ class CallableMockInstance {
18209
18386
  }
18210
18387
  }
18211
18388
  reset() {
18389
+ this.interceptHandle?.restore();
18390
+ this.interceptHandle = null;
18212
18391
  this.close();
18213
18392
  this.routes = [];
18214
18393
  this.staticRoutes.clear();
@@ -18279,11 +18458,19 @@ class CallableMockInstance {
18279
18458
  if (!this.server) {
18280
18459
  return;
18281
18460
  }
18461
+ this.server.closeAllConnections();
18282
18462
  this.server.close();
18283
18463
  this.server = undefined;
18284
18464
  this.serverInfo = undefined;
18285
18465
  this.logger.log("server", "Server stopped");
18286
18466
  }
18467
+ intercept(options) {
18468
+ if (this.interceptHandle?.active) {
18469
+ throw new SchmockError("Already intercepting. Call restore() first.", "ALREADY_INTERCEPTING");
18470
+ }
18471
+ this.interceptHandle = createFetchInterceptor((method, path, opts) => this.handle(method, path, opts), options);
18472
+ return this.interceptHandle;
18473
+ }
18287
18474
  async handle(method, path, options) {
18288
18475
  const handleStart = performance.now();
18289
18476
  const requestId = this.globalConfig.debug ? crypto.randomUUID() : "";
@@ -18474,13 +18661,14 @@ function schmock(config) {
18474
18661
  getRoutes: instance.getRoutes.bind(instance),
18475
18662
  getState: instance.getState.bind(instance),
18476
18663
  listen: instance.listen.bind(instance),
18477
- close: instance.close.bind(instance)
18664
+ close: instance.close.bind(instance),
18665
+ intercept: (options) => instance.intercept(options)
18478
18666
  });
18479
18667
  instance.setCallableRef(callableInstance);
18480
18668
  return callableInstance;
18481
18669
  }
18482
18670
  // ../openapi/package.json
18483
- var version = "1.13.0";
18671
+ var version = "2.0.1";
18484
18672
 
18485
18673
  // ../openapi/src/utils.ts
18486
18674
  function isRecord(value) {
@@ -18498,14 +18686,16 @@ function getRouteCallbacks(route) {
18498
18686
  function fireCallbacks(callbacks, context, response) {
18499
18687
  for (const callback of callbacks) {
18500
18688
  const url = resolveCallbackUrl(callback.urlExpression, context, response);
18501
- if (!url || !url.startsWith("http"))
18689
+ if (!url?.startsWith("http"))
18502
18690
  continue;
18503
18691
  const body = Array.isArray(response) ? response[1] : response;
18504
18692
  fetch(url, {
18505
18693
  method: callback.method,
18506
18694
  headers: { "content-type": "application/json" },
18507
18695
  body: body !== undefined ? JSON.stringify(body) : undefined
18508
- }).catch(() => {});
18696
+ }).catch((error) => {
18697
+ console.warn(`[@schmock/openapi] Callback ${callback.method} ${url} failed:`, error instanceof Error ? error.message : error);
18698
+ });
18509
18699
  }
18510
18700
  }
18511
18701
  function resolveCallbackUrl(expression, context, response) {
@@ -18551,7 +18741,6 @@ function resolveJsonPointer(obj, pointer) {
18551
18741
 
18552
18742
  // ../openapi/src/generators.ts
18553
18743
  import { randomUUID } from "node:crypto";
18554
-
18555
18744
  // ../faker/src/constants.ts
18556
18745
  var MAX_ARRAY_SIZE = 1e4;
18557
18746
  var MAX_NESTING_DEPTH = 10;
@@ -22436,7 +22625,9 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22436
22625
  return explicitCount;
22437
22626
  }
22438
22627
  if (schema.minItems !== undefined && schema.maxItems !== undefined) {
22439
- return Math.floor(random() * (schema.maxItems - schema.minItems + 1)) + schema.minItems;
22628
+ const min = schema.minItems;
22629
+ const max = Math.max(min, schema.maxItems);
22630
+ return Math.floor(random() * (max - min + 1)) + min;
22440
22631
  }
22441
22632
  if (schema.minItems !== undefined) {
22442
22633
  return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
@@ -22449,6 +22640,8 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22449
22640
  function applyOverrides(data, overrides, params, state, query) {
22450
22641
  if (!overrides)
22451
22642
  return data;
22643
+ if (typeof data !== "object" || data === null)
22644
+ return data;
22452
22645
  const result = structuredClone(data);
22453
22646
  for (const [key, value] of Object.entries(overrides)) {
22454
22647
  if (DANGEROUS_KEYS.has(key))
@@ -22457,10 +22650,11 @@ function applyOverrides(data, overrides, params, state, query) {
22457
22650
  setNestedProperty(result, key, value, { params, state, query });
22458
22651
  } else {
22459
22652
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
22653
+ const nested = value;
22460
22654
  if (result[key] && typeof result[key] === "object") {
22461
- result[key] = applyOverrides(result[key], value, params, state, query);
22655
+ result[key] = applyOverrides(result[key], nested, params, state, query);
22462
22656
  } else {
22463
- result[key] = applyOverrides({}, value, params, state, query);
22657
+ result[key] = applyOverrides({}, nested, params, state, query);
22464
22658
  }
22465
22659
  } else if (typeof value === "string" && value.includes("{{")) {
22466
22660
  result[key] = processTemplate(value, { params, state, query });
@@ -22492,34 +22686,28 @@ function setNestedProperty(obj, path, value, context) {
22492
22686
  current[finalKey] = value;
22493
22687
  }
22494
22688
  }
22689
+ function resolveTemplatePath(context, expression) {
22690
+ const parts = expression.trim().split(".");
22691
+ let result = context;
22692
+ for (const part of parts) {
22693
+ if (result && typeof result === "object") {
22694
+ result = result[part];
22695
+ } else {
22696
+ return;
22697
+ }
22698
+ }
22699
+ return result;
22700
+ }
22495
22701
  function processTemplate(template, context) {
22496
22702
  const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
22497
22703
  if (singleTemplateMatch) {
22498
- const expression = singleTemplateMatch[1];
22499
- const parts = expression.trim().split(".");
22500
- let result = context;
22501
- for (const part of parts) {
22502
- if (result && typeof result === "object") {
22503
- result = result[part];
22504
- } else {
22505
- return template;
22506
- }
22507
- }
22704
+ const result = resolveTemplatePath(context, singleTemplateMatch[1]);
22508
22705
  return result !== undefined ? result : template;
22509
22706
  }
22510
- const processed = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22511
- const parts = expression.trim().split(".");
22512
- let result = context;
22513
- for (const part of parts) {
22514
- if (result && typeof result === "object") {
22515
- result = result[part];
22516
- } else {
22517
- return match;
22518
- }
22519
- }
22707
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22708
+ const result = resolveTemplatePath(context, expression);
22520
22709
  return result !== undefined ? String(result) : match;
22521
22710
  });
22522
- return processed;
22523
22711
  }
22524
22712
 
22525
22713
  // ../faker/src/field-mappings.ts
@@ -23991,7 +24179,6 @@ function enhanceFieldSchema(fieldName, fieldSchema) {
23991
24179
  async function generateFromSchema(options) {
23992
24180
  const { schema, count, overrides, params, state, query, seed } = options;
23993
24181
  validateSchema(schema);
23994
- let generated;
23995
24182
  if (schema.type === "array" && schema.items) {
23996
24183
  const itemCount = determineArrayCount(schema, count);
23997
24184
  if (itemCount > MAX_ARRAY_SIZE) {
@@ -24003,38 +24190,39 @@ async function generateFromSchema(options) {
24003
24190
  }
24004
24191
  const itemSchema = rawItemSchema;
24005
24192
  const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
24006
- generated = [];
24193
+ const items = [];
24007
24194
  for (let i3 = 0;i3 < itemCount; i3++) {
24008
24195
  let item = await generateWithJsf(enhancedItemSchema, seed);
24009
24196
  item = postProcessGenerated(item, enhancedItemSchema);
24010
24197
  item = applyOverrides(item, overrides, params, state, query);
24011
- generated.push(item);
24198
+ items.push(item);
24012
24199
  }
24013
- } else {
24014
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24015
- generated = await generateWithJsf(enhancedSchema, seed);
24016
- generated = postProcessGenerated(generated, enhancedSchema);
24017
- generated = applyOverrides(generated, overrides, params, state, query);
24200
+ return items;
24018
24201
  }
24202
+ const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24203
+ let generated = await generateWithJsf(enhancedSchema, seed);
24204
+ generated = postProcessGenerated(generated, enhancedSchema);
24205
+ generated = applyOverrides(generated, overrides, params, state, query);
24019
24206
  return generated;
24020
24207
  }
24021
24208
  function postProcessGenerated(data, schema) {
24022
24209
  if (data === null || data === undefined || !schema || typeof schema !== "object") {
24023
24210
  return data;
24024
24211
  }
24025
- const schemaAny = schema;
24026
- if (schemaAny.schmockNullable === true) {
24212
+ const schemaExt = schema;
24213
+ if (schemaExt.schmockNullable === true) {
24027
24214
  if (Math.random() < NULLABLE_NULL_PROBABILITY) {
24028
24215
  return null;
24029
24216
  }
24030
24217
  }
24031
- if (schema.type === "boolean" && typeof schemaAny.schmockTrueProbability === "number") {
24032
- return Math.random() < schemaAny.schmockTrueProbability;
24218
+ if (schema.type === "boolean" && typeof schemaExt.schmockTrueProbability === "number") {
24219
+ return Math.random() < schemaExt.schmockTrueProbability;
24033
24220
  }
24034
24221
  if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
24222
+ const record = data;
24035
24223
  for (const [key, propSchema] of Object.entries(schema.properties)) {
24036
- if (key in data && isJSONSchema7(propSchema)) {
24037
- data[key] = postProcessGenerated(data[key], propSchema);
24224
+ if (key in record && isJSONSchema7(propSchema)) {
24225
+ record[key] = postProcessGenerated(record[key], propSchema);
24038
24226
  }
24039
24227
  }
24040
24228
  }
@@ -24295,7 +24483,9 @@ async function generateErrorResponse(status, meta) {
24295
24483
  try {
24296
24484
  const body = await generateFromSchema({ schema: errorSchema });
24297
24485
  return toTuple(status, body);
24298
- } catch {}
24486
+ } catch (error) {
24487
+ console.warn(`[@schmock/openapi] Error schema generation failed for status ${status}:`, error instanceof Error ? error.message : error);
24488
+ }
24299
24489
  }
24300
24490
  const defaults = {
24301
24491
  404: { error: "Not found", code: "NOT_FOUND" },
@@ -24307,7 +24497,8 @@ async function generateErrorResponse(status, meta) {
24307
24497
  async function generateWrapperSkeleton(schema) {
24308
24498
  try {
24309
24499
  return await generateFromSchema({ schema });
24310
- } catch {
24500
+ } catch (error) {
24501
+ console.warn("[@schmock/openapi] Wrapper skeleton generation failed:", error instanceof Error ? error.message : error);
24311
24502
  return {};
24312
24503
  }
24313
24504
  }
@@ -24886,7 +25077,7 @@ async function parseSpec(source) {
24886
25077
  }
24887
25078
  const isSwagger2 = "swagger" in api && typeof api.swagger === "string";
24888
25079
  const title = api.info?.title ?? "Untitled";
24889
- const version2 = api.info?.version ?? "0.0.0";
25080
+ const version3 = api.info?.version ?? "0.0.0";
24890
25081
  let basePath = "";
24891
25082
  if (isSwagger2 && "basePath" in api) {
24892
25083
  const bp = api.basePath;
@@ -24911,7 +25102,7 @@ async function parseSpec(source) {
24911
25102
  const paths = [];
24912
25103
  const rawPaths = "paths" in api && isRecord(api.paths) ? api.paths : undefined;
24913
25104
  if (!rawPaths) {
24914
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25105
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
24915
25106
  }
24916
25107
  for (const [pathTemplate, pathItemRaw] of Object.entries(rawPaths)) {
24917
25108
  if (!isRecord(pathItemRaw))
@@ -24952,7 +25143,7 @@ async function parseSpec(source) {
24952
25143
  });
24953
25144
  }
24954
25145
  }
24955
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25146
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
24956
25147
  }
24957
25148
  function isValidParamLocation(location2, isSwagger2) {
24958
25149
  const validLocations = isSwagger2 ? ["path", "query", "header", "body"] : ["path", "query", "header"];
@@ -25483,7 +25674,8 @@ async function generateResponseBody(schema, seed, onSchema, context) {
25483
25674
  }
25484
25675
  try {
25485
25676
  return await generateFromSchema({ schema: finalSchema, seed });
25486
- } catch {
25677
+ } catch (error) {
25678
+ console.warn("[@schmock/openapi] Response body generation failed:", error instanceof Error ? error.message : error);
25487
25679
  return {};
25488
25680
  }
25489
25681
  }
@@ -25697,7 +25889,15 @@ async function createCliServer(options) {
25697
25889
  collectBody(req, headers).then((body) => mock.handle(method, path, { headers, body, query }).then((schmockResponse) => {
25698
25890
  const extra = cors ? CORS_HEADERS : undefined;
25699
25891
  writeSchmockResponse(res, schmockResponse, extra);
25700
- }));
25892
+ })).catch((error) => {
25893
+ if (!res.headersSent) {
25894
+ res.writeHead(500, { "content-type": "application/json" });
25895
+ }
25896
+ res.end(JSON.stringify({
25897
+ error: error instanceof Error ? error.message : "Internal Server Error",
25898
+ code: "SERVER_ERROR"
25899
+ }));
25900
+ });
25701
25901
  });
25702
25902
  return new Promise((resolve, reject) => {
25703
25903
  httpServer.on("error", reject);
@@ -25715,6 +25915,13 @@ async function createCliServer(options) {
25715
25915
  });
25716
25916
  });
25717
25917
  }
25918
+ function validatePort(value) {
25919
+ const port = Number(value);
25920
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
25921
+ throw new Error(`Invalid port "${value}". Port must be an integer between 0 and 65535.`);
25922
+ }
25923
+ return port;
25924
+ }
25718
25925
  function parseCliArgs(args) {
25719
25926
  const { values, positionals } = parseArgs({
25720
25927
  args,
@@ -25737,7 +25944,7 @@ function parseCliArgs(args) {
25737
25944
  const spec = values.spec ?? positionals[0] ?? "";
25738
25945
  return {
25739
25946
  spec,
25740
- port: values.port ? Number(values.port) : undefined,
25947
+ port: values.port ? validatePort(values.port) : undefined,
25741
25948
  hostname: values.hostname,
25742
25949
  seed: values.seed,
25743
25950
  cors: values.cors,
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.1",
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",