@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 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,CA8NtF"}
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;AAanC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,YA6HjF"}
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
- if (value !== null) {
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;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAqIxD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,gMA8D3F"}
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(getExampleFromSchema(responseSchema, {
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;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAMnC,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;AA4DD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,CAAC,EAAE,WAAW,CAAC,eAAe,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAgD/B"}
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(getExampleFromSchema(responseSchema, {
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":"AAAA;;;;;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
+ {"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 && typeof subSchema === 'object' && predicate(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 && typeof subSchema === 'object') {
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 with the same `getExampleFromSchema` the HTTP mocker uses.
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":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAmB,MAAM,oBAAoB,CAAA;AAWvF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAwBhG"}
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 { getExampleFromSchema } from '@scalar/workspace-store/request-example';
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 with the same `getExampleFromSchema` the HTTP mocker uses.
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
- value = getExampleFromSchema(getResolvedRefDeep(message.payload), {
33
- emptyString: 'string',
34
- mode: 'read',
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): 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;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYlD;AAiBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAuCxE"}
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,QAwHpD"}
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;AAOhC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,QAmG7E"}
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.0",
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.12.7",
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",