@scalar/fastify-api-reference 1.49.0 → 1.49.1

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,11 @@
1
1
  # @scalar/fastify-api-reference
2
2
 
3
+ ## 1.49.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#8466](https://github.com/scalar/scalar/pull/8466): chore: new build pipeline
8
+
3
9
  ## 1.49.0
4
10
 
5
11
  ### Patch Changes
@@ -1,171 +1,208 @@
1
- import { getHtmlDocument } from "@scalar/core/libs/html-rendering";
2
- import { normalize, toJson, toYaml } from "@scalar/openapi-parser";
3
- import fp from "fastify-plugin";
4
- import { slug } from "github-slugger";
5
- import { getJavaScriptFile } from "./utils/getJavaScriptFile.js";
6
- const RELATIVE_JAVASCRIPT_PATH = "js/scalar.js";
1
+ import { getHtmlDocument } from '@scalar/core/libs/html-rendering';
2
+ import { normalize, toJson, toYaml } from '@scalar/openapi-parser';
3
+ import fp from 'fastify-plugin';
4
+ import { slug } from 'github-slugger';
5
+ import { getJavaScriptFile } from './utils/getJavaScriptFile.js';
6
+ /**
7
+ * Path to the bundled Scalar JavaScript file
8
+ */
9
+ const RELATIVE_JAVASCRIPT_PATH = 'js/scalar.js';
10
+ /**
11
+ * This Schema is used to hide the route from the documentation.
12
+ *
13
+ * We don't know whether `@fastify/swagger` is registered, but it doesn't hurt to add a schema anyway.
14
+ *
15
+ * @see https://github.com/fastify/fastify-swagger#hide-a-route
16
+ */
7
17
  const schemaToHideRoute = {
8
- hide: true
18
+ hide: true,
9
19
  };
10
20
  const getRoutePrefix = (routePrefix) => {
11
- const prefix = routePrefix ?? "/reference";
12
- return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
21
+ const prefix = routePrefix ?? '/reference';
22
+ // Remove trailing slash if present
23
+ return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
13
24
  };
25
+ /**
26
+ * Get the endpoints for the OpenAPI specification.
27
+ */
14
28
  const getOpenApiDocumentEndpoints = (openApiDocumentEndpoints) => {
15
- const { json = "/openapi.json", yaml = "/openapi.yaml" } = openApiDocumentEndpoints ?? {};
16
- return { json, yaml };
29
+ const { json = '/openapi.json', yaml = '/openapi.yaml' } = openApiDocumentEndpoints ?? {};
30
+ return { json, yaml };
17
31
  };
18
- const getJavaScriptUrl = (routePrefix) => `${getRoutePrefix(routePrefix)}/${RELATIVE_JAVASCRIPT_PATH}`.replace(/\/\//g, "/");
32
+ /**
33
+ * Get the URL for the Scalar JavaScript file.
34
+ */
35
+ const getJavaScriptUrl = (routePrefix) => `${getRoutePrefix(routePrefix)}/${RELATIVE_JAVASCRIPT_PATH}`.replace(/\/\//g, '/');
36
+ /**
37
+ * The default configuration for Fastify
38
+ */
19
39
  const DEFAULT_CONFIGURATION = {
20
- _integration: "fastify"
40
+ _integration: 'fastify',
21
41
  };
22
- const fastifyApiReference = fp(
23
- (fastify, options, next) => {
42
+ const fastifyApiReference = fp((fastify, options, next) => {
24
43
  const { configuration: givenConfiguration } = options;
44
+ // Merge the defaults
25
45
  let configuration = {
26
- ...DEFAULT_CONFIGURATION,
27
- ...givenConfiguration
46
+ ...DEFAULT_CONFIGURATION,
47
+ ...givenConfiguration,
28
48
  };
29
49
  const specSource = (() => {
30
- const { content, url } = configuration ?? {};
31
- if (content) {
32
- return {
33
- type: "content",
34
- get: () => {
35
- if (typeof content === "function") {
36
- return content();
37
- }
38
- return content;
39
- }
40
- };
41
- }
42
- if (url) {
43
- return {
44
- type: "url",
45
- get: () => url
46
- };
47
- }
48
- if (fastify.hasPlugin("@fastify/swagger") && typeof fastify.swagger === "function") {
49
- return {
50
- type: "swagger",
51
- get: () => fastify.swagger()
52
- };
53
- }
54
- return void 0;
50
+ const { content, url } = configuration ?? {};
51
+ if (content) {
52
+ return {
53
+ type: 'content',
54
+ get: () => {
55
+ if (typeof content === 'function') {
56
+ return content();
57
+ }
58
+ return content;
59
+ },
60
+ };
61
+ }
62
+ if (url) {
63
+ return {
64
+ type: 'url',
65
+ get: () => url,
66
+ };
67
+ }
68
+ // Even if @fastify/swagger is loaded, when the `decorator` option is set, the `swagger` function is not available.
69
+ if (fastify.hasPlugin('@fastify/swagger') && typeof fastify.swagger === 'function') {
70
+ return {
71
+ type: 'swagger',
72
+ get: () => fastify.swagger(),
73
+ };
74
+ }
75
+ return void 0;
55
76
  })();
77
+ // If no OpenAPI specification is passed and @fastify/swagger isn't loaded, show a warning.
56
78
  if (!specSource && !configuration.sources) {
57
- fastify.log.warn(
58
- "[@scalar/fastify-api-reference] You didn't provide a `content`, `url`, `sources` or @fastify/swagger could not be found. Please provide one of these options."
59
- );
60
- return next();
79
+ fastify.log.warn("[@scalar/fastify-api-reference] You didn't provide a `content`, `url`, `sources` or @fastify/swagger could not be found. Please provide one of these options.");
80
+ return next();
61
81
  }
82
+ // Read the JavaScript file once.
62
83
  const fileContent = getJavaScriptFile();
63
84
  const hooks = {};
64
85
  if (options.hooks) {
65
- const additionalHooks = ["onRequest", "preHandler"];
66
- for (const hook of additionalHooks) {
67
- if (options.hooks[hook]) {
68
- hooks[hook] = options.hooks[hook];
86
+ const additionalHooks = ['onRequest', 'preHandler'];
87
+ for (const hook of additionalHooks) {
88
+ if (options.hooks[hook]) {
89
+ hooks[hook] = options.hooks[hook];
90
+ }
69
91
  }
70
- }
71
92
  }
72
93
  const getSpecFilenameSlug = (spec) => {
73
- return slug(spec?.specification?.info?.title ?? "spec");
94
+ // Same GitHub Slugger and default file name as in `@scalar/api-reference`, when generating the download
95
+ return slug(spec?.specification?.info?.title ?? 'spec');
74
96
  };
97
+ // Only expose the endpoints if specSource is available
75
98
  if (specSource) {
76
- const openApiSpecUrlJson = `${getRoutePrefix(options.routePrefix)}${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).json}`;
77
- fastify.route({
78
- method: "GET",
79
- url: openApiSpecUrlJson,
80
- schema: schemaToHideRoute,
81
- ...hooks,
82
- ...options.logLevel && { logLevel: options.logLevel },
83
- handler(_, reply) {
84
- const spec = normalize(specSource.get());
85
- const filename = getSpecFilenameSlug(spec);
86
- const json = JSON.parse(toJson(spec));
87
- return reply.header("Content-Type", "application/json").header("Content-Disposition", `filename=${filename}.json`).header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Methods", "*").send(json);
88
- }
89
- });
90
- const openApiSpecUrlYaml = `${getRoutePrefix(options.routePrefix)}${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).yaml}`;
91
- fastify.route({
92
- method: "GET",
93
- url: openApiSpecUrlYaml,
94
- schema: schemaToHideRoute,
95
- ...hooks,
96
- ...options.logLevel && { logLevel: options.logLevel },
97
- handler(_, reply) {
98
- const spec = normalize(specSource.get());
99
- const filename = getSpecFilenameSlug(spec);
100
- const yaml = toYaml(spec);
101
- return reply.header("Content-Type", "application/yaml").header("Content-Disposition", `filename=${filename}.yaml`).header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Methods", "*").send(yaml);
102
- }
103
- });
99
+ const openApiSpecUrlJson = `${getRoutePrefix(options.routePrefix)}${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).json}`;
100
+ fastify.route({
101
+ method: 'GET',
102
+ url: openApiSpecUrlJson,
103
+ schema: schemaToHideRoute,
104
+ ...hooks,
105
+ ...(options.logLevel && { logLevel: options.logLevel }),
106
+ handler(_, reply) {
107
+ const spec = normalize(specSource.get());
108
+ const filename = getSpecFilenameSlug(spec);
109
+ const json = JSON.parse(toJson(spec)); // parsing minifies the JSON
110
+ return reply
111
+ .header('Content-Type', 'application/json')
112
+ .header('Content-Disposition', `filename=${filename}.json`)
113
+ .header('Access-Control-Allow-Origin', '*')
114
+ .header('Access-Control-Allow-Methods', '*')
115
+ .send(json);
116
+ },
117
+ });
118
+ const openApiSpecUrlYaml = `${getRoutePrefix(options.routePrefix)}${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).yaml}`;
119
+ fastify.route({
120
+ method: 'GET',
121
+ url: openApiSpecUrlYaml,
122
+ schema: schemaToHideRoute,
123
+ ...hooks,
124
+ ...(options.logLevel && { logLevel: options.logLevel }),
125
+ handler(_, reply) {
126
+ const spec = normalize(specSource.get());
127
+ const filename = getSpecFilenameSlug(spec);
128
+ const yaml = toYaml(spec);
129
+ return reply
130
+ .header('Content-Type', 'application/yaml')
131
+ .header('Content-Disposition', `filename=${filename}.yaml`)
132
+ .header('Access-Control-Allow-Origin', '*')
133
+ .header('Access-Control-Allow-Methods', '*')
134
+ .send(yaml);
135
+ },
136
+ });
104
137
  }
105
- const ignoreTrailingSlash = (
106
- // @ts-expect-error We're still on Fastify 4, this is introduced in Fastify 5
107
- fastify.initialConfig?.routerOptions?.ignoreTrailingSlash === true || fastify.initialConfig?.ignoreTrailingSlash === true
108
- );
138
+ // Redirect route without a trailing slash to force a trailing slash:
139
+ // We need this so the request to the JS file is relative.
140
+ // With ignoreTrailingSlash: true, fastify responds to both routes anyway.
141
+ const ignoreTrailingSlash =
142
+ // @ts-expect-error We're still on Fastify 4, this is introduced in Fastify 5
143
+ fastify.initialConfig?.routerOptions?.ignoreTrailingSlash === true ||
144
+ fastify.initialConfig?.ignoreTrailingSlash === true;
109
145
  if (!ignoreTrailingSlash && getRoutePrefix(options.routePrefix)) {
110
- fastify.route({
111
- method: "GET",
112
- url: getRoutePrefix(options.routePrefix),
146
+ fastify.route({
147
+ method: 'GET',
148
+ url: getRoutePrefix(options.routePrefix),
149
+ schema: schemaToHideRoute,
150
+ ...hooks,
151
+ ...(options.logLevel && { logLevel: options.logLevel }),
152
+ handler(request, reply) {
153
+ // we are in a route without a trailing slash so redirect directly to the one with a trailing slash
154
+ const currentUrl = new URL(request.url, `${request.protocol}://${request.hostname}`);
155
+ return reply.redirect(`${currentUrl.pathname}/`, 301);
156
+ },
157
+ });
158
+ }
159
+ // If no theme is passed, use the default theme.
160
+ fastify.route({
161
+ method: 'GET',
162
+ url: `${getRoutePrefix(options.routePrefix)}/`,
163
+ // We don't know whether @fastify/swagger is registered, but it doesn't hurt to add a schema anyway.
113
164
  schema: schemaToHideRoute,
114
165
  ...hooks,
115
- ...options.logLevel && { logLevel: options.logLevel },
166
+ ...(options.logLevel && { logLevel: options.logLevel }),
116
167
  handler(request, reply) {
117
- const currentUrl = new URL(request.url, `${request.protocol}://${request.hostname}`);
118
- return reply.redirect(`${currentUrl.pathname}/`, 301);
119
- }
120
- });
121
- }
122
- fastify.route({
123
- method: "GET",
124
- url: `${getRoutePrefix(options.routePrefix)}/`,
125
- // We don't know whether @fastify/swagger is registered, but it doesn't hurt to add a schema anyway.
126
- schema: schemaToHideRoute,
127
- ...hooks,
128
- ...options.logLevel && { logLevel: options.logLevel },
129
- handler(request, reply) {
130
- const currentUrl = new URL(request.url, `${request.protocol}://${request.hostname}`);
131
- if (!currentUrl.pathname.endsWith("/")) {
132
- return reply.redirect(`${currentUrl.pathname}/`, 301);
133
- }
134
- if (specSource && specSource.type !== "url") {
135
- configuration = {
136
- ...configuration,
137
- // Use a relative URL in case we're proxied
138
- url: `.${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).json}`
139
- };
140
- }
141
- return reply.header("Content-Type", "text/html; charset=utf-8").send(
142
- getHtmlDocument({
143
- // We're using the bundled JS here by default, but the user can pass a CDN URL.
144
- cdn: RELATIVE_JAVASCRIPT_PATH,
145
- ...configuration
146
- })
147
- );
148
- }
168
+ // Redirect if it's the route without a slash
169
+ const currentUrl = new URL(request.url, `${request.protocol}://${request.hostname}`);
170
+ if (!currentUrl.pathname.endsWith('/')) {
171
+ return reply.redirect(`${currentUrl.pathname}/`, 301);
172
+ }
173
+ /**
174
+ * Regardless of where we source the spec from, provide it as a URL, to have the
175
+ * download button point to the exposed endpoint.
176
+ * If the URL is explicitly passed, defer to that URL instead.
177
+ */
178
+ if (specSource && specSource.type !== 'url') {
179
+ configuration = {
180
+ ...configuration,
181
+ // Use a relative URL in case we're proxied
182
+ url: `.${getOpenApiDocumentEndpoints(options.openApiDocumentEndpoints).json}`,
183
+ };
184
+ }
185
+ // Respond with the HTML document
186
+ return reply.header('Content-Type', 'text/html; charset=utf-8').send(getHtmlDocument({
187
+ // We're using the bundled JS here by default, but the user can pass a CDN URL.
188
+ cdn: RELATIVE_JAVASCRIPT_PATH,
189
+ ...configuration,
190
+ }));
191
+ },
149
192
  });
150
193
  fastify.route({
151
- method: "GET",
152
- url: getJavaScriptUrl(options.routePrefix),
153
- // We don't know whether @fastify/swagger is registered, but it doesn't hurt to add a schema anyway.
154
- schema: schemaToHideRoute,
155
- ...hooks,
156
- ...options.logLevel && { logLevel: options.logLevel },
157
- handler(_, reply) {
158
- return reply.header("Content-Type", "application/javascript; charset=utf-8").send(fileContent);
159
- }
194
+ method: 'GET',
195
+ url: getJavaScriptUrl(options.routePrefix),
196
+ // We don't know whether @fastify/swagger is registered, but it doesn't hurt to add a schema anyway.
197
+ schema: schemaToHideRoute,
198
+ ...hooks,
199
+ ...(options.logLevel && { logLevel: options.logLevel }),
200
+ handler(_, reply) {
201
+ return reply.header('Content-Type', 'application/javascript; charset=utf-8').send(fileContent);
202
+ },
160
203
  });
161
204
  next();
162
- },
163
- {
164
- name: "@scalar/fastify-api-reference"
165
- }
166
- );
167
- var fastifyApiReference_default = fastifyApiReference;
168
- export {
169
- fastifyApiReference_default as default
170
- };
171
- //# sourceMappingURL=fastifyApiReference.js.map
205
+ }, {
206
+ name: '@scalar/fastify-api-reference',
207
+ });
208
+ export default fastifyApiReference;