@schmock/cli 2.0.2 → 2.1.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.
- package/dist/bin.js +74 -36
- package/dist/index.js +74 -36
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -23820,6 +23820,26 @@ function extractPathname(url) {
|
|
|
23820
23820
|
}
|
|
23821
23821
|
return urlWithoutQuery;
|
|
23822
23822
|
}
|
|
23823
|
+
function extractOrigin(url) {
|
|
23824
|
+
if (!url.includes("://"))
|
|
23825
|
+
return null;
|
|
23826
|
+
try {
|
|
23827
|
+
return new URL(url).origin;
|
|
23828
|
+
} catch {
|
|
23829
|
+
return null;
|
|
23830
|
+
}
|
|
23831
|
+
}
|
|
23832
|
+
function parseBaseUrl(baseUrl) {
|
|
23833
|
+
if (baseUrl.includes("://")) {
|
|
23834
|
+
try {
|
|
23835
|
+
const parsed = new URL(baseUrl);
|
|
23836
|
+
const rawPath = parsed.pathname;
|
|
23837
|
+
const path = rawPath === "/" ? "" : rawPath.replace(/\/$/, "");
|
|
23838
|
+
return { origin: parsed.origin, path };
|
|
23839
|
+
} catch {}
|
|
23840
|
+
}
|
|
23841
|
+
return { origin: null, path: baseUrl.replace(/\/$/, "") };
|
|
23842
|
+
}
|
|
23823
23843
|
function extractQuery(url) {
|
|
23824
23844
|
const queryStart = url.indexOf("?");
|
|
23825
23845
|
if (queryStart === -1)
|
|
@@ -23896,10 +23916,18 @@ function createFetchInterceptor(handle, options = {}) {
|
|
|
23896
23916
|
const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
|
|
23897
23917
|
const path = extractPathname(urlString);
|
|
23898
23918
|
if (baseUrl) {
|
|
23899
|
-
const
|
|
23900
|
-
|
|
23901
|
-
|
|
23902
|
-
|
|
23919
|
+
const { origin: baseOrigin, path: basePath } = parseBaseUrl(baseUrl);
|
|
23920
|
+
if (baseOrigin) {
|
|
23921
|
+
const reqOrigin = extractOrigin(urlString);
|
|
23922
|
+
if (reqOrigin !== baseOrigin) {
|
|
23923
|
+
return originalFetch(input, init);
|
|
23924
|
+
}
|
|
23925
|
+
}
|
|
23926
|
+
if (basePath) {
|
|
23927
|
+
const isMatch = path === basePath || path.startsWith(`${basePath}/`);
|
|
23928
|
+
if (!isMatch) {
|
|
23929
|
+
return originalFetch(input, init);
|
|
23930
|
+
}
|
|
23903
23931
|
}
|
|
23904
23932
|
}
|
|
23905
23933
|
const method = input instanceof Request ? input.method : init?.method ?? "GET";
|
|
@@ -23985,14 +24013,14 @@ function parseRouteKey(routeKey) {
|
|
|
23985
24013
|
}
|
|
23986
24014
|
const [, method, path] = match;
|
|
23987
24015
|
const params = [];
|
|
23988
|
-
const
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
|
|
23993
|
-
|
|
23994
|
-
|
|
23995
|
-
|
|
24016
|
+
const regexPath = path.split(/(:[a-zA-Z0-9_-]+)/g).map((segment) => {
|
|
24017
|
+
const paramMatch = segment.match(/^:([a-zA-Z0-9_-]+)$/);
|
|
24018
|
+
if (paramMatch) {
|
|
24019
|
+
params.push(paramMatch[1]);
|
|
24020
|
+
return "([^/]+)";
|
|
24021
|
+
}
|
|
24022
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24023
|
+
}).join("");
|
|
23996
24024
|
const pattern = new RegExp(`^${regexPath}$`);
|
|
23997
24025
|
return {
|
|
23998
24026
|
method: toHttpMethod(method),
|
|
@@ -24198,6 +24226,7 @@ class CallableMockInstance {
|
|
|
24198
24226
|
const existing = this.routes.find((r) => r.method === parsed.method && r.path === parsed.path);
|
|
24199
24227
|
if (existing) {
|
|
24200
24228
|
this.logger.log("warning", `Duplicate route: ${route} — first registration wins`);
|
|
24229
|
+
return this;
|
|
24201
24230
|
}
|
|
24202
24231
|
const compiledRoute = {
|
|
24203
24232
|
pattern: parsed.pattern,
|
|
@@ -24210,9 +24239,7 @@ class CallableMockInstance {
|
|
|
24210
24239
|
this.routes.push(compiledRoute);
|
|
24211
24240
|
if (parsed.params.length === 0) {
|
|
24212
24241
|
const key = `${parsed.method} ${normalizePath(parsed.path)}`;
|
|
24213
|
-
|
|
24214
|
-
this.staticRoutes.set(key, compiledRoute);
|
|
24215
|
-
}
|
|
24242
|
+
this.staticRoutes.set(key, compiledRoute);
|
|
24216
24243
|
}
|
|
24217
24244
|
this.logger.log("route", `Route defined: ${route}`, {
|
|
24218
24245
|
contentType: config.contentType,
|
|
@@ -24494,6 +24521,10 @@ class CallableMockInstance {
|
|
|
24494
24521
|
timestamp: Date.now(),
|
|
24495
24522
|
response: { status: response.status, body: response.body }
|
|
24496
24523
|
});
|
|
24524
|
+
const maxHistorySize = this.globalConfig.maxHistorySize;
|
|
24525
|
+
if (typeof maxHistorySize === "number" && maxHistorySize >= 0 && this.requestHistory.length > maxHistorySize) {
|
|
24526
|
+
this.requestHistory.splice(0, this.requestHistory.length - maxHistorySize);
|
|
24527
|
+
}
|
|
24497
24528
|
this.emit("request:end", {
|
|
24498
24529
|
method,
|
|
24499
24530
|
path: requestPath,
|
|
@@ -24576,7 +24607,7 @@ function schmock(config) {
|
|
|
24576
24607
|
return callableInstance;
|
|
24577
24608
|
}
|
|
24578
24609
|
// ../openapi/package.json
|
|
24579
|
-
var version = "2.0
|
|
24610
|
+
var version = "2.1.0";
|
|
24580
24611
|
|
|
24581
24612
|
// ../openapi/src/utils.ts
|
|
24582
24613
|
function isRecord(value) {
|
|
@@ -30005,19 +30036,21 @@ function validateSchema(schema, path = "$") {
|
|
|
30005
30036
|
validateSchema(schema.items, `${path}.items`);
|
|
30006
30037
|
}
|
|
30007
30038
|
}
|
|
30008
|
-
if (
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30039
|
+
if (path === "$") {
|
|
30040
|
+
if (hasCircularReference(schema)) {
|
|
30041
|
+
throw new SchemaValidationError(path, "Schema contains circular references which are not supported");
|
|
30042
|
+
}
|
|
30043
|
+
if (schema.$ref === "#") {
|
|
30044
|
+
throw new SchemaValidationError(path, "Self-referencing schemas are not supported");
|
|
30045
|
+
}
|
|
30046
|
+
const depth = calculateNestingDepth(schema);
|
|
30047
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
30048
|
+
throw new ResourceLimitError("schema_nesting_depth", MAX_NESTING_DEPTH, depth);
|
|
30049
|
+
}
|
|
30050
|
+
if (depth >= DEEP_NESTING_THRESHOLD) {
|
|
30051
|
+
checkForDeepNestingWithArrays(schema, path);
|
|
30052
|
+
}
|
|
30053
|
+
checkArraySizeLimits(schema, path);
|
|
30021
30054
|
}
|
|
30022
30055
|
}
|
|
30023
30056
|
function hasCircularReference(schema, currentPath = new Set) {
|
|
@@ -31636,17 +31669,21 @@ function processContentNegotiation(context) {
|
|
|
31636
31669
|
}
|
|
31637
31670
|
return;
|
|
31638
31671
|
}
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31672
|
+
function createBodyValidatorContext() {
|
|
31673
|
+
return {
|
|
31674
|
+
ajv: new import_ajv.default({ allErrors: true }),
|
|
31675
|
+
cache: new WeakMap
|
|
31676
|
+
};
|
|
31677
|
+
}
|
|
31678
|
+
function validateRequestBody(context, validatorCtx) {
|
|
31642
31679
|
const requestBodySchema = getRouteRequestBody(context.route);
|
|
31643
31680
|
if (!requestBodySchema || context.body === undefined) {
|
|
31644
31681
|
return;
|
|
31645
31682
|
}
|
|
31646
|
-
let validate =
|
|
31683
|
+
let validate = validatorCtx.cache.get(requestBodySchema);
|
|
31647
31684
|
if (!validate) {
|
|
31648
|
-
validate = ajv.compile(requestBodySchema);
|
|
31649
|
-
|
|
31685
|
+
validate = validatorCtx.ajv.compile(requestBodySchema);
|
|
31686
|
+
validatorCtx.cache.set(requestBodySchema, validate);
|
|
31650
31687
|
}
|
|
31651
31688
|
if (!validate(context.body)) {
|
|
31652
31689
|
const errors = validate.errors?.map((e3) => ({
|
|
@@ -31766,6 +31803,7 @@ async function openapi(options) {
|
|
|
31766
31803
|
}
|
|
31767
31804
|
const securitySchemes = spec.securitySchemes;
|
|
31768
31805
|
const globalSecurity = spec.globalSecurity;
|
|
31806
|
+
const bodyValidatorCtx = createBodyValidatorContext();
|
|
31769
31807
|
return {
|
|
31770
31808
|
name: "@schmock/openapi",
|
|
31771
31809
|
version,
|
|
@@ -31827,7 +31865,7 @@ async function openapi(options) {
|
|
|
31827
31865
|
if (contentResult)
|
|
31828
31866
|
return contentResult;
|
|
31829
31867
|
if (options.validateRequests) {
|
|
31830
|
-
const validationResult = validateRequestBody(context);
|
|
31868
|
+
const validationResult = validateRequestBody(context, bodyValidatorCtx);
|
|
31831
31869
|
if (validationResult) {
|
|
31832
31870
|
return validationResult;
|
|
31833
31871
|
}
|
package/dist/index.js
CHANGED
|
@@ -23819,6 +23819,26 @@ function extractPathname(url) {
|
|
|
23819
23819
|
}
|
|
23820
23820
|
return urlWithoutQuery;
|
|
23821
23821
|
}
|
|
23822
|
+
function extractOrigin(url) {
|
|
23823
|
+
if (!url.includes("://"))
|
|
23824
|
+
return null;
|
|
23825
|
+
try {
|
|
23826
|
+
return new URL(url).origin;
|
|
23827
|
+
} catch {
|
|
23828
|
+
return null;
|
|
23829
|
+
}
|
|
23830
|
+
}
|
|
23831
|
+
function parseBaseUrl(baseUrl) {
|
|
23832
|
+
if (baseUrl.includes("://")) {
|
|
23833
|
+
try {
|
|
23834
|
+
const parsed = new URL(baseUrl);
|
|
23835
|
+
const rawPath = parsed.pathname;
|
|
23836
|
+
const path = rawPath === "/" ? "" : rawPath.replace(/\/$/, "");
|
|
23837
|
+
return { origin: parsed.origin, path };
|
|
23838
|
+
} catch {}
|
|
23839
|
+
}
|
|
23840
|
+
return { origin: null, path: baseUrl.replace(/\/$/, "") };
|
|
23841
|
+
}
|
|
23822
23842
|
function extractQuery(url) {
|
|
23823
23843
|
const queryStart = url.indexOf("?");
|
|
23824
23844
|
if (queryStart === -1)
|
|
@@ -23895,10 +23915,18 @@ function createFetchInterceptor(handle, options = {}) {
|
|
|
23895
23915
|
const urlString = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
|
|
23896
23916
|
const path = extractPathname(urlString);
|
|
23897
23917
|
if (baseUrl) {
|
|
23898
|
-
const
|
|
23899
|
-
|
|
23900
|
-
|
|
23901
|
-
|
|
23918
|
+
const { origin: baseOrigin, path: basePath } = parseBaseUrl(baseUrl);
|
|
23919
|
+
if (baseOrigin) {
|
|
23920
|
+
const reqOrigin = extractOrigin(urlString);
|
|
23921
|
+
if (reqOrigin !== baseOrigin) {
|
|
23922
|
+
return originalFetch(input, init);
|
|
23923
|
+
}
|
|
23924
|
+
}
|
|
23925
|
+
if (basePath) {
|
|
23926
|
+
const isMatch = path === basePath || path.startsWith(`${basePath}/`);
|
|
23927
|
+
if (!isMatch) {
|
|
23928
|
+
return originalFetch(input, init);
|
|
23929
|
+
}
|
|
23902
23930
|
}
|
|
23903
23931
|
}
|
|
23904
23932
|
const method = input instanceof Request ? input.method : init?.method ?? "GET";
|
|
@@ -23984,14 +24012,14 @@ function parseRouteKey(routeKey) {
|
|
|
23984
24012
|
}
|
|
23985
24013
|
const [, method, path] = match;
|
|
23986
24014
|
const params = [];
|
|
23987
|
-
const
|
|
23988
|
-
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
|
|
23993
|
-
|
|
23994
|
-
|
|
24015
|
+
const regexPath = path.split(/(:[a-zA-Z0-9_-]+)/g).map((segment) => {
|
|
24016
|
+
const paramMatch = segment.match(/^:([a-zA-Z0-9_-]+)$/);
|
|
24017
|
+
if (paramMatch) {
|
|
24018
|
+
params.push(paramMatch[1]);
|
|
24019
|
+
return "([^/]+)";
|
|
24020
|
+
}
|
|
24021
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24022
|
+
}).join("");
|
|
23995
24023
|
const pattern = new RegExp(`^${regexPath}$`);
|
|
23996
24024
|
return {
|
|
23997
24025
|
method: toHttpMethod(method),
|
|
@@ -24197,6 +24225,7 @@ class CallableMockInstance {
|
|
|
24197
24225
|
const existing = this.routes.find((r) => r.method === parsed.method && r.path === parsed.path);
|
|
24198
24226
|
if (existing) {
|
|
24199
24227
|
this.logger.log("warning", `Duplicate route: ${route} — first registration wins`);
|
|
24228
|
+
return this;
|
|
24200
24229
|
}
|
|
24201
24230
|
const compiledRoute = {
|
|
24202
24231
|
pattern: parsed.pattern,
|
|
@@ -24209,9 +24238,7 @@ class CallableMockInstance {
|
|
|
24209
24238
|
this.routes.push(compiledRoute);
|
|
24210
24239
|
if (parsed.params.length === 0) {
|
|
24211
24240
|
const key = `${parsed.method} ${normalizePath(parsed.path)}`;
|
|
24212
|
-
|
|
24213
|
-
this.staticRoutes.set(key, compiledRoute);
|
|
24214
|
-
}
|
|
24241
|
+
this.staticRoutes.set(key, compiledRoute);
|
|
24215
24242
|
}
|
|
24216
24243
|
this.logger.log("route", `Route defined: ${route}`, {
|
|
24217
24244
|
contentType: config.contentType,
|
|
@@ -24493,6 +24520,10 @@ class CallableMockInstance {
|
|
|
24493
24520
|
timestamp: Date.now(),
|
|
24494
24521
|
response: { status: response.status, body: response.body }
|
|
24495
24522
|
});
|
|
24523
|
+
const maxHistorySize = this.globalConfig.maxHistorySize;
|
|
24524
|
+
if (typeof maxHistorySize === "number" && maxHistorySize >= 0 && this.requestHistory.length > maxHistorySize) {
|
|
24525
|
+
this.requestHistory.splice(0, this.requestHistory.length - maxHistorySize);
|
|
24526
|
+
}
|
|
24496
24527
|
this.emit("request:end", {
|
|
24497
24528
|
method,
|
|
24498
24529
|
path: requestPath,
|
|
@@ -24575,7 +24606,7 @@ function schmock(config) {
|
|
|
24575
24606
|
return callableInstance;
|
|
24576
24607
|
}
|
|
24577
24608
|
// ../openapi/package.json
|
|
24578
|
-
var version = "2.0
|
|
24609
|
+
var version = "2.1.0";
|
|
24579
24610
|
|
|
24580
24611
|
// ../openapi/src/utils.ts
|
|
24581
24612
|
function isRecord(value) {
|
|
@@ -30004,19 +30035,21 @@ function validateSchema(schema, path = "$") {
|
|
|
30004
30035
|
validateSchema(schema.items, `${path}.items`);
|
|
30005
30036
|
}
|
|
30006
30037
|
}
|
|
30007
|
-
if (
|
|
30008
|
-
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30038
|
+
if (path === "$") {
|
|
30039
|
+
if (hasCircularReference(schema)) {
|
|
30040
|
+
throw new SchemaValidationError(path, "Schema contains circular references which are not supported");
|
|
30041
|
+
}
|
|
30042
|
+
if (schema.$ref === "#") {
|
|
30043
|
+
throw new SchemaValidationError(path, "Self-referencing schemas are not supported");
|
|
30044
|
+
}
|
|
30045
|
+
const depth = calculateNestingDepth(schema);
|
|
30046
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
30047
|
+
throw new ResourceLimitError("schema_nesting_depth", MAX_NESTING_DEPTH, depth);
|
|
30048
|
+
}
|
|
30049
|
+
if (depth >= DEEP_NESTING_THRESHOLD) {
|
|
30050
|
+
checkForDeepNestingWithArrays(schema, path);
|
|
30051
|
+
}
|
|
30052
|
+
checkArraySizeLimits(schema, path);
|
|
30020
30053
|
}
|
|
30021
30054
|
}
|
|
30022
30055
|
function hasCircularReference(schema, currentPath = new Set) {
|
|
@@ -31635,17 +31668,21 @@ function processContentNegotiation(context) {
|
|
|
31635
31668
|
}
|
|
31636
31669
|
return;
|
|
31637
31670
|
}
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31671
|
+
function createBodyValidatorContext() {
|
|
31672
|
+
return {
|
|
31673
|
+
ajv: new import_ajv.default({ allErrors: true }),
|
|
31674
|
+
cache: new WeakMap
|
|
31675
|
+
};
|
|
31676
|
+
}
|
|
31677
|
+
function validateRequestBody(context, validatorCtx) {
|
|
31641
31678
|
const requestBodySchema = getRouteRequestBody(context.route);
|
|
31642
31679
|
if (!requestBodySchema || context.body === undefined) {
|
|
31643
31680
|
return;
|
|
31644
31681
|
}
|
|
31645
|
-
let validate =
|
|
31682
|
+
let validate = validatorCtx.cache.get(requestBodySchema);
|
|
31646
31683
|
if (!validate) {
|
|
31647
|
-
validate = ajv.compile(requestBodySchema);
|
|
31648
|
-
|
|
31684
|
+
validate = validatorCtx.ajv.compile(requestBodySchema);
|
|
31685
|
+
validatorCtx.cache.set(requestBodySchema, validate);
|
|
31649
31686
|
}
|
|
31650
31687
|
if (!validate(context.body)) {
|
|
31651
31688
|
const errors = validate.errors?.map((e3) => ({
|
|
@@ -31765,6 +31802,7 @@ async function openapi(options) {
|
|
|
31765
31802
|
}
|
|
31766
31803
|
const securitySchemes = spec.securitySchemes;
|
|
31767
31804
|
const globalSecurity = spec.globalSecurity;
|
|
31805
|
+
const bodyValidatorCtx = createBodyValidatorContext();
|
|
31768
31806
|
return {
|
|
31769
31807
|
name: "@schmock/openapi",
|
|
31770
31808
|
version,
|
|
@@ -31826,7 +31864,7 @@ async function openapi(options) {
|
|
|
31826
31864
|
if (contentResult)
|
|
31827
31865
|
return contentResult;
|
|
31828
31866
|
if (options.validateRequests) {
|
|
31829
|
-
const validationResult = validateRequestBody(context);
|
|
31867
|
+
const validationResult = validateRequestBody(context, bodyValidatorCtx);
|
|
31830
31868
|
if (validationResult) {
|
|
31831
31869
|
return validationResult;
|
|
31832
31870
|
}
|
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
|
|
4
|
+
"version": "2.1.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": "^2.0
|
|
36
|
-
"@schmock/openapi": "^2.0
|
|
35
|
+
"@schmock/core": "^2.1.0",
|
|
36
|
+
"@schmock/openapi": "^2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@amiceli/vitest-cucumber": "^6.5.0",
|