@scalar/mock-server 0.2.14 → 0.2.15

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/createMockServer.d.ts +13 -0
  3. package/dist/createMockServer.d.ts.map +1 -0
  4. package/dist/createMockServer.js +122 -0
  5. package/dist/index.d.ts +3 -32
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +4 -120
  8. package/dist/utils/anyBasicAuthentication.d.ts +6 -0
  9. package/dist/utils/anyBasicAuthentication.d.ts.map +1 -0
  10. package/dist/utils/anyBasicAuthentication.js +25 -0
  11. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.d.ts +6 -0
  12. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.d.ts.map +1 -0
  13. package/dist/utils/anyOpenAuthPasswordGrantAuthentication.js +22 -0
  14. package/dist/utils/findPreferredResponseKey.d.ts +5 -0
  15. package/dist/utils/findPreferredResponseKey.d.ts.map +1 -0
  16. package/dist/utils/findPreferredResponseKey.js +8 -0
  17. package/dist/utils/getOpenAuthTokenUrl.d.ts +3 -0
  18. package/dist/utils/getOpenAuthTokenUrl.d.ts.map +1 -0
  19. package/dist/utils/getOpenAuthTokenUrl.js +21 -0
  20. package/dist/utils/index.d.ts +4 -0
  21. package/dist/utils/index.d.ts.map +1 -0
  22. package/dist/utils/isAuthenticationRequired.d.ts +6 -0
  23. package/dist/utils/isAuthenticationRequired.d.ts.map +1 -0
  24. package/dist/utils/isAuthenticationRequired.js +20 -0
  25. package/dist/utils/isBasicAuthenticationRequired.d.ts +3 -0
  26. package/dist/utils/isBasicAuthenticationRequired.d.ts.map +1 -0
  27. package/dist/utils/isBasicAuthenticationRequired.js +13 -0
  28. package/dist/utils/isOpenAuthPasswordGrantRequired.d.ts +3 -0
  29. package/dist/utils/isOpenAuthPasswordGrantRequired.d.ts.map +1 -0
  30. package/dist/utils/isOpenAuthPasswordGrantRequired.js +14 -0
  31. package/dist/utils/routeFromPath.d.ts +6 -0
  32. package/dist/utils/routeFromPath.d.ts.map +1 -0
  33. package/dist/utils/routeFromPath.js +9 -0
  34. package/package.json +12 -13
  35. package/dist/index.cjs +0 -125
  36. package/dist/index.cjs.map +0 -1
  37. package/dist/index.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @scalar/mock-server
2
2
 
3
+ ## 0.2.15
4
+
5
+ ### Patch Changes
6
+
7
+ - 6d2cfe8: chore: update build setup
8
+ - 3f219f4: feat: add OpenAuth2 token endpoint
9
+ - 3f219f4: feat: check whether OpenAuth2 password grant authentication is required
10
+
3
11
  ## 0.2.14
4
12
 
5
13
  ### Patch Changes
@@ -0,0 +1,13 @@
1
+ import { type OpenAPI } from '@scalar/openapi-parser';
2
+ import { type Context, Hono } from 'hono';
3
+ /**
4
+ * Create a mock server instance
5
+ */
6
+ export declare function createMockServer(options?: {
7
+ specification: string | Record<string, any>;
8
+ onRequest?: (data: {
9
+ context: Context;
10
+ operation: OpenAPI.Operation;
11
+ }) => void;
12
+ }): Promise<Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">>;
13
+ //# sourceMappingURL=createMockServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createMockServer.d.ts","sourceRoot":"","sources":["../src/createMockServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAezC;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAC/C,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAA;KAAE,KAAK,IAAI,CAAA;CAC/E,uFAiJA"}
@@ -0,0 +1,122 @@
1
+ import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
2
+ import { openapi } from '@scalar/openapi-parser';
3
+ import { Hono } from 'hono';
4
+ import { cors } from 'hono/cors';
5
+ import { anyBasicAuthentication } from './utils/anyBasicAuthentication.js';
6
+ import { anyOpenAuthPasswordGrantAuthentication } from './utils/anyOpenAuthPasswordGrantAuthentication.js';
7
+ import { getOpenAuthTokenUrl } from './utils/getOpenAuthTokenUrl.js';
8
+ import { isBasicAuthenticationRequired } from './utils/isBasicAuthenticationRequired.js';
9
+ import { isOpenAuthPasswordGrantRequired } from './utils/isOpenAuthPasswordGrantRequired.js';
10
+ import { routeFromPath } from './utils/routeFromPath.js';
11
+ import { isAuthenticationRequired } from './utils/isAuthenticationRequired.js';
12
+ import { findPreferredResponseKey } from './utils/findPreferredResponseKey.js';
13
+
14
+ /**
15
+ * Create a mock server instance
16
+ */
17
+ async function createMockServer(options) {
18
+ const app = new Hono();
19
+ // Resolve references
20
+ const result = await openapi()
21
+ .load(options?.specification ?? {})
22
+ .dereference()
23
+ .get();
24
+ // CORS headers
25
+ app.use(cors());
26
+ // OpenAPI JSON file
27
+ app.get('/openapi.json', async (c) => {
28
+ if (!options?.specification) {
29
+ return c.text('Not found', 404);
30
+ }
31
+ const { specification } = await openapi().load(options.specification).get();
32
+ return c.json(specification);
33
+ });
34
+ // OpenAPI YAML file
35
+ app.get('/openapi.yaml', async (c) => {
36
+ if (!options?.specification) {
37
+ return c.text('Not found', 404);
38
+ }
39
+ const specification = await openapi().load(options.specification).toYaml();
40
+ return c.text(specification);
41
+ });
42
+ // OpenAuth2 token endpoint
43
+ const tokenUrl = getOpenAuthTokenUrl(result?.schema);
44
+ if (typeof tokenUrl === 'string') {
45
+ app.post(tokenUrl, async (c) => {
46
+ return c.json({
47
+ access_token: 'super-secret-token',
48
+ token_type: 'Bearer',
49
+ expires_in: 3600,
50
+ refresh_token: 'secret-refresh-token',
51
+ }, 200,
52
+ /**
53
+ * When responding with an access token, the server must also include the additional Cache-Control: no-store
54
+ * HTTP header to ensure clients do not cache this request.
55
+ * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
56
+ */
57
+ {
58
+ 'Cache-Control': 'no-store',
59
+ });
60
+ });
61
+ }
62
+ // Paths
63
+ Object.keys(result.schema?.paths ?? {}).forEach((path) => {
64
+ // Operations
65
+ Object.keys(result.schema?.paths?.[path] ?? {}).forEach((method) => {
66
+ const route = routeFromPath(path);
67
+ // @ts-expect-error Needs a proper type
68
+ const operation = result.schema?.paths?.[path]?.[method];
69
+ // Check if authentication is required
70
+ const requiresAuthentication = isAuthenticationRequired(operation.security);
71
+ // Check whether we need basic authentication
72
+ const requiresBasicAuthentication = isBasicAuthenticationRequired(operation, result?.schema);
73
+ // Add HTTP basic authentication
74
+ if (requiresAuthentication && requiresBasicAuthentication) {
75
+ // @ts-expect-error Needs a proper type
76
+ app[method](route, anyBasicAuthentication());
77
+ }
78
+ // Check whether we need OpenAuth password grant authentication
79
+ const requiresOpenAuthPasswordGrant = isOpenAuthPasswordGrantRequired(operation, result?.schema);
80
+ // Add HTTP basic authentication
81
+ if (requiresAuthentication && requiresOpenAuthPasswordGrant) {
82
+ // @ts-expect-error Needs a proper type
83
+ app[method](route, anyOpenAuthPasswordGrantAuthentication());
84
+ }
85
+ // Route
86
+ // @ts-expect-error Needs a proper type
87
+ app[method](route, (c) => {
88
+ // Call onRequest callback
89
+ if (options?.onRequest) {
90
+ options.onRequest({
91
+ context: c,
92
+ operation,
93
+ });
94
+ }
95
+ // Response
96
+ // default, 200, 201 …
97
+ const preferredResponseKey = findPreferredResponseKey(Object.keys(operation.responses ?? {}));
98
+ // Focus on JSON for now
99
+ const jsonResponse = preferredResponseKey
100
+ ? operation.responses?.[preferredResponseKey]?.content?.['application/json']
101
+ : null;
102
+ // Get or generate JSON
103
+ const response = jsonResponse?.example
104
+ ? jsonResponse.example
105
+ : jsonResponse?.schema
106
+ ? getExampleFromSchema(jsonResponse.schema, {
107
+ emptyString: '…',
108
+ variables: c.req.param(),
109
+ })
110
+ : null;
111
+ // Status code
112
+ const statusCode = parseInt(preferredResponseKey === 'default'
113
+ ? '200'
114
+ : preferredResponseKey ?? '200', 10);
115
+ return c.json(response, statusCode);
116
+ });
117
+ });
118
+ });
119
+ return app;
120
+ }
121
+
122
+ export { createMockServer };
package/dist/index.d.ts CHANGED
@@ -1,32 +1,3 @@
1
- import * as hono_types from 'hono/types';
2
- import { OpenAPI, OpenAPIV3 } from '@scalar/openapi-parser';
3
- import { Context, Hono } from 'hono';
4
-
5
- /**
6
- * Create a mock server instance
7
- */
8
- declare function createMockServer(options?: {
9
- specification: string | Record<string, any>;
10
- onRequest?: (data: {
11
- context: Context;
12
- operation: OpenAPI.Operation;
13
- }) => void;
14
- }): Promise<Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">>;
15
-
16
- /**
17
- * Find the preferred response key: default, 200, 201 …
18
- */
19
- declare function findPreferredResponseKey(responses?: string[]): string | undefined;
20
-
21
- /**
22
- * Convert path to route
23
- * Example: /posts/{id} -> /posts/:id
24
- */
25
- declare function routeFromPath(path: string): string;
26
-
27
- /**
28
- * Check whether the given security scheme key is in the `security` configuration for this operation.
29
- **/
30
- declare function isAuthenticationRequired(security?: OpenAPIV3.SecurityRequirementObject[]): boolean;
31
-
32
- export { createMockServer, findPreferredResponseKey, isAuthenticationRequired, routeFromPath };
1
+ export * from './createMockServer.js';
2
+ export * from './utils';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,SAAS,CAAA"}
package/dist/index.js CHANGED
@@ -1,120 +1,4 @@
1
- import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
2
- import { openapi } from '@scalar/openapi-parser';
3
- import { Hono } from 'hono';
4
- import { cors } from 'hono/cors';
5
- import { HTTPException } from 'hono/http-exception';
6
-
7
- function findPreferredResponseKey(responses) {
8
- return ["default", "200", "201", "204", "404", "500"].find(
9
- (key) => responses?.includes(key) ?? false
10
- );
11
- }
12
-
13
- function routeFromPath(path) {
14
- return path.replace(/{/g, ":").replace(/}/g, "");
15
- }
16
-
17
- function isAuthenticationRequired(security) {
18
- if (!security) {
19
- return false;
20
- }
21
- if (Array.isArray(security) && !security.length) {
22
- return false;
23
- }
24
- if ((security ?? []).some(
25
- (securityRequirement) => !Object.keys(securityRequirement).length
26
- )) {
27
- return false;
28
- }
29
- return true;
30
- }
31
-
32
- function anyBasicAuthentication() {
33
- return async function(ctx, next) {
34
- if (ctx.req.header("Authorization")?.startsWith("Basic ")) {
35
- return await next();
36
- }
37
- throw new HTTPException(401, {
38
- res: new Response("Unauthorized", {
39
- status: 401,
40
- headers: {
41
- "WWW-Authenticate": 'Basic realm="Authentication Required"'
42
- }
43
- })
44
- });
45
- };
46
- }
47
-
48
- function isBasicAuthenticationRequired(operation, schema) {
49
- const allowedSecuritySchemes = operation.security?.map(
50
- (securityScheme) => {
51
- return Object.keys(securityScheme)[0];
52
- }
53
- );
54
- const httpBasicAuthIsRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
55
- const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
56
- return securityScheme?.type === "http" && securityScheme?.scheme === "basic";
57
- }) !== void 0;
58
- return httpBasicAuthIsRequired;
59
- }
60
-
61
- async function createMockServer(options) {
62
- const app = new Hono();
63
- const result = await openapi().load(options?.specification ?? {}).dereference().get();
64
- app.use(cors());
65
- app.get("/openapi.json", async (c) => {
66
- if (!options?.specification) {
67
- return c.text("Not found", 404);
68
- }
69
- const { specification } = await openapi().load(options.specification).get();
70
- return c.json(specification);
71
- });
72
- app.get("/openapi.yaml", async (c) => {
73
- if (!options?.specification) {
74
- return c.text("Not found", 404);
75
- }
76
- const specification = await openapi().load(options.specification).toYaml();
77
- return c.text(specification);
78
- });
79
- Object.keys(result.schema?.paths ?? {}).forEach((path) => {
80
- Object.keys(result.schema?.paths?.[path] ?? {}).forEach((method) => {
81
- const route = routeFromPath(path);
82
- const operation = result.schema?.paths?.[path]?.[method];
83
- const requiresAuthentication = isAuthenticationRequired(
84
- operation.security
85
- );
86
- const requiresBasicAuthentication = isBasicAuthenticationRequired(
87
- operation,
88
- result?.schema
89
- );
90
- if (requiresAuthentication && requiresBasicAuthentication) {
91
- app[method](route, anyBasicAuthentication());
92
- }
93
- app[method](route, (c) => {
94
- if (options?.onRequest) {
95
- options.onRequest({
96
- context: c,
97
- operation
98
- });
99
- }
100
- const preferredResponseKey = findPreferredResponseKey(
101
- Object.keys(operation.responses ?? {})
102
- );
103
- const jsonResponse = preferredResponseKey ? operation.responses?.[preferredResponseKey]?.content?.["application/json"] : null;
104
- const response = jsonResponse?.example ? jsonResponse.example : jsonResponse?.schema ? getExampleFromSchema(jsonResponse.schema, {
105
- emptyString: "\u2026",
106
- variables: c.req.param()
107
- }) : null;
108
- const statusCode = parseInt(
109
- preferredResponseKey === "default" ? "200" : preferredResponseKey ?? "200",
110
- 10
111
- );
112
- return c.json(response, statusCode);
113
- });
114
- });
115
- });
116
- return app;
117
- }
118
-
119
- export { createMockServer, findPreferredResponseKey, isAuthenticationRequired, routeFromPath };
120
- //# sourceMappingURL=index.js.map
1
+ export { createMockServer } from './createMockServer.js';
2
+ export { findPreferredResponseKey } from './utils/findPreferredResponseKey.js';
3
+ export { routeFromPath } from './utils/routeFromPath.js';
4
+ export { isAuthenticationRequired } from './utils/isAuthenticationRequired.js';
@@ -0,0 +1,6 @@
1
+ import type { Context } from 'hono';
2
+ /**
3
+ * Middleware to check for any basic authentication header
4
+ */
5
+ export declare function anyBasicAuthentication(): (ctx: Context, next: () => Promise<void>) => Promise<void>;
6
+ //# sourceMappingURL=anyBasicAuthentication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anyBasicAuthentication.d.ts","sourceRoot":"","sources":["../../src/utils/anyBasicAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC;;GAEG;AACH,wBAAgB,sBAAsB,UACR,OAAO,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC,mBAiB/D"}
@@ -0,0 +1,25 @@
1
+ import { HTTPException } from 'hono/http-exception';
2
+
3
+ /**
4
+ * Middleware to check for any basic authentication header
5
+ */
6
+ function anyBasicAuthentication() {
7
+ return async function (ctx, next) {
8
+ // Check if the request has an Authorization header
9
+ // Note: We don’t care *what* credentials are sent, though.
10
+ if (ctx.req.header('Authorization')?.startsWith('Basic ')) {
11
+ return await next();
12
+ }
13
+ // Unauthorized
14
+ throw new HTTPException(401, {
15
+ res: new Response('Unauthorized', {
16
+ status: 401,
17
+ headers: {
18
+ 'WWW-Authenticate': 'Basic realm="Authentication Required"',
19
+ },
20
+ }),
21
+ });
22
+ };
23
+ }
24
+
25
+ export { anyBasicAuthentication };
@@ -0,0 +1,6 @@
1
+ import type { Context } from 'hono';
2
+ /**
3
+ * Middleware to check for any bearer authentication header
4
+ */
5
+ export declare function anyOpenAuthPasswordGrantAuthentication(): (ctx: Context, next: () => Promise<void>) => Promise<void>;
6
+ //# sourceMappingURL=anyOpenAuthPasswordGrantAuthentication.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anyOpenAuthPasswordGrantAuthentication.d.ts","sourceRoot":"","sources":["../../src/utils/anyOpenAuthPasswordGrantAuthentication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC;;GAEG;AACH,wBAAgB,sCAAsC,UACxB,OAAO,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC,mBAc/D"}
@@ -0,0 +1,22 @@
1
+ import { HTTPException } from 'hono/http-exception';
2
+
3
+ /**
4
+ * Middleware to check for any bearer authentication header
5
+ */
6
+ function anyOpenAuthPasswordGrantAuthentication() {
7
+ return async function (ctx, next) {
8
+ // Check if the request has an Authorization header
9
+ // Note: We don’t care *what* credentials are sent, though.
10
+ if (ctx.req.header('Authorization')?.startsWith('Bearer ')) {
11
+ return await next();
12
+ }
13
+ // Unauthorized
14
+ throw new HTTPException(401, {
15
+ res: new Response('Unauthorized', {
16
+ status: 401,
17
+ }),
18
+ });
19
+ };
20
+ }
21
+
22
+ export { anyOpenAuthPasswordGrantAuthentication };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Find the preferred response key: default, 200, 201 …
3
+ */
4
+ export declare function findPreferredResponseKey(responses?: string[]): string | undefined;
5
+ //# sourceMappingURL=findPreferredResponseKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findPreferredResponseKey.d.ts","sourceRoot":"","sources":["../../src/utils/findPreferredResponseKey.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,sBAI5D"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Find the preferred response key: default, 200, 201 …
3
+ */
4
+ function findPreferredResponseKey(responses) {
5
+ return ['default', '200', '201', '204', '404', '500'].find((key) => responses?.includes(key) ?? false);
6
+ }
7
+
8
+ export { findPreferredResponseKey };
@@ -0,0 +1,3 @@
1
+ import type { OpenAPI } from '@scalar/openapi-parser';
2
+ export declare function getOpenAuthTokenUrl(schema?: OpenAPI.Document): string | false | undefined;
3
+ //# sourceMappingURL=getOpenAuthTokenUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getOpenAuthTokenUrl.d.ts","sourceRoot":"","sources":["../../src/utils/getOpenAuthTokenUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,wBAAwB,CAAA;AAE7E,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,8BAgC5D"}
@@ -0,0 +1,21 @@
1
+ function getOpenAuthTokenUrl(schema) {
2
+ const securitySchemes = schema?.components?.securitySchemes;
3
+ if (securitySchemes === undefined) {
4
+ return false;
5
+ }
6
+ // TODO: Make this work with other OpenAuth workflows
7
+ const openAuthPasswordGrant = Object.values(securitySchemes).filter((securityScheme) => {
8
+ if (securityScheme.type === 'oauth2' &&
9
+ securityScheme.flows?.password !== undefined) {
10
+ return true;
11
+ }
12
+ return false;
13
+ });
14
+ if (!openAuthPasswordGrant.length) {
15
+ return undefined;
16
+ }
17
+ // @ts-expect-error TypeScript, I know it’s there (or undefined, both is fine).
18
+ return openAuthPasswordGrant[0]?.flows?.password?.tokenUrl;
19
+ }
20
+
21
+ export { getOpenAuthTokenUrl };
@@ -0,0 +1,4 @@
1
+ export * from './findPreferredResponseKey.js';
2
+ export * from './routeFromPath.js';
3
+ export * from './isAuthenticationRequired.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,4BAA4B,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { OpenAPIV3 } from '@scalar/openapi-parser';
2
+ /**
3
+ * Check whether the given security scheme key is in the `security` configuration for this operation.
4
+ **/
5
+ export declare function isAuthenticationRequired(security?: OpenAPIV3.SecurityRequirementObject[]): boolean;
6
+ //# sourceMappingURL=isAuthenticationRequired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isAuthenticationRequired.d.ts","sourceRoot":"","sources":["../../src/utils/isAuthenticationRequired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD;;IAEI;AACJ,wBAAgB,wBAAwB,CACtC,QAAQ,CAAC,EAAE,SAAS,CAAC,yBAAyB,EAAE,GAC/C,OAAO,CAqBT"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Check whether the given security scheme key is in the `security` configuration for this operation.
3
+ **/
4
+ function isAuthenticationRequired(security) {
5
+ // If security is not defined, auth is not required.
6
+ if (!security) {
7
+ return false;
8
+ }
9
+ // Don’t require auth if security is just an empty array []
10
+ if (Array.isArray(security) && !security.length) {
11
+ return false;
12
+ }
13
+ // Includes empty object = auth is not required
14
+ if ((security ?? []).some((securityRequirement) => !Object.keys(securityRequirement).length)) {
15
+ return false;
16
+ }
17
+ return true;
18
+ }
19
+
20
+ export { isAuthenticationRequired };
@@ -0,0 +1,3 @@
1
+ import type { OpenAPI } from '@scalar/openapi-parser';
2
+ export declare function isBasicAuthenticationRequired(operation: OpenAPI.Operation, schema?: OpenAPI.Document): boolean;
3
+ //# sourceMappingURL=isBasicAuthenticationRequired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isBasicAuthenticationRequired.d.ts","sourceRoot":"","sources":["../../src/utils/isBasicAuthenticationRequired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,wBAAwB,CAAA;AAE7E,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,WAwB1B"}
@@ -0,0 +1,13 @@
1
+ function isBasicAuthenticationRequired(operation, schema) {
2
+ const allowedSecuritySchemes = operation.security?.map((securityScheme) => {
3
+ return Object.keys(securityScheme)[0];
4
+ });
5
+ // Check if one of them is HTTP Basic Auth
6
+ const httpBasicAuthIsRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
7
+ const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
8
+ return (securityScheme?.type === 'http' && securityScheme?.scheme === 'basic');
9
+ }) >= 0;
10
+ return httpBasicAuthIsRequired;
11
+ }
12
+
13
+ export { isBasicAuthenticationRequired };
@@ -0,0 +1,3 @@
1
+ import type { OpenAPI } from '@scalar/openapi-parser';
2
+ export declare function isOpenAuthPasswordGrantRequired(operation: OpenAPI.Operation, schema?: OpenAPI.Document): boolean;
3
+ //# sourceMappingURL=isOpenAuthPasswordGrantRequired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isOpenAuthPasswordGrantRequired.d.ts","sourceRoot":"","sources":["../../src/utils/isOpenAuthPasswordGrantRequired.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,wBAAwB,CAAA;AAE7E,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,WAyB1B"}
@@ -0,0 +1,14 @@
1
+ function isOpenAuthPasswordGrantRequired(operation, schema) {
2
+ const allowedSecuritySchemes = operation.security?.map((securityScheme) => {
3
+ return Object.keys(securityScheme)[0];
4
+ });
5
+ // Check if one of them is OpenAuth2 Password Grant
6
+ const passwordGrantRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
7
+ const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
8
+ return (securityScheme?.type === 'oauth2' &&
9
+ securityScheme?.flows?.password !== undefined);
10
+ }) >= 0;
11
+ return passwordGrantRequired;
12
+ }
13
+
14
+ export { isOpenAuthPasswordGrantRequired };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Convert path to route
3
+ * Example: /posts/{id} -> /posts/:id
4
+ */
5
+ export declare function routeFromPath(path: string): string;
6
+ //# sourceMappingURL=routeFromPath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeFromPath.d.ts","sourceRoot":"","sources":["../../src/utils/routeFromPath.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,UAEzC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Convert path to route
3
+ * Example: /posts/{id} -> /posts/:id
4
+ */
5
+ function routeFromPath(path) {
6
+ return path.replace(/{/g, ':').replace(/}/g, '');
7
+ }
8
+
9
+ export { routeFromPath };
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "swagger",
17
17
  "cli"
18
18
  ],
19
- "version": "0.2.14",
19
+ "version": "0.2.15",
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
@@ -24,7 +24,10 @@
24
24
  "main": "dist/index.js",
25
25
  "types": "dist/index.d.ts",
26
26
  "exports": {
27
- "import": "./dist/index.js"
27
+ ".": {
28
+ "import": "./dist/index.js",
29
+ "types": "./dist/index.d.ts"
30
+ }
28
31
  },
29
32
  "files": [
30
33
  "./dist",
@@ -32,26 +35,22 @@
32
35
  ],
33
36
  "module": "dist/index.js",
34
37
  "dependencies": {
35
- "@hono/node-server": "^1.11.0",
36
38
  "@scalar/openapi-parser": "^0.7.2",
37
39
  "hono": "^4.2.7",
38
40
  "@scalar/oas-utils": "0.2.14"
39
41
  },
40
42
  "devDependencies": {
43
+ "@hono/node-server": "^1.11.0",
41
44
  "@types/node": "^20.14.10",
42
- "esbuild": "^0.21.1",
43
- "rollup": "^4.16.4",
44
- "rollup-plugin-delete": "^2.0.0",
45
- "rollup-plugin-dts": "^6.1.0",
46
- "rollup-plugin-esbuild": "^6.1.1",
47
- "tslib": "^2.6.2",
48
45
  "vite-node": "^1.3.1",
49
- "@scalar/galaxy": "0.2.4",
50
- "@scalar/build-tooling": "0.1.9"
46
+ "@scalar/build-tooling": "0.1.9",
47
+ "@scalar/galaxy": "0.2.4"
51
48
  },
52
49
  "scripts": {
53
- "build": "rm -Rf dist/ && rollup -c",
50
+ "build": "scalar-build-rollup",
51
+ "dev": "nodemon --exec \"vite-node playground/index.ts\" --ext ts --quiet",
54
52
  "test": "vitest",
55
- "types:check": "tsc --noEmit --skipLibCheck"
53
+ "types:build": "scalar-types-build",
54
+ "types:check": "scalar-types-check"
56
55
  }
57
56
  }
package/dist/index.cjs DELETED
@@ -1,125 +0,0 @@
1
- 'use strict';
2
-
3
- var specGetters = require('@scalar/oas-utils/spec-getters');
4
- var openapiParser = require('@scalar/openapi-parser');
5
- var hono = require('hono');
6
- var cors = require('hono/cors');
7
- var httpException = require('hono/http-exception');
8
-
9
- function findPreferredResponseKey(responses) {
10
- return ["default", "200", "201", "204", "404", "500"].find(
11
- (key) => responses?.includes(key) ?? false
12
- );
13
- }
14
-
15
- function routeFromPath(path) {
16
- return path.replace(/{/g, ":").replace(/}/g, "");
17
- }
18
-
19
- function isAuthenticationRequired(security) {
20
- if (!security) {
21
- return false;
22
- }
23
- if (Array.isArray(security) && !security.length) {
24
- return false;
25
- }
26
- if ((security ?? []).some(
27
- (securityRequirement) => !Object.keys(securityRequirement).length
28
- )) {
29
- return false;
30
- }
31
- return true;
32
- }
33
-
34
- function anyBasicAuthentication() {
35
- return async function(ctx, next) {
36
- if (ctx.req.header("Authorization")?.startsWith("Basic ")) {
37
- return await next();
38
- }
39
- throw new httpException.HTTPException(401, {
40
- res: new Response("Unauthorized", {
41
- status: 401,
42
- headers: {
43
- "WWW-Authenticate": 'Basic realm="Authentication Required"'
44
- }
45
- })
46
- });
47
- };
48
- }
49
-
50
- function isBasicAuthenticationRequired(operation, schema) {
51
- const allowedSecuritySchemes = operation.security?.map(
52
- (securityScheme) => {
53
- return Object.keys(securityScheme)[0];
54
- }
55
- );
56
- const httpBasicAuthIsRequired = allowedSecuritySchemes?.findIndex((securitySchemeKey) => {
57
- const securityScheme = schema?.components?.securitySchemes?.[securitySchemeKey];
58
- return securityScheme?.type === "http" && securityScheme?.scheme === "basic";
59
- }) !== void 0;
60
- return httpBasicAuthIsRequired;
61
- }
62
-
63
- async function createMockServer(options) {
64
- const app = new hono.Hono();
65
- const result = await openapiParser.openapi().load(options?.specification ?? {}).dereference().get();
66
- app.use(cors.cors());
67
- app.get("/openapi.json", async (c) => {
68
- if (!options?.specification) {
69
- return c.text("Not found", 404);
70
- }
71
- const { specification } = await openapiParser.openapi().load(options.specification).get();
72
- return c.json(specification);
73
- });
74
- app.get("/openapi.yaml", async (c) => {
75
- if (!options?.specification) {
76
- return c.text("Not found", 404);
77
- }
78
- const specification = await openapiParser.openapi().load(options.specification).toYaml();
79
- return c.text(specification);
80
- });
81
- Object.keys(result.schema?.paths ?? {}).forEach((path) => {
82
- Object.keys(result.schema?.paths?.[path] ?? {}).forEach((method) => {
83
- const route = routeFromPath(path);
84
- const operation = result.schema?.paths?.[path]?.[method];
85
- const requiresAuthentication = isAuthenticationRequired(
86
- operation.security
87
- );
88
- const requiresBasicAuthentication = isBasicAuthenticationRequired(
89
- operation,
90
- result?.schema
91
- );
92
- if (requiresAuthentication && requiresBasicAuthentication) {
93
- app[method](route, anyBasicAuthentication());
94
- }
95
- app[method](route, (c) => {
96
- if (options?.onRequest) {
97
- options.onRequest({
98
- context: c,
99
- operation
100
- });
101
- }
102
- const preferredResponseKey = findPreferredResponseKey(
103
- Object.keys(operation.responses ?? {})
104
- );
105
- const jsonResponse = preferredResponseKey ? operation.responses?.[preferredResponseKey]?.content?.["application/json"] : null;
106
- const response = jsonResponse?.example ? jsonResponse.example : jsonResponse?.schema ? specGetters.getExampleFromSchema(jsonResponse.schema, {
107
- emptyString: "\u2026",
108
- variables: c.req.param()
109
- }) : null;
110
- const statusCode = parseInt(
111
- preferredResponseKey === "default" ? "200" : preferredResponseKey ?? "200",
112
- 10
113
- );
114
- return c.json(response, statusCode);
115
- });
116
- });
117
- });
118
- return app;
119
- }
120
-
121
- exports.createMockServer = createMockServer;
122
- exports.findPreferredResponseKey = findPreferredResponseKey;
123
- exports.isAuthenticationRequired = isAuthenticationRequired;
124
- exports.routeFromPath = routeFromPath;
125
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/utils/findPreferredResponseKey.ts","../src/utils/routeFromPath.ts","../src/utils/isAuthenticationRequired.ts","../src/utils/anyBasicAuthentication.ts","../src/utils/isBasicAuthenticationRequired.ts","../src/createMockServer.ts"],"sourcesContent":["/**\n * Find the preferred response key: default, 200, 201 …\n */\nexport function findPreferredResponseKey(responses?: string[]) {\n return ['default', '200', '201', '204', '404', '500'].find(\n (key) => responses?.includes(key) ?? false,\n )\n}\n","/**\n * Convert path to route\n * Example: /posts/{id} -> /posts/:id\n */\nexport function routeFromPath(path: string) {\n return path.replace(/{/g, ':').replace(/}/g, '')\n}\n","import type { OpenAPIV3 } from '@scalar/openapi-parser'\n\n/**\n * Check whether the given security scheme key is in the `security` configuration for this operation.\n **/\nexport function isAuthenticationRequired(\n security?: OpenAPIV3.SecurityRequirementObject[],\n): boolean {\n // If security is not defined, auth is not required.\n if (!security) {\n return false\n }\n\n // Don’t require auth if security is just an empty array []\n if (Array.isArray(security) && !security.length) {\n return false\n }\n\n // Includes empty object = auth is not required\n if (\n (security ?? []).some(\n (securityRequirement) => !Object.keys(securityRequirement).length,\n )\n ) {\n return false\n }\n\n return true\n}\n","import type { Context } from 'hono'\nimport { HTTPException } from 'hono/http-exception'\n\n/**\n * Middleware to check for any basic authentication header\n */\nexport function anyBasicAuthentication() {\n return async function (ctx: Context, next: () => Promise<void>) {\n // Check if the request has an Authorization header\n // Note: We don’t care *what* credentials are sent, though.\n if (ctx.req.header('Authorization')?.startsWith('Basic ')) {\n return await next()\n }\n\n // Unauthorized\n throw new HTTPException(401, {\n res: new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': 'Basic realm=\"Authentication Required\"',\n },\n }),\n })\n }\n}\n","import type { OpenAPI, OpenAPIV3_1 } from '@scalar/openapi-parser'\n\nexport function isBasicAuthenticationRequired(\n operation: OpenAPI.Operation,\n schema?: OpenAPI.Document,\n) {\n const allowedSecuritySchemes = operation.security?.map(\n (securityScheme: OpenAPIV3_1.SecurityRequirementObject) => {\n return Object.keys(securityScheme)[0]\n },\n )\n // Check if one of them is HTTP Basic Auth\n const httpBasicAuthIsRequired =\n allowedSecuritySchemes?.findIndex((securitySchemeKey: string) => {\n const securityScheme =\n schema?.components?.securitySchemes?.[securitySchemeKey]\n\n return (\n securityScheme?.type === 'http' && securityScheme?.scheme === 'basic'\n )\n }) !== undefined\n return httpBasicAuthIsRequired\n}\n","import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters'\nimport { type OpenAPI, openapi } from '@scalar/openapi-parser'\nimport { type Context, Hono } from 'hono'\nimport { cors } from 'hono/cors'\nimport type { StatusCode } from 'hono/utils/http-status'\n\nimport {\n findPreferredResponseKey,\n isAuthenticationRequired,\n routeFromPath,\n} from './utils'\nimport { anyBasicAuthentication } from './utils/anyBasicAuthentication'\nimport { isBasicAuthenticationRequired } from './utils/isBasicAuthenticationRequired'\n\n/**\n * Create a mock server instance\n */\nexport async function createMockServer(options?: {\n specification: string | Record<string, any>\n onRequest?: (data: { context: Context; operation: OpenAPI.Operation }) => void\n}) {\n const app = new Hono()\n\n // Resolve references\n const result = await openapi()\n .load(options?.specification ?? {})\n .dereference()\n .get()\n\n // CORS headers\n app.use(cors())\n\n // OpenAPI JSON file\n app.get('/openapi.json', async (c) => {\n if (!options?.specification) {\n return c.text('Not found', 404)\n }\n\n const { specification } = await openapi().load(options.specification).get()\n\n return c.json(specification)\n })\n\n // OpenAPI YAML file\n app.get('/openapi.yaml', async (c) => {\n if (!options?.specification) {\n return c.text('Not found', 404)\n }\n\n const specification = await openapi().load(options.specification).toYaml()\n\n return c.text(specification)\n })\n\n // Paths\n Object.keys(result.schema?.paths ?? {}).forEach((path) => {\n // Operations\n Object.keys(result.schema?.paths?.[path] ?? {}).forEach((method) => {\n const route = routeFromPath(path)\n\n // @ts-expect-error Needs a proper type\n const operation = result.schema?.paths?.[path]?.[method]\n\n // Check if authentication is required\n const requiresAuthentication = isAuthenticationRequired(\n operation.security,\n )\n // Get all available authentication methods\n const requiresBasicAuthentication = isBasicAuthenticationRequired(\n operation,\n result?.schema,\n )\n\n // Add HTTP basic authentication\n if (requiresAuthentication && requiresBasicAuthentication) {\n // @ts-expect-error Needs a proper type\n app[method](route, anyBasicAuthentication())\n }\n\n // Route\n // @ts-expect-error Needs a proper type\n app[method](route, (c: Context) => {\n // Call onRequest callback\n if (options?.onRequest) {\n options.onRequest({\n context: c,\n operation,\n })\n }\n\n // Response\n // default, 200, 201 …\n const preferredResponseKey = findPreferredResponseKey(\n Object.keys(operation.responses ?? {}),\n )\n\n // Focus on JSON for now\n const jsonResponse = preferredResponseKey\n ? operation.responses?.[preferredResponseKey]?.content?.[\n 'application/json'\n ]\n : null\n\n // Get or generate JSON\n const response = jsonResponse?.example\n ? jsonResponse.example\n : jsonResponse?.schema\n ? getExampleFromSchema(jsonResponse.schema, {\n emptyString: '…',\n variables: c.req.param(),\n })\n : null\n\n // Status code\n const statusCode = parseInt(\n preferredResponseKey === 'default'\n ? '200'\n : preferredResponseKey ?? '200',\n 10,\n ) as StatusCode\n\n return c.json(response, statusCode)\n })\n })\n })\n\n return app\n}\n"],"names":["HTTPException","Hono","openapi","cors","getExampleFromSchema"],"mappings":";;;;;;;;AAGO,SAAS,yBAAyB,SAAsB,EAAA;AAC7D,EAAA,OAAO,CAAC,SAAW,EAAA,KAAA,EAAO,OAAO,KAAO,EAAA,KAAA,EAAO,KAAK,CAAE,CAAA,IAAA;AAAA,IACpD,CAAC,GAAA,KAAQ,SAAW,EAAA,QAAA,CAAS,GAAG,CAAK,IAAA,KAAA;AAAA,GACvC,CAAA;AACF;;ACHO,SAAS,cAAc,IAAc,EAAA;AAC1C,EAAA,OAAO,KAAK,OAAQ,CAAA,IAAA,EAAM,GAAG,CAAE,CAAA,OAAA,CAAQ,MAAM,EAAE,CAAA,CAAA;AACjD;;ACDO,SAAS,yBACd,QACS,EAAA;AAET,EAAA,IAAI,CAAC,QAAU,EAAA;AACb,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAGA,EAAA,IAAI,MAAM,OAAQ,CAAA,QAAQ,CAAK,IAAA,CAAC,SAAS,MAAQ,EAAA;AAC/C,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAGA,EACG,IAAA,CAAA,QAAA,IAAY,EAAI,EAAA,IAAA;AAAA,IACf,CAAC,mBAAwB,KAAA,CAAC,MAAO,CAAA,IAAA,CAAK,mBAAmB,CAAE,CAAA,MAAA;AAAA,GAE7D,EAAA;AACA,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAEA,EAAO,OAAA,IAAA,CAAA;AACT;;ACtBO,SAAS,sBAAyB,GAAA;AACvC,EAAO,OAAA,eAAgB,KAAc,IAA2B,EAAA;AAG9D,IAAA,IAAI,IAAI,GAAI,CAAA,MAAA,CAAO,eAAe,CAAG,EAAA,UAAA,CAAW,QAAQ,CAAG,EAAA;AACzD,MAAA,OAAO,MAAM,IAAK,EAAA,CAAA;AAAA,KACpB;AAGA,IAAM,MAAA,IAAIA,4BAAc,GAAK,EAAA;AAAA,MAC3B,GAAA,EAAK,IAAI,QAAA,CAAS,cAAgB,EAAA;AAAA,QAChC,MAAQ,EAAA,GAAA;AAAA,QACR,OAAS,EAAA;AAAA,UACP,kBAAoB,EAAA,uCAAA;AAAA,SACtB;AAAA,OACD,CAAA;AAAA,KACF,CAAA,CAAA;AAAA,GACH,CAAA;AACF;;ACtBgB,SAAA,6BAAA,CACd,WACA,MACA,EAAA;AACA,EAAM,MAAA,sBAAA,GAAyB,UAAU,QAAU,EAAA,GAAA;AAAA,IACjD,CAAC,cAA0D,KAAA;AACzD,MAAA,OAAO,MAAO,CAAA,IAAA,CAAK,cAAc,CAAA,CAAE,CAAC,CAAA,CAAA;AAAA,KACtC;AAAA,GACF,CAAA;AAEA,EAAA,MAAM,uBACJ,GAAA,sBAAA,EAAwB,SAAU,CAAA,CAAC,iBAA8B,KAAA;AAC/D,IAAA,MAAM,cACJ,GAAA,MAAA,EAAQ,UAAY,EAAA,eAAA,GAAkB,iBAAiB,CAAA,CAAA;AAEzD,IAAA,OACE,cAAgB,EAAA,IAAA,KAAS,MAAU,IAAA,cAAA,EAAgB,MAAW,KAAA,OAAA,CAAA;AAAA,GAEjE,CAAM,KAAA,KAAA,CAAA,CAAA;AACT,EAAO,OAAA,uBAAA,CAAA;AACT;;ACLA,eAAsB,iBAAiB,OAGpC,EAAA;AACD,EAAM,MAAA,GAAA,GAAM,IAAIC,SAAK,EAAA,CAAA;AAGrB,EAAA,MAAM,MAAS,GAAA,MAAMC,qBAAQ,EAAA,CAC1B,IAAK,CAAA,OAAA,EAAS,aAAiB,IAAA,EAAE,CAAA,CACjC,WAAY,EAAA,CACZ,GAAI,EAAA,CAAA;AAGP,EAAI,GAAA,CAAA,GAAA,CAAIC,WAAM,CAAA,CAAA;AAGd,EAAI,GAAA,CAAA,GAAA,CAAI,eAAiB,EAAA,OAAO,CAAM,KAAA;AACpC,IAAI,IAAA,CAAC,SAAS,aAAe,EAAA;AAC3B,MAAO,OAAA,CAAA,CAAE,IAAK,CAAA,WAAA,EAAa,GAAG,CAAA,CAAA;AAAA,KAChC;AAEA,IAAM,MAAA,EAAE,aAAc,EAAA,GAAI,MAAMD,qBAAA,GAAU,IAAK,CAAA,OAAA,CAAQ,aAAa,CAAA,CAAE,GAAI,EAAA,CAAA;AAE1E,IAAO,OAAA,CAAA,CAAE,KAAK,aAAa,CAAA,CAAA;AAAA,GAC5B,CAAA,CAAA;AAGD,EAAI,GAAA,CAAA,GAAA,CAAI,eAAiB,EAAA,OAAO,CAAM,KAAA;AACpC,IAAI,IAAA,CAAC,SAAS,aAAe,EAAA;AAC3B,MAAO,OAAA,CAAA,CAAE,IAAK,CAAA,WAAA,EAAa,GAAG,CAAA,CAAA;AAAA,KAChC;AAEA,IAAM,MAAA,aAAA,GAAgB,MAAMA,qBAAQ,EAAA,CAAE,KAAK,OAAQ,CAAA,aAAa,EAAE,MAAO,EAAA,CAAA;AAEzE,IAAO,OAAA,CAAA,CAAE,KAAK,aAAa,CAAA,CAAA;AAAA,GAC5B,CAAA,CAAA;AAGD,EAAO,MAAA,CAAA,IAAA,CAAK,OAAO,MAAQ,EAAA,KAAA,IAAS,EAAE,CAAA,CAAE,OAAQ,CAAA,CAAC,IAAS,KAAA;AAExD,IAAO,MAAA,CAAA,IAAA,CAAK,MAAO,CAAA,MAAA,EAAQ,KAAQ,GAAA,IAAI,CAAK,IAAA,EAAE,CAAA,CAAE,OAAQ,CAAA,CAAC,MAAW,KAAA;AAClE,MAAM,MAAA,KAAA,GAAQ,cAAc,IAAI,CAAA,CAAA;AAGhC,MAAA,MAAM,YAAY,MAAO,CAAA,MAAA,EAAQ,KAAQ,GAAA,IAAI,IAAI,MAAM,CAAA,CAAA;AAGvD,MAAA,MAAM,sBAAyB,GAAA,wBAAA;AAAA,QAC7B,SAAU,CAAA,QAAA;AAAA,OACZ,CAAA;AAEA,MAAA,MAAM,2BAA8B,GAAA,6BAAA;AAAA,QAClC,SAAA;AAAA,QACA,MAAQ,EAAA,MAAA;AAAA,OACV,CAAA;AAGA,MAAA,IAAI,0BAA0B,2BAA6B,EAAA;AAEzD,QAAA,GAAA,CAAI,MAAM,CAAA,CAAE,KAAO,EAAA,sBAAA,EAAwB,CAAA,CAAA;AAAA,OAC7C;AAIA,MAAA,GAAA,CAAI,MAAM,CAAA,CAAE,KAAO,EAAA,CAAC,CAAe,KAAA;AAEjC,QAAA,IAAI,SAAS,SAAW,EAAA;AACtB,UAAA,OAAA,CAAQ,SAAU,CAAA;AAAA,YAChB,OAAS,EAAA,CAAA;AAAA,YACT,SAAA;AAAA,WACD,CAAA,CAAA;AAAA,SACH;AAIA,QAAA,MAAM,oBAAuB,GAAA,wBAAA;AAAA,UAC3B,MAAO,CAAA,IAAA,CAAK,SAAU,CAAA,SAAA,IAAa,EAAE,CAAA;AAAA,SACvC,CAAA;AAGA,QAAM,MAAA,YAAA,GAAe,uBACjB,SAAU,CAAA,SAAA,GAAY,oBAAoB,CAAG,EAAA,OAAA,GAC3C,kBACF,CACA,GAAA,IAAA,CAAA;AAGJ,QAAM,MAAA,QAAA,GAAW,cAAc,OAC3B,GAAA,YAAA,CAAa,UACb,YAAc,EAAA,MAAA,GACZE,gCAAqB,CAAA,YAAA,CAAa,MAAQ,EAAA;AAAA,UACxC,WAAa,EAAA,QAAA;AAAA,UACb,SAAA,EAAW,CAAE,CAAA,GAAA,CAAI,KAAM,EAAA;AAAA,SACxB,CACD,GAAA,IAAA,CAAA;AAGN,QAAA,MAAM,UAAa,GAAA,QAAA;AAAA,UACjB,oBAAA,KAAyB,SACrB,GAAA,KAAA,GACA,oBAAwB,IAAA,KAAA;AAAA,UAC5B,EAAA;AAAA,SACF,CAAA;AAEA,QAAO,OAAA,CAAA,CAAE,IAAK,CAAA,QAAA,EAAU,UAAU,CAAA,CAAA;AAAA,OACnC,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAAA,GACF,CAAA,CAAA;AAED,EAAO,OAAA,GAAA,CAAA;AACT;;;;;;;"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/findPreferredResponseKey.ts","../src/utils/routeFromPath.ts","../src/utils/isAuthenticationRequired.ts","../src/utils/anyBasicAuthentication.ts","../src/utils/isBasicAuthenticationRequired.ts","../src/createMockServer.ts"],"sourcesContent":["/**\n * Find the preferred response key: default, 200, 201 …\n */\nexport function findPreferredResponseKey(responses?: string[]) {\n return ['default', '200', '201', '204', '404', '500'].find(\n (key) => responses?.includes(key) ?? false,\n )\n}\n","/**\n * Convert path to route\n * Example: /posts/{id} -> /posts/:id\n */\nexport function routeFromPath(path: string) {\n return path.replace(/{/g, ':').replace(/}/g, '')\n}\n","import type { OpenAPIV3 } from '@scalar/openapi-parser'\n\n/**\n * Check whether the given security scheme key is in the `security` configuration for this operation.\n **/\nexport function isAuthenticationRequired(\n security?: OpenAPIV3.SecurityRequirementObject[],\n): boolean {\n // If security is not defined, auth is not required.\n if (!security) {\n return false\n }\n\n // Don’t require auth if security is just an empty array []\n if (Array.isArray(security) && !security.length) {\n return false\n }\n\n // Includes empty object = auth is not required\n if (\n (security ?? []).some(\n (securityRequirement) => !Object.keys(securityRequirement).length,\n )\n ) {\n return false\n }\n\n return true\n}\n","import type { Context } from 'hono'\nimport { HTTPException } from 'hono/http-exception'\n\n/**\n * Middleware to check for any basic authentication header\n */\nexport function anyBasicAuthentication() {\n return async function (ctx: Context, next: () => Promise<void>) {\n // Check if the request has an Authorization header\n // Note: We don’t care *what* credentials are sent, though.\n if (ctx.req.header('Authorization')?.startsWith('Basic ')) {\n return await next()\n }\n\n // Unauthorized\n throw new HTTPException(401, {\n res: new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': 'Basic realm=\"Authentication Required\"',\n },\n }),\n })\n }\n}\n","import type { OpenAPI, OpenAPIV3_1 } from '@scalar/openapi-parser'\n\nexport function isBasicAuthenticationRequired(\n operation: OpenAPI.Operation,\n schema?: OpenAPI.Document,\n) {\n const allowedSecuritySchemes = operation.security?.map(\n (securityScheme: OpenAPIV3_1.SecurityRequirementObject) => {\n return Object.keys(securityScheme)[0]\n },\n )\n // Check if one of them is HTTP Basic Auth\n const httpBasicAuthIsRequired =\n allowedSecuritySchemes?.findIndex((securitySchemeKey: string) => {\n const securityScheme =\n schema?.components?.securitySchemes?.[securitySchemeKey]\n\n return (\n securityScheme?.type === 'http' && securityScheme?.scheme === 'basic'\n )\n }) !== undefined\n return httpBasicAuthIsRequired\n}\n","import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters'\nimport { type OpenAPI, openapi } from '@scalar/openapi-parser'\nimport { type Context, Hono } from 'hono'\nimport { cors } from 'hono/cors'\nimport type { StatusCode } from 'hono/utils/http-status'\n\nimport {\n findPreferredResponseKey,\n isAuthenticationRequired,\n routeFromPath,\n} from './utils'\nimport { anyBasicAuthentication } from './utils/anyBasicAuthentication'\nimport { isBasicAuthenticationRequired } from './utils/isBasicAuthenticationRequired'\n\n/**\n * Create a mock server instance\n */\nexport async function createMockServer(options?: {\n specification: string | Record<string, any>\n onRequest?: (data: { context: Context; operation: OpenAPI.Operation }) => void\n}) {\n const app = new Hono()\n\n // Resolve references\n const result = await openapi()\n .load(options?.specification ?? {})\n .dereference()\n .get()\n\n // CORS headers\n app.use(cors())\n\n // OpenAPI JSON file\n app.get('/openapi.json', async (c) => {\n if (!options?.specification) {\n return c.text('Not found', 404)\n }\n\n const { specification } = await openapi().load(options.specification).get()\n\n return c.json(specification)\n })\n\n // OpenAPI YAML file\n app.get('/openapi.yaml', async (c) => {\n if (!options?.specification) {\n return c.text('Not found', 404)\n }\n\n const specification = await openapi().load(options.specification).toYaml()\n\n return c.text(specification)\n })\n\n // Paths\n Object.keys(result.schema?.paths ?? {}).forEach((path) => {\n // Operations\n Object.keys(result.schema?.paths?.[path] ?? {}).forEach((method) => {\n const route = routeFromPath(path)\n\n // @ts-expect-error Needs a proper type\n const operation = result.schema?.paths?.[path]?.[method]\n\n // Check if authentication is required\n const requiresAuthentication = isAuthenticationRequired(\n operation.security,\n )\n // Get all available authentication methods\n const requiresBasicAuthentication = isBasicAuthenticationRequired(\n operation,\n result?.schema,\n )\n\n // Add HTTP basic authentication\n if (requiresAuthentication && requiresBasicAuthentication) {\n // @ts-expect-error Needs a proper type\n app[method](route, anyBasicAuthentication())\n }\n\n // Route\n // @ts-expect-error Needs a proper type\n app[method](route, (c: Context) => {\n // Call onRequest callback\n if (options?.onRequest) {\n options.onRequest({\n context: c,\n operation,\n })\n }\n\n // Response\n // default, 200, 201 …\n const preferredResponseKey = findPreferredResponseKey(\n Object.keys(operation.responses ?? {}),\n )\n\n // Focus on JSON for now\n const jsonResponse = preferredResponseKey\n ? operation.responses?.[preferredResponseKey]?.content?.[\n 'application/json'\n ]\n : null\n\n // Get or generate JSON\n const response = jsonResponse?.example\n ? jsonResponse.example\n : jsonResponse?.schema\n ? getExampleFromSchema(jsonResponse.schema, {\n emptyString: '…',\n variables: c.req.param(),\n })\n : null\n\n // Status code\n const statusCode = parseInt(\n preferredResponseKey === 'default'\n ? '200'\n : preferredResponseKey ?? '200',\n 10,\n ) as StatusCode\n\n return c.json(response, statusCode)\n })\n })\n })\n\n return app\n}\n"],"names":[],"mappings":"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"}