@schmock/cli 2.0.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
@@ -17926,7 +17926,7 @@ function extractQuery(url) {
17926
17926
  }
17927
17927
  function extractHeaders(input, init) {
17928
17928
  const headers = {};
17929
- const raw = input instanceof Request ? input.headers : init?.headers;
17929
+ const raw = init?.headers ?? (input instanceof Request ? input.headers : undefined);
17930
17930
  if (!raw)
17931
17931
  return headers;
17932
17932
  if (raw instanceof Headers) {
@@ -17935,18 +17935,19 @@ function extractHeaders(input, init) {
17935
17935
  });
17936
17936
  } else if (Array.isArray(raw)) {
17937
17937
  for (const [key, value] of raw) {
17938
- headers[key] = value;
17938
+ headers[key.toLowerCase()] = value;
17939
17939
  }
17940
17940
  } else {
17941
- Object.assign(headers, raw);
17941
+ for (const key of Object.keys(raw)) {
17942
+ headers[key.toLowerCase()] = raw[key];
17943
+ }
17942
17944
  }
17943
17945
  return headers;
17944
17946
  }
17945
17947
  async function extractBody(input, init) {
17946
- const rawBody = input instanceof Request ? input.body : init?.body;
17947
- if (rawBody === null || rawBody === undefined)
17948
+ const bodyInit = init?.body ?? (input instanceof Request ? input.body : null);
17949
+ if (bodyInit === null || bodyInit === undefined)
17948
17950
  return;
17949
- const bodyInit = init?.body;
17950
17951
  if (typeof bodyInit === "string") {
17951
17952
  try {
17952
17953
  return JSON.parse(bodyInit);
@@ -17954,7 +17955,14 @@ async function extractBody(input, init) {
17954
17955
  return bodyInit;
17955
17956
  }
17956
17957
  }
17957
- if (input instanceof Request && input.body) {
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) {
17958
17966
  try {
17959
17967
  return await input.clone().json();
17960
17968
  } catch {
@@ -17980,8 +17988,12 @@ function createFetchInterceptor(handle, options = {}) {
17980
17988
  globalThis.fetch = async (input, init) => {
17981
17989
  const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17982
17990
  const path = extractPathname(urlString);
17983
- if (baseUrl && !path.startsWith(baseUrl)) {
17984
- return originalFetch(input, init);
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
+ }
17985
17997
  }
17986
17998
  const method = input instanceof Request ? input.method : init?.method ?? "GET";
17987
17999
  const headers = extractHeaders(input, init);
@@ -18375,6 +18387,8 @@ class CallableMockInstance {
18375
18387
  }
18376
18388
  }
18377
18389
  reset() {
18390
+ this.interceptHandle?.restore();
18391
+ this.interceptHandle = null;
18378
18392
  this.close();
18379
18393
  this.routes = [];
18380
18394
  this.staticRoutes.clear();
@@ -18445,6 +18459,7 @@ class CallableMockInstance {
18445
18459
  if (!this.server) {
18446
18460
  return;
18447
18461
  }
18462
+ this.server.closeAllConnections();
18448
18463
  this.server.close();
18449
18464
  this.server = undefined;
18450
18465
  this.serverInfo = undefined;
@@ -18654,7 +18669,7 @@ function schmock(config) {
18654
18669
  return callableInstance;
18655
18670
  }
18656
18671
  // ../openapi/package.json
18657
- var version = "2.0.0";
18672
+ var version = "2.0.1";
18658
18673
 
18659
18674
  // ../openapi/src/utils.ts
18660
18675
  function isRecord(value) {
@@ -18679,7 +18694,9 @@ function fireCallbacks(callbacks, context, response) {
18679
18694
  method: callback.method,
18680
18695
  headers: { "content-type": "application/json" },
18681
18696
  body: body !== undefined ? JSON.stringify(body) : undefined
18682
- }).catch(() => {});
18697
+ }).catch((error) => {
18698
+ console.warn(`[@schmock/openapi] Callback ${callback.method} ${url} failed:`, error instanceof Error ? error.message : error);
18699
+ });
18683
18700
  }
18684
18701
  }
18685
18702
  function resolveCallbackUrl(expression, context, response) {
@@ -18725,7 +18742,6 @@ function resolveJsonPointer(obj, pointer) {
18725
18742
 
18726
18743
  // ../openapi/src/generators.ts
18727
18744
  import { randomUUID } from "node:crypto";
18728
-
18729
18745
  // ../faker/src/constants.ts
18730
18746
  var MAX_ARRAY_SIZE = 1e4;
18731
18747
  var MAX_NESTING_DEPTH = 10;
@@ -22610,7 +22626,9 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22610
22626
  return explicitCount;
22611
22627
  }
22612
22628
  if (schema.minItems !== undefined && schema.maxItems !== undefined) {
22613
- 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;
22614
22632
  }
22615
22633
  if (schema.minItems !== undefined) {
22616
22634
  return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
@@ -22623,6 +22641,8 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22623
22641
  function applyOverrides(data, overrides, params, state, query) {
22624
22642
  if (!overrides)
22625
22643
  return data;
22644
+ if (typeof data !== "object" || data === null)
22645
+ return data;
22626
22646
  const result = structuredClone(data);
22627
22647
  for (const [key, value] of Object.entries(overrides)) {
22628
22648
  if (DANGEROUS_KEYS.has(key))
@@ -22631,10 +22651,11 @@ function applyOverrides(data, overrides, params, state, query) {
22631
22651
  setNestedProperty(result, key, value, { params, state, query });
22632
22652
  } else {
22633
22653
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
22654
+ const nested = value;
22634
22655
  if (result[key] && typeof result[key] === "object") {
22635
- result[key] = applyOverrides(result[key], value, params, state, query);
22656
+ result[key] = applyOverrides(result[key], nested, params, state, query);
22636
22657
  } else {
22637
- result[key] = applyOverrides({}, value, params, state, query);
22658
+ result[key] = applyOverrides({}, nested, params, state, query);
22638
22659
  }
22639
22660
  } else if (typeof value === "string" && value.includes("{{")) {
22640
22661
  result[key] = processTemplate(value, { params, state, query });
@@ -22666,34 +22687,28 @@ function setNestedProperty(obj, path, value, context) {
22666
22687
  current[finalKey] = value;
22667
22688
  }
22668
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
+ }
22669
22702
  function processTemplate(template, context) {
22670
22703
  const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
22671
22704
  if (singleTemplateMatch) {
22672
- const expression = singleTemplateMatch[1];
22673
- const parts = expression.trim().split(".");
22674
- let result = context;
22675
- for (const part of parts) {
22676
- if (result && typeof result === "object") {
22677
- result = result[part];
22678
- } else {
22679
- return template;
22680
- }
22681
- }
22705
+ const result = resolveTemplatePath(context, singleTemplateMatch[1]);
22682
22706
  return result !== undefined ? result : template;
22683
22707
  }
22684
- const processed = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22685
- const parts = expression.trim().split(".");
22686
- let result = context;
22687
- for (const part of parts) {
22688
- if (result && typeof result === "object") {
22689
- result = result[part];
22690
- } else {
22691
- return match;
22692
- }
22693
- }
22708
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22709
+ const result = resolveTemplatePath(context, expression);
22694
22710
  return result !== undefined ? String(result) : match;
22695
22711
  });
22696
- return processed;
22697
22712
  }
22698
22713
 
22699
22714
  // ../faker/src/field-mappings.ts
@@ -24165,7 +24180,6 @@ function enhanceFieldSchema(fieldName, fieldSchema) {
24165
24180
  async function generateFromSchema(options) {
24166
24181
  const { schema, count, overrides, params, state, query, seed } = options;
24167
24182
  validateSchema(schema);
24168
- let generated;
24169
24183
  if (schema.type === "array" && schema.items) {
24170
24184
  const itemCount = determineArrayCount(schema, count);
24171
24185
  if (itemCount > MAX_ARRAY_SIZE) {
@@ -24177,38 +24191,39 @@ async function generateFromSchema(options) {
24177
24191
  }
24178
24192
  const itemSchema = rawItemSchema;
24179
24193
  const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
24180
- generated = [];
24194
+ const items = [];
24181
24195
  for (let i3 = 0;i3 < itemCount; i3++) {
24182
24196
  let item = await generateWithJsf(enhancedItemSchema, seed);
24183
24197
  item = postProcessGenerated(item, enhancedItemSchema);
24184
24198
  item = applyOverrides(item, overrides, params, state, query);
24185
- generated.push(item);
24199
+ items.push(item);
24186
24200
  }
24187
- } else {
24188
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24189
- generated = await generateWithJsf(enhancedSchema, seed);
24190
- generated = postProcessGenerated(generated, enhancedSchema);
24191
- generated = applyOverrides(generated, overrides, params, state, query);
24201
+ return items;
24192
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);
24193
24207
  return generated;
24194
24208
  }
24195
24209
  function postProcessGenerated(data, schema) {
24196
24210
  if (data === null || data === undefined || !schema || typeof schema !== "object") {
24197
24211
  return data;
24198
24212
  }
24199
- const schemaAny = schema;
24200
- if (schemaAny.schmockNullable === true) {
24213
+ const schemaExt = schema;
24214
+ if (schemaExt.schmockNullable === true) {
24201
24215
  if (Math.random() < NULLABLE_NULL_PROBABILITY) {
24202
24216
  return null;
24203
24217
  }
24204
24218
  }
24205
- if (schema.type === "boolean" && typeof schemaAny.schmockTrueProbability === "number") {
24206
- return Math.random() < schemaAny.schmockTrueProbability;
24219
+ if (schema.type === "boolean" && typeof schemaExt.schmockTrueProbability === "number") {
24220
+ return Math.random() < schemaExt.schmockTrueProbability;
24207
24221
  }
24208
24222
  if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
24223
+ const record = data;
24209
24224
  for (const [key, propSchema] of Object.entries(schema.properties)) {
24210
- if (key in data && isJSONSchema7(propSchema)) {
24211
- data[key] = postProcessGenerated(data[key], propSchema);
24225
+ if (key in record && isJSONSchema7(propSchema)) {
24226
+ record[key] = postProcessGenerated(record[key], propSchema);
24212
24227
  }
24213
24228
  }
24214
24229
  }
@@ -24469,7 +24484,9 @@ async function generateErrorResponse(status, meta) {
24469
24484
  try {
24470
24485
  const body = await generateFromSchema({ schema: errorSchema });
24471
24486
  return toTuple(status, body);
24472
- } catch {}
24487
+ } catch (error) {
24488
+ console.warn(`[@schmock/openapi] Error schema generation failed for status ${status}:`, error instanceof Error ? error.message : error);
24489
+ }
24473
24490
  }
24474
24491
  const defaults = {
24475
24492
  404: { error: "Not found", code: "NOT_FOUND" },
@@ -24481,7 +24498,8 @@ async function generateErrorResponse(status, meta) {
24481
24498
  async function generateWrapperSkeleton(schema) {
24482
24499
  try {
24483
24500
  return await generateFromSchema({ schema });
24484
- } catch {
24501
+ } catch (error) {
24502
+ console.warn("[@schmock/openapi] Wrapper skeleton generation failed:", error instanceof Error ? error.message : error);
24485
24503
  return {};
24486
24504
  }
24487
24505
  }
@@ -25060,7 +25078,7 @@ async function parseSpec(source) {
25060
25078
  }
25061
25079
  const isSwagger2 = "swagger" in api && typeof api.swagger === "string";
25062
25080
  const title = api.info?.title ?? "Untitled";
25063
- const version2 = api.info?.version ?? "0.0.0";
25081
+ const version3 = api.info?.version ?? "0.0.0";
25064
25082
  let basePath = "";
25065
25083
  if (isSwagger2 && "basePath" in api) {
25066
25084
  const bp = api.basePath;
@@ -25085,7 +25103,7 @@ async function parseSpec(source) {
25085
25103
  const paths = [];
25086
25104
  const rawPaths = "paths" in api && isRecord(api.paths) ? api.paths : undefined;
25087
25105
  if (!rawPaths) {
25088
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25106
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
25089
25107
  }
25090
25108
  for (const [pathTemplate, pathItemRaw] of Object.entries(rawPaths)) {
25091
25109
  if (!isRecord(pathItemRaw))
@@ -25126,7 +25144,7 @@ async function parseSpec(source) {
25126
25144
  });
25127
25145
  }
25128
25146
  }
25129
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25147
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
25130
25148
  }
25131
25149
  function isValidParamLocation(location2, isSwagger2) {
25132
25150
  const validLocations = isSwagger2 ? ["path", "query", "header", "body"] : ["path", "query", "header"];
@@ -25657,7 +25675,8 @@ async function generateResponseBody(schema, seed, onSchema, context) {
25657
25675
  }
25658
25676
  try {
25659
25677
  return await generateFromSchema({ schema: finalSchema, seed });
25660
- } catch {
25678
+ } catch (error) {
25679
+ console.warn("[@schmock/openapi] Response body generation failed:", error instanceof Error ? error.message : error);
25661
25680
  return {};
25662
25681
  }
25663
25682
  }
@@ -25871,7 +25890,15 @@ async function createCliServer(options) {
25871
25890
  collectBody(req, headers).then((body) => mock.handle(method, path, { headers, body, query }).then((schmockResponse) => {
25872
25891
  const extra = cors ? CORS_HEADERS : undefined;
25873
25892
  writeSchmockResponse(res, schmockResponse, extra);
25874
- }));
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
+ });
25875
25902
  });
25876
25903
  return new Promise((resolve, reject) => {
25877
25904
  httpServer.on("error", reject);
@@ -25889,6 +25916,13 @@ async function createCliServer(options) {
25889
25916
  });
25890
25917
  });
25891
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
+ }
25892
25926
  function parseCliArgs(args) {
25893
25927
  const { values, positionals } = parseArgs({
25894
25928
  args,
@@ -25911,7 +25945,7 @@ function parseCliArgs(args) {
25911
25945
  const spec = values.spec ?? positionals[0] ?? "";
25912
25946
  return {
25913
25947
  spec,
25914
- port: values.port ? Number(values.port) : undefined,
25948
+ port: values.port ? validatePort(values.port) : undefined,
25915
25949
  hostname: values.hostname,
25916
25950
  seed: values.seed,
25917
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
@@ -17925,7 +17925,7 @@ function extractQuery(url) {
17925
17925
  }
17926
17926
  function extractHeaders(input, init) {
17927
17927
  const headers = {};
17928
- const raw = input instanceof Request ? input.headers : init?.headers;
17928
+ const raw = init?.headers ?? (input instanceof Request ? input.headers : undefined);
17929
17929
  if (!raw)
17930
17930
  return headers;
17931
17931
  if (raw instanceof Headers) {
@@ -17934,18 +17934,19 @@ function extractHeaders(input, init) {
17934
17934
  });
17935
17935
  } else if (Array.isArray(raw)) {
17936
17936
  for (const [key, value] of raw) {
17937
- headers[key] = value;
17937
+ headers[key.toLowerCase()] = value;
17938
17938
  }
17939
17939
  } else {
17940
- Object.assign(headers, raw);
17940
+ for (const key of Object.keys(raw)) {
17941
+ headers[key.toLowerCase()] = raw[key];
17942
+ }
17941
17943
  }
17942
17944
  return headers;
17943
17945
  }
17944
17946
  async function extractBody(input, init) {
17945
- const rawBody = input instanceof Request ? input.body : init?.body;
17946
- if (rawBody === null || rawBody === undefined)
17947
+ const bodyInit = init?.body ?? (input instanceof Request ? input.body : null);
17948
+ if (bodyInit === null || bodyInit === undefined)
17947
17949
  return;
17948
- const bodyInit = init?.body;
17949
17950
  if (typeof bodyInit === "string") {
17950
17951
  try {
17951
17952
  return JSON.parse(bodyInit);
@@ -17953,7 +17954,14 @@ async function extractBody(input, init) {
17953
17954
  return bodyInit;
17954
17955
  }
17955
17956
  }
17956
- if (input instanceof Request && input.body) {
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) {
17957
17965
  try {
17958
17966
  return await input.clone().json();
17959
17967
  } catch {
@@ -17979,8 +17987,12 @@ function createFetchInterceptor(handle, options = {}) {
17979
17987
  globalThis.fetch = async (input, init) => {
17980
17988
  const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
17981
17989
  const path = extractPathname(urlString);
17982
- if (baseUrl && !path.startsWith(baseUrl)) {
17983
- return originalFetch(input, init);
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
+ }
17984
17996
  }
17985
17997
  const method = input instanceof Request ? input.method : init?.method ?? "GET";
17986
17998
  const headers = extractHeaders(input, init);
@@ -18374,6 +18386,8 @@ class CallableMockInstance {
18374
18386
  }
18375
18387
  }
18376
18388
  reset() {
18389
+ this.interceptHandle?.restore();
18390
+ this.interceptHandle = null;
18377
18391
  this.close();
18378
18392
  this.routes = [];
18379
18393
  this.staticRoutes.clear();
@@ -18444,6 +18458,7 @@ class CallableMockInstance {
18444
18458
  if (!this.server) {
18445
18459
  return;
18446
18460
  }
18461
+ this.server.closeAllConnections();
18447
18462
  this.server.close();
18448
18463
  this.server = undefined;
18449
18464
  this.serverInfo = undefined;
@@ -18653,7 +18668,7 @@ function schmock(config) {
18653
18668
  return callableInstance;
18654
18669
  }
18655
18670
  // ../openapi/package.json
18656
- var version = "2.0.0";
18671
+ var version = "2.0.1";
18657
18672
 
18658
18673
  // ../openapi/src/utils.ts
18659
18674
  function isRecord(value) {
@@ -18678,7 +18693,9 @@ function fireCallbacks(callbacks, context, response) {
18678
18693
  method: callback.method,
18679
18694
  headers: { "content-type": "application/json" },
18680
18695
  body: body !== undefined ? JSON.stringify(body) : undefined
18681
- }).catch(() => {});
18696
+ }).catch((error) => {
18697
+ console.warn(`[@schmock/openapi] Callback ${callback.method} ${url} failed:`, error instanceof Error ? error.message : error);
18698
+ });
18682
18699
  }
18683
18700
  }
18684
18701
  function resolveCallbackUrl(expression, context, response) {
@@ -18724,7 +18741,6 @@ function resolveJsonPointer(obj, pointer) {
18724
18741
 
18725
18742
  // ../openapi/src/generators.ts
18726
18743
  import { randomUUID } from "node:crypto";
18727
-
18728
18744
  // ../faker/src/constants.ts
18729
18745
  var MAX_ARRAY_SIZE = 1e4;
18730
18746
  var MAX_NESTING_DEPTH = 10;
@@ -22609,7 +22625,9 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22609
22625
  return explicitCount;
22610
22626
  }
22611
22627
  if (schema.minItems !== undefined && schema.maxItems !== undefined) {
22612
- 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;
22613
22631
  }
22614
22632
  if (schema.minItems !== undefined) {
22615
22633
  return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
@@ -22622,6 +22640,8 @@ function determineArrayCount(schema, explicitCount, random = Math.random) {
22622
22640
  function applyOverrides(data, overrides, params, state, query) {
22623
22641
  if (!overrides)
22624
22642
  return data;
22643
+ if (typeof data !== "object" || data === null)
22644
+ return data;
22625
22645
  const result = structuredClone(data);
22626
22646
  for (const [key, value] of Object.entries(overrides)) {
22627
22647
  if (DANGEROUS_KEYS.has(key))
@@ -22630,10 +22650,11 @@ function applyOverrides(data, overrides, params, state, query) {
22630
22650
  setNestedProperty(result, key, value, { params, state, query });
22631
22651
  } else {
22632
22652
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
22653
+ const nested = value;
22633
22654
  if (result[key] && typeof result[key] === "object") {
22634
- result[key] = applyOverrides(result[key], value, params, state, query);
22655
+ result[key] = applyOverrides(result[key], nested, params, state, query);
22635
22656
  } else {
22636
- result[key] = applyOverrides({}, value, params, state, query);
22657
+ result[key] = applyOverrides({}, nested, params, state, query);
22637
22658
  }
22638
22659
  } else if (typeof value === "string" && value.includes("{{")) {
22639
22660
  result[key] = processTemplate(value, { params, state, query });
@@ -22665,34 +22686,28 @@ function setNestedProperty(obj, path, value, context) {
22665
22686
  current[finalKey] = value;
22666
22687
  }
22667
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
+ }
22668
22701
  function processTemplate(template, context) {
22669
22702
  const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
22670
22703
  if (singleTemplateMatch) {
22671
- const expression = singleTemplateMatch[1];
22672
- const parts = expression.trim().split(".");
22673
- let result = context;
22674
- for (const part of parts) {
22675
- if (result && typeof result === "object") {
22676
- result = result[part];
22677
- } else {
22678
- return template;
22679
- }
22680
- }
22704
+ const result = resolveTemplatePath(context, singleTemplateMatch[1]);
22681
22705
  return result !== undefined ? result : template;
22682
22706
  }
22683
- const processed = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22684
- const parts = expression.trim().split(".");
22685
- let result = context;
22686
- for (const part of parts) {
22687
- if (result && typeof result === "object") {
22688
- result = result[part];
22689
- } else {
22690
- return match;
22691
- }
22692
- }
22707
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, expression) => {
22708
+ const result = resolveTemplatePath(context, expression);
22693
22709
  return result !== undefined ? String(result) : match;
22694
22710
  });
22695
- return processed;
22696
22711
  }
22697
22712
 
22698
22713
  // ../faker/src/field-mappings.ts
@@ -24164,7 +24179,6 @@ function enhanceFieldSchema(fieldName, fieldSchema) {
24164
24179
  async function generateFromSchema(options) {
24165
24180
  const { schema, count, overrides, params, state, query, seed } = options;
24166
24181
  validateSchema(schema);
24167
- let generated;
24168
24182
  if (schema.type === "array" && schema.items) {
24169
24183
  const itemCount = determineArrayCount(schema, count);
24170
24184
  if (itemCount > MAX_ARRAY_SIZE) {
@@ -24176,38 +24190,39 @@ async function generateFromSchema(options) {
24176
24190
  }
24177
24191
  const itemSchema = rawItemSchema;
24178
24192
  const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
24179
- generated = [];
24193
+ const items = [];
24180
24194
  for (let i3 = 0;i3 < itemCount; i3++) {
24181
24195
  let item = await generateWithJsf(enhancedItemSchema, seed);
24182
24196
  item = postProcessGenerated(item, enhancedItemSchema);
24183
24197
  item = applyOverrides(item, overrides, params, state, query);
24184
- generated.push(item);
24198
+ items.push(item);
24185
24199
  }
24186
- } else {
24187
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
24188
- generated = await generateWithJsf(enhancedSchema, seed);
24189
- generated = postProcessGenerated(generated, enhancedSchema);
24190
- generated = applyOverrides(generated, overrides, params, state, query);
24200
+ return items;
24191
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);
24192
24206
  return generated;
24193
24207
  }
24194
24208
  function postProcessGenerated(data, schema) {
24195
24209
  if (data === null || data === undefined || !schema || typeof schema !== "object") {
24196
24210
  return data;
24197
24211
  }
24198
- const schemaAny = schema;
24199
- if (schemaAny.schmockNullable === true) {
24212
+ const schemaExt = schema;
24213
+ if (schemaExt.schmockNullable === true) {
24200
24214
  if (Math.random() < NULLABLE_NULL_PROBABILITY) {
24201
24215
  return null;
24202
24216
  }
24203
24217
  }
24204
- if (schema.type === "boolean" && typeof schemaAny.schmockTrueProbability === "number") {
24205
- return Math.random() < schemaAny.schmockTrueProbability;
24218
+ if (schema.type === "boolean" && typeof schemaExt.schmockTrueProbability === "number") {
24219
+ return Math.random() < schemaExt.schmockTrueProbability;
24206
24220
  }
24207
24221
  if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
24222
+ const record = data;
24208
24223
  for (const [key, propSchema] of Object.entries(schema.properties)) {
24209
- if (key in data && isJSONSchema7(propSchema)) {
24210
- data[key] = postProcessGenerated(data[key], propSchema);
24224
+ if (key in record && isJSONSchema7(propSchema)) {
24225
+ record[key] = postProcessGenerated(record[key], propSchema);
24211
24226
  }
24212
24227
  }
24213
24228
  }
@@ -24468,7 +24483,9 @@ async function generateErrorResponse(status, meta) {
24468
24483
  try {
24469
24484
  const body = await generateFromSchema({ schema: errorSchema });
24470
24485
  return toTuple(status, body);
24471
- } catch {}
24486
+ } catch (error) {
24487
+ console.warn(`[@schmock/openapi] Error schema generation failed for status ${status}:`, error instanceof Error ? error.message : error);
24488
+ }
24472
24489
  }
24473
24490
  const defaults = {
24474
24491
  404: { error: "Not found", code: "NOT_FOUND" },
@@ -24480,7 +24497,8 @@ async function generateErrorResponse(status, meta) {
24480
24497
  async function generateWrapperSkeleton(schema) {
24481
24498
  try {
24482
24499
  return await generateFromSchema({ schema });
24483
- } catch {
24500
+ } catch (error) {
24501
+ console.warn("[@schmock/openapi] Wrapper skeleton generation failed:", error instanceof Error ? error.message : error);
24484
24502
  return {};
24485
24503
  }
24486
24504
  }
@@ -25059,7 +25077,7 @@ async function parseSpec(source) {
25059
25077
  }
25060
25078
  const isSwagger2 = "swagger" in api && typeof api.swagger === "string";
25061
25079
  const title = api.info?.title ?? "Untitled";
25062
- const version2 = api.info?.version ?? "0.0.0";
25080
+ const version3 = api.info?.version ?? "0.0.0";
25063
25081
  let basePath = "";
25064
25082
  if (isSwagger2 && "basePath" in api) {
25065
25083
  const bp = api.basePath;
@@ -25084,7 +25102,7 @@ async function parseSpec(source) {
25084
25102
  const paths = [];
25085
25103
  const rawPaths = "paths" in api && isRecord(api.paths) ? api.paths : undefined;
25086
25104
  if (!rawPaths) {
25087
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25105
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
25088
25106
  }
25089
25107
  for (const [pathTemplate, pathItemRaw] of Object.entries(rawPaths)) {
25090
25108
  if (!isRecord(pathItemRaw))
@@ -25125,7 +25143,7 @@ async function parseSpec(source) {
25125
25143
  });
25126
25144
  }
25127
25145
  }
25128
- return { title, version: version2, basePath, paths, securitySchemes, globalSecurity };
25146
+ return { title, version: version3, basePath, paths, securitySchemes, globalSecurity };
25129
25147
  }
25130
25148
  function isValidParamLocation(location2, isSwagger2) {
25131
25149
  const validLocations = isSwagger2 ? ["path", "query", "header", "body"] : ["path", "query", "header"];
@@ -25656,7 +25674,8 @@ async function generateResponseBody(schema, seed, onSchema, context) {
25656
25674
  }
25657
25675
  try {
25658
25676
  return await generateFromSchema({ schema: finalSchema, seed });
25659
- } catch {
25677
+ } catch (error) {
25678
+ console.warn("[@schmock/openapi] Response body generation failed:", error instanceof Error ? error.message : error);
25660
25679
  return {};
25661
25680
  }
25662
25681
  }
@@ -25870,7 +25889,15 @@ async function createCliServer(options) {
25870
25889
  collectBody(req, headers).then((body) => mock.handle(method, path, { headers, body, query }).then((schmockResponse) => {
25871
25890
  const extra = cors ? CORS_HEADERS : undefined;
25872
25891
  writeSchmockResponse(res, schmockResponse, extra);
25873
- }));
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
+ });
25874
25901
  });
25875
25902
  return new Promise((resolve, reject) => {
25876
25903
  httpServer.on("error", reject);
@@ -25888,6 +25915,13 @@ async function createCliServer(options) {
25888
25915
  });
25889
25916
  });
25890
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
+ }
25891
25925
  function parseCliArgs(args) {
25892
25926
  const { values, positionals } = parseArgs({
25893
25927
  args,
@@ -25910,7 +25944,7 @@ function parseCliArgs(args) {
25910
25944
  const spec = values.spec ?? positionals[0] ?? "";
25911
25945
  return {
25912
25946
  spec,
25913
- port: values.port ? Number(values.port) : undefined,
25947
+ port: values.port ? validatePort(values.port) : undefined,
25914
25948
  hostname: values.hostname,
25915
25949
  seed: values.seed,
25916
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": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",