@scalar/mock-server 0.14.0 → 0.14.2
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/CHANGELOG.md +22 -0
- package/dist/create-mock-server.d.ts.map +1 -1
- package/dist/routes/mock-any-response.d.ts.map +1 -1
- package/dist/routes/mock-any-response.js +9 -9
- package/dist/routes/mock-handler-response.d.ts.map +1 -1
- package/dist/routes/mock-handler-response.js +2 -6
- package/dist/utils/build-handler-context.d.ts.map +1 -1
- package/dist/utils/build-handler-context.js +2 -6
- package/dist/utils/deserialize-parameter.d.ts.map +1 -1
- package/dist/utils/deserialize-parameter.js +3 -8
- package/dist/utils/generate-message.d.ts +1 -1
- package/dist/utils/generate-message.d.ts.map +1 -1
- package/dist/utils/generate-message.js +5 -6
- package/dist/utils/generate-response-example.d.ts +25 -0
- package/dist/utils/generate-response-example.d.ts.map +1 -0
- package/dist/utils/generate-response-example.js +27 -0
- package/dist/utils/get-oauth2-metadata.d.ts +14 -0
- package/dist/utils/get-oauth2-metadata.d.ts.map +1 -0
- package/dist/utils/get-oauth2-metadata.js +23 -0
- package/dist/utils/get-open-auth-token-urls.d.ts +5 -2
- package/dist/utils/get-open-auth-token-urls.d.ts.map +1 -1
- package/dist/utils/get-open-auth-token-urls.js +4 -3
- package/dist/utils/log-authentication-instructions.d.ts.map +1 -1
- package/dist/utils/log-authentication-instructions.js +7 -0
- package/dist/utils/set-up-authentication-routes.d.ts.map +1 -1
- package/dist/utils/set-up-authentication-routes.js +14 -4
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @scalar/mock-server
|
|
2
2
|
|
|
3
|
+
## 0.14.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#10179](https://github.com/scalar/scalar/pull/10179): Serve OAuth2 authorization server metadata at the declared oauth2MetadataUrl, advertising local mock endpoints and the configured grants and scopes.
|
|
8
|
+
|
|
9
|
+
Normalize absolute OAuth token URLs to route paths when registering mock authentication routes.
|
|
10
|
+
|
|
11
|
+
- [#10179](https://github.com/scalar/scalar/pull/10179): Warn when OAuth2 metadata routes collide with declared API paths. Keep the OAuth2 metadata field in OpenAPI 3.2 schemas and document the HTTP exception for local development.
|
|
12
|
+
|
|
13
|
+
## 0.14.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#10190](https://github.com/scalar/scalar/pull/10190): Update Hono to allow HTTP QUERY requests in default CORS preflight responses.
|
|
18
|
+
- [#10164](https://github.com/scalar/scalar/pull/10164): Serve deprecated response schemas from the mock instead of answering a declared JSON response with an empty body, and generate a deprecated AsyncAPI message payload instead of sending `null`. `getExampleFromSchema` takes a new `includeDeprecated` option for callers that must produce a value satisfying the schema. A declared response header that generates no value is now skipped rather than clearing a header of the same name the mock already set, such as the CORS headers.
|
|
19
|
+
- [#10140](https://github.com/scalar/scalar/pull/10140): Replace redundant type assertions with compiler-checked annotations, typed accumulators, and existing guards across helpers, API conversion, request handling, and schema rendering.
|
|
20
|
+
|
|
21
|
+
Narrow DOM elements and caught errors before accessing their properties. Correct header lookup to include missing values and handle them during PowerShell snippet generation.
|
|
22
|
+
|
|
23
|
+
Validate release-note provider responses, represent unresolved references and absent groups in helper return types, and require narrowing merged object values. Preserve AsyncAPI broker credentials separately from HTTP authentication schemes.
|
|
24
|
+
|
|
3
25
|
## 0.14.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,IAAI,EAA0B,MAAM,MAAM,CAAA;AAIjE,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAwD5D;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,IAAI,EAA0B,MAAM,MAAM,CAAA;AAIjE,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAwD5D;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2NtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAcnC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,YA4HjF"}
|
|
@@ -5,6 +5,7 @@ import { accepts } from 'hono/accepts';
|
|
|
5
5
|
import { streamSSE } from 'hono/streaming';
|
|
6
6
|
import { collectSseEvents, isEventStreamContentType } from '../utils/collect-sse-events.js';
|
|
7
7
|
import { findPreferredResponseKey } from '../utils/find-preferred-response-key.js';
|
|
8
|
+
import { generateResponseExample } from '../utils/generate-response-example.js';
|
|
8
9
|
import { normalizeResponseBody } from '../utils/normalize-response-body.js';
|
|
9
10
|
import { parsePreferHeader } from '../utils/parse-prefer-header.js';
|
|
10
11
|
import { pathParameters } from '../utils/path-parameters.js';
|
|
@@ -36,10 +37,15 @@ export function mockAnyResponse(c, operation) {
|
|
|
36
37
|
const headers = selectedResponse?.headers ?? {};
|
|
37
38
|
Object.keys(headers).forEach((header) => {
|
|
38
39
|
const headerObject = getResolvedRef(headers[header]);
|
|
40
|
+
// Headers need `includeDeprecated` but none of `generateResponseExample`'s other options — see
|
|
41
|
+
// the note on that helper for why passing them would change what declared headers emit.
|
|
39
42
|
const value = headerObject?.schema
|
|
40
|
-
? getExampleFromSchema(getResolvedRefDeep(headerObject.schema))
|
|
43
|
+
? getExampleFromSchema(getResolvedRefDeep(headerObject.schema), { includeDeprecated: true })
|
|
41
44
|
: null;
|
|
42
|
-
|
|
45
|
+
// Loose check on purpose: Hono *deletes* a header when handed `undefined`. This loop is the first
|
|
46
|
+
// thing to set the declared headers, so in practice a delete removes a header set earlier by
|
|
47
|
+
// middleware — `cors()` sets `Access-Control-Allow-Origin` before the handler runs.
|
|
48
|
+
if (value != null) {
|
|
43
49
|
c.header(header, value);
|
|
44
50
|
}
|
|
45
51
|
});
|
|
@@ -66,13 +72,7 @@ export function mockAnyResponse(c, operation) {
|
|
|
66
72
|
const acceptedResponse = selectedResponse?.content?.[acceptedContentType];
|
|
67
73
|
const responseSchema = acceptedResponse?.schema ? getResolvedRefDeep(acceptedResponse.schema) : undefined;
|
|
68
74
|
/** Generates the response body from the schema, or returns `undefined` when there is no schema. */
|
|
69
|
-
const generateFromSchema = () => responseSchema
|
|
70
|
-
? getExampleFromSchema(responseSchema, {
|
|
71
|
-
emptyString: 'string',
|
|
72
|
-
variables: pathParameters(c),
|
|
73
|
-
mode: 'read',
|
|
74
|
-
})
|
|
75
|
-
: undefined;
|
|
75
|
+
const generateFromSchema = () => responseSchema ? generateResponseExample(responseSchema, pathParameters(c)) : undefined;
|
|
76
76
|
// Server-Sent Events are a framed, multi-event wire format, so they cannot go out as one buffered
|
|
77
77
|
// body: a client reading the stream expects `data:` lines terminated by a blank line. Everything
|
|
78
78
|
// else (JSON, XML, text) keeps taking the single-body path below.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AA+HxD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,gMAyD3F"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
|
-
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
3
2
|
import { accepts } from 'hono/accepts';
|
|
4
3
|
import { buildHandlerContext } from '../utils/build-handler-context.js';
|
|
5
4
|
import { executeHandler } from '../utils/execute-handler.js';
|
|
5
|
+
import { generateResponseExample } from '../utils/generate-response-example.js';
|
|
6
6
|
import { normalizeResponseBody } from '../utils/normalize-response-body.js';
|
|
7
7
|
import { parsePreferHeader } from '../utils/parse-prefer-header.js';
|
|
8
8
|
import { pathParameters } from '../utils/path-parameters.js';
|
|
@@ -47,11 +47,7 @@ function getExampleFromResponse(c, statusCode, responses, exampleName) {
|
|
|
47
47
|
return selectedExample
|
|
48
48
|
? normalizeResponseBody(selectedExample.value, responseSchema)
|
|
49
49
|
: responseSchema
|
|
50
|
-
? normalizeResponseBody(
|
|
51
|
-
emptyString: 'string',
|
|
52
|
-
variables: pathParameters(c),
|
|
53
|
-
mode: 'read',
|
|
54
|
-
}), responseSchema)
|
|
50
|
+
? normalizeResponseBody(generateResponseExample(responseSchema, pathParameters(c)), responseSchema)
|
|
55
51
|
: null;
|
|
56
52
|
}
|
|
57
53
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-handler-context.d.ts","sourceRoot":"","sources":["../../src/utils/build-handler-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"build-handler-context.d.ts","sourceRoot":"","sources":["../../src/utils/build-handler-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAOnC,OAAO,EAAE,KAAK,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEjF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,cAAc,CAAC,CAAA;IAC5D,GAAG,EAAE;QACH,IAAI,EAAE,GAAG,CAAA;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,CAAA;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,sBAAsB,CAAA;CACjC,CAAA;AAqDD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,CAAC,EAAE,WAAW,CAAC,eAAe,GACtC,OAAO,CAAC,oBAAoB,CAAC,CA4C/B"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
2
|
import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
|
|
3
|
-
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
4
3
|
import { accepts } from 'hono/accepts';
|
|
5
4
|
import { store } from '../libs/store.js';
|
|
5
|
+
import { generateResponseExample } from './generate-response-example.js';
|
|
6
6
|
import { normalizeResponseBody } from './normalize-response-body.js';
|
|
7
7
|
import { pathParameters } from './path-parameters.js';
|
|
8
8
|
import { createStoreWrapper } from './store-wrapper.js';
|
|
@@ -40,11 +40,7 @@ function getExampleFromResponse(c, statusCode, responses) {
|
|
|
40
40
|
return acceptedResponse.example !== undefined
|
|
41
41
|
? normalizeResponseBody(acceptedResponse.example, responseSchema)
|
|
42
42
|
: responseSchema
|
|
43
|
-
? normalizeResponseBody(
|
|
44
|
-
emptyString: 'string',
|
|
45
|
-
variables: pathParameters(c),
|
|
46
|
-
mode: 'read',
|
|
47
|
-
}), responseSchema)
|
|
43
|
+
? normalizeResponseBody(generateResponseExample(responseSchema, pathParameters(c)), responseSchema)
|
|
48
44
|
: null;
|
|
49
45
|
}
|
|
50
46
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deserialize-parameter.d.ts","sourceRoot":"","sources":["../../src/utils/deserialize-parameter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deserialize-parameter.d.ts","sourceRoot":"","sources":["../../src/utils/deserialize-parameter.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,oFAAoF;AACpF,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAUtE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,UAAU,iBAAiB,EAC3B,QAAQ,MAAM,EACd,UAAU,OAAO,KAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAGnC,CAAA;AA2BD,8GAA8G;AAC9G,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAG,OAU3E,CAAA;AAED,gHAAgH;AAChH,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAG,OAU5E,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAG,MAAM,EA4B1F,CAAA;AAgGD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GAAI,oCAKvC;IACD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,iGAAiG;IACjG,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CAC7B,KAAG,MAAM,EAAE,GAAG,SAoBd,CAAA;AAgCD;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GAAI,qEAQxC;IACD,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,+FAA+F;IAC/F,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,CAAA;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,mGAAmG;IACnG,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IACpC;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAA;CACvC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,SAoEvC,CAAA"}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Helpers for turning string-encoded request parameters back into the structured values that JSON
|
|
3
|
-
* Schema validation expects, following the OpenAPI `style`/`explode` serialization rules.
|
|
4
|
-
*
|
|
5
|
-
* @see https://spec.openapis.org/oas/v3.1.1.html#style-values
|
|
6
|
-
*/
|
|
1
|
+
import { isObjectLike } from '@scalar/helpers/object/is-object';
|
|
7
2
|
/** Default serialization style per parameter location. */
|
|
8
3
|
const DEFAULT_STYLE = {
|
|
9
4
|
query: 'form',
|
|
@@ -34,7 +29,7 @@ const matchesComposedSchema = (schema, predicate) => {
|
|
|
34
29
|
const subSchemas = schema[keyword];
|
|
35
30
|
if (Array.isArray(subSchemas)) {
|
|
36
31
|
for (const subSchema of subSchemas) {
|
|
37
|
-
if (subSchema &&
|
|
32
|
+
if (isObjectLike(subSchema) && predicate(subSchema)) {
|
|
38
33
|
return true;
|
|
39
34
|
}
|
|
40
35
|
}
|
|
@@ -87,7 +82,7 @@ export const getObjectPropertyNames = (schema) => {
|
|
|
87
82
|
const subSchemas = schema[keyword];
|
|
88
83
|
if (Array.isArray(subSchemas)) {
|
|
89
84
|
for (const subSchema of subSchemas) {
|
|
90
|
-
if (subSchema
|
|
85
|
+
if (isObjectLike(subSchema)) {
|
|
91
86
|
for (const name of getObjectPropertyNames(subSchema)) {
|
|
92
87
|
names.add(name);
|
|
93
88
|
}
|
|
@@ -2,7 +2,7 @@ import type { MockMessage, ResolvedChannel } from '../transports/types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Generate an encoded mock frame for a channel message — the AsyncAPI analogue of the REST
|
|
4
4
|
* mocker's response generation. Prefers a defined example, otherwise generates a value from the
|
|
5
|
-
* message payload schema
|
|
5
|
+
* message payload schema through the same `generateResponseExample` the HTTP mocker uses.
|
|
6
6
|
*
|
|
7
7
|
* @param channel - The resolved channel to mock a message for.
|
|
8
8
|
* @param messageId - Which message to emit; defaults to the channel's first message.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-message.d.ts","sourceRoot":"","sources":["../../src/utils/generate-message.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate-message.d.ts","sourceRoot":"","sources":["../../src/utils/generate-message.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAmB,MAAM,oBAAoB,CAAA;AAYvF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAuBhG"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
|
|
2
|
-
import {
|
|
2
|
+
import { generateResponseExample } from '../utils/generate-response-example.js';
|
|
3
3
|
/** Encode a generated value to a wire string. Strings pass through; everything else is JSON. */
|
|
4
4
|
function encode(value) {
|
|
5
5
|
if (typeof value === 'string') {
|
|
@@ -10,7 +10,7 @@ function encode(value) {
|
|
|
10
10
|
/**
|
|
11
11
|
* Generate an encoded mock frame for a channel message — the AsyncAPI analogue of the REST
|
|
12
12
|
* mocker's response generation. Prefers a defined example, otherwise generates a value from the
|
|
13
|
-
* message payload schema
|
|
13
|
+
* message payload schema through the same `generateResponseExample` the HTTP mocker uses.
|
|
14
14
|
*
|
|
15
15
|
* @param channel - The resolved channel to mock a message for.
|
|
16
16
|
* @param messageId - Which message to emit; defaults to the channel's first message.
|
|
@@ -29,10 +29,9 @@ export function generateMessage(channel, messageId) {
|
|
|
29
29
|
value = message.examples[0];
|
|
30
30
|
}
|
|
31
31
|
else if (message.payload) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
32
|
+
// No `variables`: `generateMessage` is never handed the Hono context, so a channel route's path
|
|
33
|
+
// parameters are not in scope for `x-variable` substitution. It does run per request.
|
|
34
|
+
value = generateResponseExample(getResolvedRefDeep(message.payload));
|
|
36
35
|
}
|
|
37
36
|
return {
|
|
38
37
|
data: encode(value),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
2
|
+
/** The schema shape `getExampleFromSchema` accepts, so callers do not have to name it themselves. */
|
|
3
|
+
export type ExampleSchema = Parameters<typeof getExampleFromSchema>[0];
|
|
4
|
+
/**
|
|
5
|
+
* Generate a mocked response value from a schema the mock has declared.
|
|
6
|
+
*
|
|
7
|
+
* Every value the mock puts on the wire — an HTTP body, an SSE frame, a channel message — has to
|
|
8
|
+
* satisfy the schema it was declared with, which is why this passes `includeDeprecated: true`:
|
|
9
|
+
* `deprecated` marks a field as discouraged, not absent, so omitting it answered a declared JSON
|
|
10
|
+
* response with zero bytes and dropped required properties. Owning the option set in one place keeps
|
|
11
|
+
* a newly added response path from quietly missing that.
|
|
12
|
+
*
|
|
13
|
+
* Not for response headers. Two of the options here would change what an already-declared header
|
|
14
|
+
* emits: `emptyString` yields the literal `string` for a bare `type: 'string'` and switches on
|
|
15
|
+
* format-based generation (a `format: 'date-time'` header would emit a fabricated timestamp), and
|
|
16
|
+
* `mode: 'read'` drops a `writeOnly` header entirely. Headers set `includeDeprecated` on their own
|
|
17
|
+
* call instead.
|
|
18
|
+
*
|
|
19
|
+
* @param schema - The resolved schema to generate a value for.
|
|
20
|
+
* @param variables - Values for `x-variable` substitution, usually a request's path parameters.
|
|
21
|
+
* Omitted by callers that have no request in scope, such as channel messages.
|
|
22
|
+
* @returns The generated value, or `undefined` when the schema yields nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare const generateResponseExample: (schema: ExampleSchema, variables?: Record<string, unknown>) => unknown;
|
|
25
|
+
//# sourceMappingURL=generate-response-example.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-response-example.d.ts","sourceRoot":"","sources":["../../src/utils/generate-response-example.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAA;AAE9E,qGAAqG;AACrG,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,aAAa,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAMjG,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a mocked response value from a schema the mock has declared.
|
|
4
|
+
*
|
|
5
|
+
* Every value the mock puts on the wire — an HTTP body, an SSE frame, a channel message — has to
|
|
6
|
+
* satisfy the schema it was declared with, which is why this passes `includeDeprecated: true`:
|
|
7
|
+
* `deprecated` marks a field as discouraged, not absent, so omitting it answered a declared JSON
|
|
8
|
+
* response with zero bytes and dropped required properties. Owning the option set in one place keeps
|
|
9
|
+
* a newly added response path from quietly missing that.
|
|
10
|
+
*
|
|
11
|
+
* Not for response headers. Two of the options here would change what an already-declared header
|
|
12
|
+
* emits: `emptyString` yields the literal `string` for a bare `type: 'string'` and switches on
|
|
13
|
+
* format-based generation (a `format: 'date-time'` header would emit a fabricated timestamp), and
|
|
14
|
+
* `mode: 'read'` drops a `writeOnly` header entirely. Headers set `includeDeprecated` on their own
|
|
15
|
+
* call instead.
|
|
16
|
+
*
|
|
17
|
+
* @param schema - The resolved schema to generate a value for.
|
|
18
|
+
* @param variables - Values for `x-variable` substitution, usually a request's path parameters.
|
|
19
|
+
* Omitted by callers that have no request in scope, such as channel messages.
|
|
20
|
+
* @returns The generated value, or `undefined` when the schema yields nothing.
|
|
21
|
+
*/
|
|
22
|
+
export const generateResponseExample = (schema, variables) => getExampleFromSchema(schema, {
|
|
23
|
+
emptyString: 'string',
|
|
24
|
+
variables,
|
|
25
|
+
mode: 'read',
|
|
26
|
+
includeDeprecated: true,
|
|
27
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OpenAPIV3_2 } from '@scalar/openapi-types';
|
|
2
|
+
/** The RFC8414 fields exposed by the mock authorization server. */
|
|
3
|
+
type OAuth2Metadata = {
|
|
4
|
+
issuer: string;
|
|
5
|
+
authorization_endpoint?: string;
|
|
6
|
+
token_endpoint?: string;
|
|
7
|
+
response_types_supported: string[];
|
|
8
|
+
grant_types_supported: string[];
|
|
9
|
+
scopes_supported: string[];
|
|
10
|
+
};
|
|
11
|
+
/** Advertises local mock endpoints instead of sending clients to the real authorization server. */
|
|
12
|
+
export declare const getOAuth2Metadata: (flows: OpenAPIV3_2.OAuth2SecurityScheme["flows"], origin: string) => OAuth2Metadata;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=get-oauth2-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-oauth2-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/get-oauth2-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,mEAAmE;AACnE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wBAAwB,EAAE,MAAM,EAAE,CAAA;IAClC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAA;AAED,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,GAAI,OAAO,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,QAAQ,MAAM,KAAG,cAqBpG,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getPathFromUrl } from './get-open-auth-token-urls.js';
|
|
2
|
+
/** Advertises local mock endpoints instead of sending clients to the real authorization server. */
|
|
3
|
+
export const getOAuth2Metadata = (flows, origin) => {
|
|
4
|
+
const authorizationFlow = flows?.authorizationCode ?? flows?.implicit;
|
|
5
|
+
const tokenFlow = flows?.authorizationCode ?? flows?.clientCredentials ?? flows?.password;
|
|
6
|
+
const localUrl = (url) => new URL(getPathFromUrl(url), origin).href;
|
|
7
|
+
const supportedFlows = [
|
|
8
|
+
{ flow: flows?.authorizationCode, grant: 'authorization_code', response: 'code' },
|
|
9
|
+
{ flow: flows?.implicit, grant: 'implicit', response: 'token' },
|
|
10
|
+
{ flow: flows?.clientCredentials, grant: 'client_credentials' },
|
|
11
|
+
{ flow: flows?.password, grant: 'password' },
|
|
12
|
+
].filter(({ flow }) => flow);
|
|
13
|
+
return {
|
|
14
|
+
issuer: origin,
|
|
15
|
+
...(authorizationFlow
|
|
16
|
+
? { authorization_endpoint: localUrl(authorizationFlow.authorizationUrl ?? '/oauth/authorize') }
|
|
17
|
+
: {}),
|
|
18
|
+
...(tokenFlow ? { token_endpoint: localUrl(tokenFlow.tokenUrl ?? '/oauth/token') } : {}),
|
|
19
|
+
response_types_supported: supportedFlows.flatMap(({ response }) => (response ? [response] : [])),
|
|
20
|
+
grant_types_supported: supportedFlows.map(({ grant }) => grant),
|
|
21
|
+
scopes_supported: [...new Set(supportedFlows.flatMap(({ flow }) => Object.keys(flow?.scopes ?? {})))],
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { OpenAPI } from '@scalar/openapi-types';
|
|
2
2
|
/**
|
|
3
|
-
* Extract path from URL
|
|
3
|
+
* Extract path from URL. Metadata is fetched at its exact declared URL, so its routes
|
|
4
|
+
* preserve trailing slashes instead of using the token-route normalization.
|
|
4
5
|
*/
|
|
5
|
-
export declare function getPathFromUrl(url: string
|
|
6
|
+
export declare function getPathFromUrl(url: string, { preserveTrailingSlash }?: {
|
|
7
|
+
preserveTrailingSlash?: boolean;
|
|
8
|
+
}): string;
|
|
6
9
|
export declare function getOpenAuthTokenUrls(schema?: OpenAPI.Document): string[];
|
|
7
10
|
//# sourceMappingURL=get-open-auth-token-urls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-open-auth-token-urls.d.ts","sourceRoot":"","sources":["../../src/utils/get-open-auth-token-urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAG5E
|
|
1
|
+
{"version":3,"file":"get-open-auth-token-urls.d.ts","sourceRoot":"","sources":["../../src/utils/get-open-auth-token-urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAG5E;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,EAAE,qBAA6B,EAAE,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1E,MAAM,CAYR;AAiBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAuCxE"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
2
|
/**
|
|
3
|
-
* Extract path from URL
|
|
3
|
+
* Extract path from URL. Metadata is fetched at its exact declared URL, so its routes
|
|
4
|
+
* preserve trailing slashes instead of using the token-route normalization.
|
|
4
5
|
*/
|
|
5
|
-
export function getPathFromUrl(url) {
|
|
6
|
+
export function getPathFromUrl(url, { preserveTrailingSlash = false } = {}) {
|
|
6
7
|
try {
|
|
7
8
|
// Handle relative URLs by prepending a base
|
|
8
9
|
const urlObject = url.startsWith('http') ? new URL(url) : new URL(url, 'http://example.com');
|
|
9
10
|
// Normalize: remove trailing slash except for root path
|
|
10
11
|
const path = urlObject.pathname;
|
|
11
|
-
return path === '/' ? path : path.replace(/\/$/, '');
|
|
12
|
+
return preserveTrailingSlash || path === '/' ? path : path.replace(/\/$/, '');
|
|
12
13
|
}
|
|
13
14
|
catch {
|
|
14
15
|
// If URL is invalid, return the original string
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-authentication-instructions.d.ts","sourceRoot":"","sources":["../../src/utils/log-authentication-instructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,oBAAoB,CAAC,EACjE,GAAG,GAAE,gBAA8C,
|
|
1
|
+
{"version":3,"file":"log-authentication-instructions.d.ts","sourceRoot":"","sources":["../../src/utils/log-authentication-instructions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,oBAAoB,CAAC,EACjE,GAAG,GAAE,gBAA8C,QAiIpD"}
|
|
@@ -66,6 +66,13 @@ export function logAuthenticationInstructions(securitySchemes, log = (line) => c
|
|
|
66
66
|
}
|
|
67
67
|
break;
|
|
68
68
|
case 'oauth2':
|
|
69
|
+
if ('oauth2MetadataUrl' in scheme &&
|
|
70
|
+
typeof scheme.oauth2MetadataUrl === 'string' &&
|
|
71
|
+
scheme.oauth2MetadataUrl.trim()) {
|
|
72
|
+
log('✅ OAuth 2.0 Authorization Server Metadata');
|
|
73
|
+
log(` GET ${getPathFromUrl(scheme.oauth2MetadataUrl, { preserveTrailingSlash: true })}`);
|
|
74
|
+
log('');
|
|
75
|
+
}
|
|
69
76
|
if (scheme.flows) {
|
|
70
77
|
Object.keys(scheme.flows).forEach((flow) => {
|
|
71
78
|
switch (flow) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-up-authentication-routes.d.ts","sourceRoot":"","sources":["../../src/utils/set-up-authentication-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"set-up-authentication-routes.d.ts","sourceRoot":"","sources":["../../src/utils/set-up-authentication-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAE5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQhC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,QA+G7E"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
2
|
import { respondWithAuthorizePage } from '../routes/respond-with-authorize-page.js';
|
|
3
3
|
import { respondWithToken } from '../routes/respond-with-token.js';
|
|
4
|
+
import { getOAuth2Metadata } from './get-oauth2-metadata.js';
|
|
4
5
|
import { getOpenAuthTokenUrls, getPathFromUrl } from './get-open-auth-token-urls.js';
|
|
5
6
|
/**
|
|
6
7
|
* Helper function to set up authentication routes for OAuth 2.0 flows
|
|
@@ -36,11 +37,20 @@ export function setUpAuthenticationRoutes(app, schema) {
|
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
if (scheme.type === 'oauth2') {
|
|
40
|
+
if ('oauth2MetadataUrl' in scheme &&
|
|
41
|
+
typeof scheme.oauth2MetadataUrl === 'string' &&
|
|
42
|
+
scheme.oauth2MetadataUrl.trim()) {
|
|
43
|
+
const metadataPath = getPathFromUrl(scheme.oauth2MetadataUrl, { preserveTrailingSlash: true });
|
|
44
|
+
if (schema?.paths && Object.hasOwn(schema.paths, metadataPath)) {
|
|
45
|
+
console.warn(`OAuth2 metadata route "${metadataPath}" collides with a declared API path.`);
|
|
46
|
+
}
|
|
47
|
+
app.get(metadataPath, (c) => c.json(getOAuth2Metadata(scheme.flows, new URL(c.req.url).origin)));
|
|
48
|
+
}
|
|
39
49
|
if (scheme.flows?.authorizationCode) {
|
|
40
50
|
const authorizeRoute = scheme.flows.authorizationCode.authorizationUrl ?? '/oauth/authorize';
|
|
41
51
|
const tokenRoute = scheme.flows.authorizationCode.tokenUrl ?? '/oauth/token';
|
|
42
52
|
authorizeUrls.add(getPathFromUrl(authorizeRoute));
|
|
43
|
-
tokenUrls.add(tokenRoute);
|
|
53
|
+
tokenUrls.add(getPathFromUrl(tokenRoute));
|
|
44
54
|
}
|
|
45
55
|
if (scheme.flows?.implicit) {
|
|
46
56
|
const authorizeRoute = scheme.flows.implicit.authorizationUrl ?? '/oauth/authorize';
|
|
@@ -48,11 +58,11 @@ export function setUpAuthenticationRoutes(app, schema) {
|
|
|
48
58
|
}
|
|
49
59
|
if (scheme.flows?.password) {
|
|
50
60
|
const tokenRoute = scheme.flows.password.tokenUrl ?? '/oauth/token';
|
|
51
|
-
tokenUrls.add(tokenRoute);
|
|
61
|
+
tokenUrls.add(getPathFromUrl(tokenRoute));
|
|
52
62
|
}
|
|
53
63
|
if (scheme.flows?.clientCredentials) {
|
|
54
64
|
const tokenRoute = scheme.flows.clientCredentials.tokenUrl ?? '/oauth/token';
|
|
55
|
-
tokenUrls.add(tokenRoute);
|
|
65
|
+
tokenUrls.add(getPathFromUrl(tokenRoute));
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
68
|
else if (scheme.type === 'openIdConnect') {
|
|
@@ -74,7 +84,7 @@ export function setUpAuthenticationRoutes(app, schema) {
|
|
|
74
84
|
const authorizeRoute = '/oauth/authorize';
|
|
75
85
|
const tokenRoute = '/oauth/token';
|
|
76
86
|
authorizeUrls.add(getPathFromUrl(authorizeRoute));
|
|
77
|
-
tokenUrls.add(tokenRoute);
|
|
87
|
+
tokenUrls.add(getPathFromUrl(tokenRoute));
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
});
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"swagger",
|
|
17
17
|
"cli"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.14.
|
|
19
|
+
"version": "0.14.2",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -53,17 +53,17 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@faker-js/faker": "10.4.0",
|
|
55
55
|
"@hono/node-ws": "^1.2.0",
|
|
56
|
+
"@scalar/helpers": "0.12.0",
|
|
57
|
+
"@scalar/json-magic": "0.14.0",
|
|
58
|
+
"@scalar/openapi-types": "0.9.6",
|
|
59
|
+
"@scalar/openapi-upgrader": "0.2.17",
|
|
60
|
+
"@scalar/types": "0.20.0",
|
|
61
|
+
"@scalar/workspace-store": "0.62.0",
|
|
56
62
|
"ajv": "^8.20.0",
|
|
57
63
|
"ajv-formats": "^3.0.1",
|
|
58
|
-
"hono": "^4.
|
|
64
|
+
"hono": "^4.13.7",
|
|
59
65
|
"quickjs-emscripten": "0.32.0",
|
|
60
|
-
"yaml": "^2.9.0"
|
|
61
|
-
"@scalar/helpers": "0.11.3",
|
|
62
|
-
"@scalar/json-magic": "0.13.4",
|
|
63
|
-
"@scalar/openapi-types": "0.9.5",
|
|
64
|
-
"@scalar/openapi-upgrader": "0.2.15",
|
|
65
|
-
"@scalar/types": "0.19.0",
|
|
66
|
-
"@scalar/workspace-store": "0.60.0"
|
|
66
|
+
"yaml": "^2.9.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^24.1.0",
|