@scalar/hono-api-reference 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Scalar } from "./scalar.js";
2
- export {
3
- Scalar,
4
- Scalar as apiReference
5
- };
6
- //# sourceMappingURL=index.js.map
1
+ import { Scalar } from './scalar.js';
2
+ export { Scalar,
3
+ /**
4
+ * @deprecated Use `Scalar` instead.
5
+ */
6
+ Scalar as apiReference, };
package/dist/scalar.js CHANGED
@@ -1,7 +1,13 @@
1
- import { getHtmlDocument } from "@scalar/core/libs/html-rendering";
1
+ import { getHtmlDocument } from '@scalar/core/libs/html-rendering';
2
+ /**
3
+ * The default configuration for the API Reference.
4
+ */
2
5
  const DEFAULT_CONFIGURATION = {
3
- _integration: "hono"
6
+ _integration: 'hono',
4
7
  };
8
+ /**
9
+ * The custom theme for Hono
10
+ */
5
11
  const customTheme = `
6
12
  .dark-mode {
7
13
  color-scheme: dark;
@@ -53,22 +59,24 @@ const customTheme = `
53
59
  --scalar-sidebar-search-color: var(--scalar-color-3);
54
60
  }
55
61
  `;
56
- const Scalar = (configOrResolver) => {
57
- return async (c) => {
58
- let resolvedConfig = {};
59
- if (typeof configOrResolver === "function") {
60
- resolvedConfig = await configOrResolver(c);
61
- } else {
62
- resolvedConfig = configOrResolver;
63
- }
64
- const configuration = {
65
- ...DEFAULT_CONFIGURATION,
66
- ...resolvedConfig
62
+ /**
63
+ * The Hono middleware for the Scalar API Reference.
64
+ */
65
+ export const Scalar = (configOrResolver) => {
66
+ return async (c) => {
67
+ let resolvedConfig = {};
68
+ if (typeof configOrResolver === 'function') {
69
+ resolvedConfig = await configOrResolver(c);
70
+ }
71
+ else {
72
+ resolvedConfig = configOrResolver;
73
+ }
74
+ // Merge the defaults
75
+ const configuration = {
76
+ ...DEFAULT_CONFIGURATION,
77
+ ...resolvedConfig,
78
+ };
79
+ // Respond with the HTML document
80
+ return c.html(getHtmlDocument(configuration, customTheme));
67
81
  };
68
- return c.html(getHtmlDocument(configuration, customTheme));
69
- };
70
82
  };
71
- export {
72
- Scalar
73
- };
74
- //# sourceMappingURL=scalar.js.map
@@ -0,0 +1,209 @@
1
+ import { Hono } from 'hono';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+ import { Scalar } from './scalar.js';
4
+ describe('Scalar', () => {
5
+ afterEach(() => {
6
+ vi.useRealTimers();
7
+ });
8
+ it('returns HTML with default theme CSS when theme is not provided', async () => {
9
+ const app = new Hono();
10
+ const config = {
11
+ cdn: 'https://cdn.example.com',
12
+ content: { info: { title: 'Test API' } },
13
+ };
14
+ app.get('/', Scalar(config));
15
+ const response = await app.request('/');
16
+ expect(response.status).toBe(200);
17
+ expect(response.headers.get('content-type')).toContain('text/html');
18
+ const text = await response.text();
19
+ expect(text).toContain('<title>Scalar API Reference</title>');
20
+ expect(text).toContain('https://cdn.example.com');
21
+ expect(text).toContain('Test API');
22
+ expect(text).toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
23
+ });
24
+ it('excludes default theme CSS when theme is provided', async () => {
25
+ const app = new Hono();
26
+ app.get('/', Scalar({
27
+ content: { info: { title: 'Test API' } },
28
+ theme: 'kepler',
29
+ cdn: 'https://cdn.example.com',
30
+ }));
31
+ const response = await app.request('/');
32
+ expect(response.status).toBe(200);
33
+ expect(response.headers.get('content-type')).toContain('text/html');
34
+ const text = await response.text();
35
+ expect(text).toContain('<title>Scalar API Reference</title>');
36
+ expect(text).toContain('https://cdn.example.com');
37
+ expect(text).toContain('Test API');
38
+ // Ensure default theme CSS is not included
39
+ expect(text).not.toContain('--scalar-color-1');
40
+ });
41
+ it('handles missing spec content gracefully', async () => {
42
+ const app = new Hono();
43
+ const options = {
44
+ cdn: 'https://cdn.example.com',
45
+ };
46
+ app.get('/', Scalar(options));
47
+ const response = await app.request('/');
48
+ expect(response.status).toBe(200);
49
+ expect(response.headers.get('content-type')).toContain('text/html');
50
+ const text = await response.text();
51
+ expect(text).toContain('<title>Scalar API Reference</title>');
52
+ expect(text).toContain('https://cdn.example.com');
53
+ // Ensure no undefined content
54
+ expect(text).not.toContain('undefined');
55
+ });
56
+ it('uses default CDN when CDN is not provided', async () => {
57
+ const app = new Hono();
58
+ const options = {
59
+ content: { info: { title: 'Test API' } },
60
+ };
61
+ app.get('/', Scalar(options));
62
+ const response = await app.request('/');
63
+ expect(response.status).toBe(200);
64
+ expect(response.headers.get('content-type')).toContain('text/html');
65
+ const text = await response.text();
66
+ expect(text).toContain('<title>Scalar API Reference</title>');
67
+ expect(text).toContain('https://cdn.jsdelivr.net/npm/@scalar/api-reference');
68
+ });
69
+ it('includes content only once', async () => {
70
+ const app = new Hono();
71
+ app.get('/', Scalar({ content: { info: { title: 'Test API' } } }));
72
+ const response = await app.request('/');
73
+ expect(response.status).toBe(200);
74
+ const text = await response.text();
75
+ // Check the title is present
76
+ expect(text).toContain('Test API');
77
+ // Check that the title is only present once
78
+ const titleCount = (text.match(/Test API/g) || []).length;
79
+ expect(titleCount).toBe(1);
80
+ });
81
+ it('preserves URL in configuration', async () => {
82
+ const app = new Hono();
83
+ app.get('/', Scalar({
84
+ url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
85
+ }));
86
+ const response = await app.request('/');
87
+ const text = await response.text();
88
+ // Check the URL is present
89
+ expect(text).toContain('https://registry.scalar.com/@scalar/apis/galaxy?format=json');
90
+ });
91
+ it('applies custom theme CSS without theme specified', async () => {
92
+ const app = new Hono();
93
+ app.get('/', Scalar({}));
94
+ const response = await app.request('/');
95
+ const text = await response.text();
96
+ expect(text).toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
97
+ expect(text).toContain('--scalar-color-accent: #e36002');
98
+ });
99
+ it('excludes custom theme CSS when theme is specified', async () => {
100
+ const app = new Hono();
101
+ app.get('/', Scalar({ theme: 'none' }));
102
+ const response = await app.request('/');
103
+ const text = await response.text();
104
+ expect(text).not.toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
105
+ });
106
+ it('includes hono integration in configuration', async () => {
107
+ const app = new Hono();
108
+ app.get('/', Scalar({}));
109
+ const response = await app.request('/');
110
+ const text = await response.text();
111
+ expect(text).toContain('_integration": "hono"');
112
+ });
113
+ it('handles content as function', async () => {
114
+ const app = new Hono();
115
+ const contentFn = () => ({ info: { title: 'Function API' } });
116
+ app.get('/', Scalar({ content: contentFn }));
117
+ const response = await app.request('/');
118
+ const text = await response.text();
119
+ expect(text).toContain('Function API');
120
+ });
121
+ it('removes content when URL is provided', async () => {
122
+ const app = new Hono();
123
+ app.get('/', Scalar({
124
+ url: 'https://example.com/api.json',
125
+ content: { info: { title: 'Test API' } },
126
+ }));
127
+ const response = await app.request('/');
128
+ const text = await response.text();
129
+ expect(text).toContain('https://example.com/api.json');
130
+ expect(text).not.toContain('Test API');
131
+ });
132
+ it('sets HTML content type and 200 status', async () => {
133
+ const app = new Hono();
134
+ app.get('/', Scalar({}));
135
+ const response = await app.request('/');
136
+ expect(response.status).toBe(200);
137
+ expect(response.headers.get('content-type')).toContain('text/html');
138
+ });
139
+ it('works with the deprecated export', async () => {
140
+ const app = new Hono();
141
+ const config = {
142
+ cdn: 'https://cdn.example.com',
143
+ content: { info: { title: 'Test API' } },
144
+ };
145
+ app.get('/', Scalar(config));
146
+ const response = await app.request('/');
147
+ expect(response.status).toBe(200);
148
+ expect(response.headers.get('content-type')).toContain('text/html');
149
+ const text = await response.text();
150
+ expect(text).toContain('<title>Scalar API Reference</title>');
151
+ expect(text).toContain('https://cdn.example.com');
152
+ expect(text).toContain('Test API');
153
+ expect(text).toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
154
+ });
155
+ it('works with config resolver', async () => {
156
+ const app = new Hono();
157
+ // mock env
158
+ app.use('*', (c, next) => {
159
+ c.env = { SOME_VAR: 'SOME_VAR', ENVIRONMENT: 'development' };
160
+ return next();
161
+ });
162
+ const config = { content: { info: { title: 'Test API' } } };
163
+ app.get('/', Scalar((c) => {
164
+ expect(c.env.SOME_VAR).toBe('SOME_VAR');
165
+ expect(c.env.ENVIRONMENT).toBe('development');
166
+ return {
167
+ ...config,
168
+ proxyUrl: c.env.ENVIRONMENT === 'development' ? 'https://proxy.scalar.com' : undefined,
169
+ };
170
+ }));
171
+ const response = await app.request('/');
172
+ expect(response.status).toBe(200);
173
+ expect(response.headers.get('content-type')).toContain('text/html');
174
+ const text = await response.text();
175
+ expect(text).toContain('<title>Scalar API Reference</title>');
176
+ expect(text).toContain('Test API');
177
+ expect(text).toContain('https://proxy.scalar.com');
178
+ });
179
+ it('works with config resolver (async)', async () => {
180
+ vi.useFakeTimers();
181
+ const app = new Hono();
182
+ // mock env
183
+ app.use('*', (c, next) => {
184
+ c.env = { SOME_VAR: 'SOME_VAR', ENVIRONMENT: 'development' };
185
+ return next();
186
+ });
187
+ const config = { content: { info: { title: 'Test API' } } };
188
+ app.get('/', Scalar(async (c) => {
189
+ expect(c.env.SOME_VAR).toBe('SOME_VAR');
190
+ expect(c.env.ENVIRONMENT).toBe('development');
191
+ const theme = await new Promise((resolve) => {
192
+ setTimeout(() => resolve('deepSpace'),
193
+ // advance time by the same amount below
194
+ 100);
195
+ });
196
+ return { ...config, theme };
197
+ }));
198
+ const req = app.request('/');
199
+ // Same time of handler Promise above
200
+ vi.advanceTimersByTime(100);
201
+ const response = await req;
202
+ expect(response.status).toBe(200);
203
+ expect(response.headers.get('content-type')).toContain('text/html');
204
+ const text = await response.text();
205
+ expect(text).toContain('<title>Scalar API Reference</title>');
206
+ expect(text).toContain('Test API');
207
+ expect(text).toContain('deepSpace');
208
+ });
209
+ });
package/dist/types.js CHANGED
@@ -1 +1 @@
1
- //# sourceMappingURL=types.js.map
1
+ export {};
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "integrations/hono"
12
12
  },
13
- "version": "0.10.2",
13
+ "version": "0.10.4",
14
14
  "engines": {
15
15
  "node": ">=22"
16
16
  },
@@ -44,31 +44,25 @@
44
44
  "documentation": "https://scalar.com/products/api-references/integrations/hono"
45
45
  },
46
46
  "dependencies": {
47
- "@scalar/core": "0.4.2"
47
+ "@scalar/core": "0.4.4"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@hono/node-server": "^1.19.7",
51
51
  "@hono/zod-openapi": "^1.2.0",
52
52
  "hono": "4.12.4",
53
- "vite": "^7.3.1",
54
- "vitest": "4.0.16",
55
- "@scalar/build-tooling": "0.5.0",
56
- "@scalar/openapi-to-markdown": "0.4.4"
53
+ "vite": "8.0.0",
54
+ "vitest": "4.1.0",
55
+ "@scalar/openapi-to-markdown": "0.4.10"
57
56
  },
58
57
  "peerDependencies": {
59
58
  "hono": "^4.12.5"
60
59
  },
61
60
  "scripts": {
62
- "build": "scalar-build-esbuild",
63
- "dev": "nodemon --exec \"vite-node playground/index.ts\" --ext ts --quiet --watch ./",
61
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
62
+ "dev": "tsx watch playground/index.ts",
64
63
  "docker:build": "docker buildx build --platform=linux/amd64 -t scalar-hono-reference --build-arg=\"BASE_IMAGE=scalar-base\" .",
65
64
  "docker:run": "docker run -t scalar-hono-reference",
66
- "format": "scalar-format",
67
- "format:check": "scalar-format-check",
68
- "lint:check": "scalar-lint-check",
69
- "lint:fix": "scalar-lint-fix",
70
65
  "test": "vitest",
71
- "types:build": "scalar-types-build",
72
- "types:check": "scalar-types-check"
66
+ "types:check": "tsc --noEmit"
73
67
  }
74
68
  }
package/dist/index.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { Scalar } from './scalar'\n\nexport {\n Scalar,\n /**\n * @deprecated Use `Scalar` instead.\n */\n Scalar as apiReference,\n}\n\nexport type { ApiReferenceConfiguration } from './types'\n"],
5
- "mappings": "AAAA,SAAS,cAAc;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/scalar.ts"],
4
- "sourcesContent": ["import { getHtmlDocument } from '@scalar/core/libs/html-rendering'\nimport type { Context, Env, MiddlewareHandler } from 'hono'\n\nimport type { ApiReferenceConfiguration } from './types'\n\n/**\n * The default configuration for the API Reference.\n */\nconst DEFAULT_CONFIGURATION: Partial<ApiReferenceConfiguration> = {\n _integration: 'hono',\n}\n\n/**\n * The custom theme for Hono\n */\nconst customTheme = `\n.dark-mode {\n color-scheme: dark;\n --scalar-color-1: rgba(255, 255, 245, .86);\n --scalar-color-2: rgba(255, 255, 245, .6);\n --scalar-color-3: rgba(255, 255, 245, .38);\n --scalar-color-disabled: rgba(255, 255, 245, .25);\n --scalar-color-ghost: rgba(255, 255, 245, .25);\n --scalar-color-accent: #e36002;\n --scalar-background-1: #1e1e20;\n --scalar-background-2: #2a2a2a;\n --scalar-background-3: #505053;\n --scalar-background-4: rgba(255, 255, 255, 0.06);\n --scalar-background-accent: #e360021f;\n\n --scalar-border-color: rgba(255, 255, 255, 0.1);\n --scalar-scrollbar-color: rgba(255, 255, 255, 0.24);\n --scalar-scrollbar-color-active: rgba(255, 255, 255, 0.48);\n --scalar-lifted-brightness: 1.45;\n --scalar-backdrop-brightness: 0.5;\n\n --scalar-shadow-1: 0 1px 3px 0 rgb(0, 0, 0, 0.1);\n --scalar-shadow-2: rgba(15, 15, 15, 0.2) 0px 3px 6px,\n rgba(15, 15, 15, 0.4) 0px 9px 24px, 0 0 0 1px rgba(255, 255, 255, 0.1);\n\n --scalar-button-1: #f6f6f6;\n --scalar-button-1-color: #000;\n --scalar-button-1-hover: #e7e7e7;\n\n --scalar-color-green: #3dd68c;\n --scalar-color-red: #f66f81;\n --scalar-color-yellow: #f9b44e;\n --scalar-color-blue: #5c73e7;\n --scalar-color-orange: #ff8d4d;\n --scalar-color-purple: #b191f9;\n}\n/* Sidebar */\n.dark-mode .sidebar {\n --scalar-sidebar-background-1: #161618;\n --scalar-sidebar-item-hover-color: var(--scalar-color-accent);\n --scalar-sidebar-item-hover-background: transparent;\n --scalar-sidebar-item-active-background: transparent;\n --scalar-sidebar-border-color: transparent;\n --scalar-sidebar-color-1: var(--scalar-color-1);\n --scalar-sidebar-color-2: var(--scalar-color-2);\n --scalar-sidebar-color-active: var(--scalar-color-accent);\n --scalar-sidebar-search-background: #252529;\n --scalar-sidebar-search-border-color: transparent;\n --scalar-sidebar-search-color: var(--scalar-color-3);\n}\n`\n\ntype Configuration<E extends Env> =\n | Partial<ApiReferenceConfiguration>\n | ((c: Context<E>) => Partial<ApiReferenceConfiguration> | Promise<Partial<ApiReferenceConfiguration>>)\n\n/**\n * The Hono middleware for the Scalar API Reference.\n */\nexport const Scalar = <E extends Env>(configOrResolver: Configuration<E>): MiddlewareHandler<E> => {\n return async (c) => {\n let resolvedConfig: Partial<ApiReferenceConfiguration> = {}\n\n if (typeof configOrResolver === 'function') {\n resolvedConfig = await configOrResolver(c)\n } else {\n resolvedConfig = configOrResolver\n }\n\n // Merge the defaults\n const configuration = {\n ...DEFAULT_CONFIGURATION,\n ...resolvedConfig,\n }\n\n // Respond with the HTML document\n return c.html(getHtmlDocument(configuration, customTheme))\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAQhC,MAAM,wBAA4D;AAAA,EAChE,cAAc;AAChB;AAKA,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2Db,MAAM,SAAS,CAAgB,qBAA6D;AACjG,SAAO,OAAO,MAAM;AAClB,QAAI,iBAAqD,CAAC;AAE1D,QAAI,OAAO,qBAAqB,YAAY;AAC1C,uBAAiB,MAAM,iBAAiB,CAAC;AAAA,IAC3C,OAAO;AACL,uBAAiB;AAAA,IACnB;AAGA,UAAM,gBAAgB;AAAA,MACpB,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAGA,WAAO,EAAE,KAAK,gBAAgB,eAAe,WAAW,CAAC;AAAA,EAC3D;AACF;",
6
- "names": []
7
- }
package/dist/types.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }