@scalar/import 0.3.23 → 0.3.25

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.
@@ -0,0 +1,1050 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { resolve } from "./resolve.js";
3
+ global.fetch = vi.fn();
4
+ function createFetchResponse(data, headers = {}) {
5
+ return {
6
+ ok: true,
7
+ text: () => new Promise((r) => r(data)),
8
+ headers: new Headers(headers)
9
+ };
10
+ }
11
+ describe("resolve", () => {
12
+ beforeEach(() => {
13
+ global.fetch.mockReset();
14
+ });
15
+ it("returns JSON urls", async () => {
16
+ const result = await resolve("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.json");
17
+ expect(result).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.json");
18
+ });
19
+ it("returns YAML urls", async () => {
20
+ const result = await resolve("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml");
21
+ expect(result).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml");
22
+ const otherResult = await resolve("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yml");
23
+ expect(otherResult).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yml");
24
+ });
25
+ it("finds URLs from sandbox URL", async () => {
26
+ const result = await resolve("https://sandbox.scalar.com/p/GcxDQ");
27
+ expect(result).toBe("https://sandbox.scalar.com/files/GcxDQ/openapi.yaml");
28
+ const otherResult = await resolve("https://sandbox.scalar.com/e/GcxDQ");
29
+ expect(otherResult).toBe("https://sandbox.scalar.com/files/GcxDQ/openapi.yaml");
30
+ });
31
+ it("finds URL in the CDN example", async () => {
32
+ const html = `<!doctype html>
33
+ <html>
34
+ <head>
35
+ <title>Scalar API Reference</title>
36
+ <meta charset="utf-8" />
37
+ <meta
38
+ name="viewport"
39
+ content="width=device-width, initial-scale=1" />
40
+ </head>
41
+ <body>
42
+ <script
43
+ id="api-reference"
44
+ data-url="https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml"><\/script>
45
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
46
+ </body>
47
+ </html>`;
48
+ fetch.mockResolvedValue(createFetchResponse(html));
49
+ const result = await resolve("https://example.com/reference");
50
+ expect(result).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml");
51
+ });
52
+ it("works with single quote data attributes", async () => {
53
+ const html = `<!doctype html>
54
+ <html>
55
+ <head>
56
+ <title>Scalar API Reference</title>
57
+ <meta charset="utf-8" />
58
+ <meta
59
+ name="viewport"
60
+ content="width=device-width, initial-scale=1" />
61
+ </head>
62
+ <body>
63
+ <script
64
+ id="api-reference"
65
+ data-url='https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml'><\/script>
66
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
67
+ </body>
68
+ </html>`;
69
+ fetch.mockResolvedValue(createFetchResponse(html));
70
+ const result = await resolve("https://example.com/reference");
71
+ expect(result).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml");
72
+ });
73
+ it("returns absolute URLs", async () => {
74
+ const html = `<!doctype html>
75
+ <html>
76
+ <head>
77
+ <title>Scalar API Reference</title>
78
+ <meta charset="utf-8" />
79
+ <meta
80
+ name="viewport"
81
+ content="width=device-width, initial-scale=1" />
82
+ </head>
83
+ <body>
84
+ <div data-url="/not-what-we-are-looking-for" id="foobar" />
85
+ <script
86
+ id="api-reference"
87
+ data-url="/openapi.yaml"><\/script>
88
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
89
+ </body>
90
+ </html>`;
91
+ fetch.mockResolvedValue(createFetchResponse(html));
92
+ const result = await resolve("https://example.com/reference");
93
+ expect(result).toBe("https://example.com/openapi.yaml");
94
+ });
95
+ it("returns absolute URLs based on the X-Forwarded-Host header", async () => {
96
+ const html = `<!doctype html>
97
+ <html>
98
+ <head>
99
+ <title>Scalar API Reference</title>
100
+ <meta charset="utf-8" />
101
+ <meta
102
+ name="viewport"
103
+ content="width=device-width, initial-scale=1" />
104
+ </head>
105
+ <body>
106
+ <div data-url="/not-what-we-are-looking-for" id="foobar" />
107
+ <script
108
+ id="api-reference"
109
+ data-url="../openapi.yaml"><\/script>
110
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
111
+ </body>
112
+ </html>`;
113
+ fetch.mockResolvedValue(
114
+ createFetchResponse(html, {
115
+ "X-Forwarded-Host": "https://example.com/somewhere/else/"
116
+ })
117
+ );
118
+ const result = await resolve("https://example.com/reference");
119
+ expect(result).toBe("https://example.com/somewhere/openapi.yaml");
120
+ });
121
+ it("finds URLs in some wrangled configuration object", async () => {
122
+ const html = `<!DOCTYPE html>
123
+ <html>
124
+ <head>
125
+ <title>Hono API Reference Demo</title>
126
+ <meta charset="utf-8" />
127
+ <meta
128
+ name="viewport"
129
+ content="width=device-width, initial-scale=1" />
130
+ </head>
131
+ <body>
132
+
133
+ <script
134
+ id="api-reference"
135
+ type="application/json"
136
+ data-configuration="{&amp;quot;spec&amp;quot;:{&amp;quot;url&amp;quot;:&amp;quot;/openapi.yaml&amp;quot;},&amp;quot;pageTitle&amp;quot;:&amp;quot;Hono API Reference Demo&amp;quot;}">
137
+
138
+ <\/script>
139
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
140
+
141
+ </body>
142
+ </html>`;
143
+ fetch.mockResolvedValue(createFetchResponse(html));
144
+ const result = await resolve("https://example.com/reference");
145
+ expect(result).toBe("https://example.com/openapi.yaml");
146
+ });
147
+ it("finds URLs in redoc HTML", async () => {
148
+ const html = `<!DOCTYPE html>
149
+ <html>
150
+ <head>
151
+ <title>Redoc</title>
152
+ <meta charset="utf-8"/>
153
+ <meta name="viewport" content="width=device-width, initial-scale=1">
154
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
155
+ </head>
156
+ <body>
157
+ <redoc spec-url='https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml'></redoc>
158
+ <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> <\/script>
159
+ </body>
160
+ </html>`;
161
+ fetch.mockResolvedValue(createFetchResponse(html));
162
+ const result = await resolve("https://example.com/reference");
163
+ expect(result).toBe("https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml");
164
+ });
165
+ it("finds embedded OpenAPI documents", async () => {
166
+ const html = `<!DOCTYPE html>
167
+ <html>
168
+ <head />
169
+ <body>
170
+ <script
171
+ id="api-reference"
172
+ type="application/json"
173
+ data-configuration="{&quot;spec&quot;:{&quot;content&quot;:{&quot;openapi&quot;:&quot;3.0.0&quot;,&quot;paths&quot;:{&quot;/&quot;:{&quot;get&quot;:{&quot;operationId&quot;:&quot;AppController_getHello&quot;,&quot;parameters&quot;:[],&quot;responses&quot;:{&quot;200&quot;:{&quot;description&quot;:&quot;&quot;}}}}},&quot;info&quot;:{&quot;title&quot;:&quot;Cats example&quot;,&quot;description&quot;:&quot;The cats API description&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;contact&quot;:{}},&quot;tags&quot;:[{&quot;name&quot;:&quot;cats&quot;,&quot;description&quot;:&quot;&quot;}],&quot;servers&quot;:[],&quot;components&quot;:{&quot;schemas&quot;:{}}}}}">{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"AppController_getHello","parameters":[],"responses":{"200":{"description":""}}}}},"info":{"title":"Cats example","description":"The cats API description","version":"1.0","contact":{}},"tags":[{"name":"cats","description":""}],"servers":[],"components":{"schemas":{}}}<\/script>
174
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
175
+ </body>
176
+ </html>
177
+ `;
178
+ fetch.mockResolvedValue(createFetchResponse(html));
179
+ const result = await resolve("https://example.com/reference");
180
+ expect(result).toStrictEqual({
181
+ openapi: "3.0.0",
182
+ paths: {
183
+ "/": {
184
+ get: {
185
+ operationId: "AppController_getHello",
186
+ parameters: [],
187
+ responses: { "200": { description: "" } }
188
+ }
189
+ }
190
+ },
191
+ info: {
192
+ title: "Cats example",
193
+ description: "The cats API description",
194
+ version: "1.0",
195
+ contact: {}
196
+ },
197
+ tags: [{ name: "cats", description: "" }],
198
+ servers: [],
199
+ components: { schemas: {} }
200
+ });
201
+ });
202
+ it("finds embedded OpenAPI documents, even if they contain HTML tags", async () => {
203
+ const html = `<!DOCTYPE html>
204
+ <html>
205
+ <head />
206
+ <body>
207
+ <script
208
+ id="api-reference"
209
+ type="application/json"
210
+ data-configuration="{&quot;spec&quot;:{&quot;content&quot;:{&quot;openapi&quot;:&quot;3.0.0&quot;,&quot;paths&quot;:{&quot;/&quot;:{&quot;get&quot;:{&quot;operationId&quot;:&quot;AppController_getHello&quot;,&quot;parameters&quot;:[],&quot;responses&quot;:{&quot;200&quot;:{&quot;description&quot;:&quot;&quot;}}}}},&quot;info&quot;:{&quot;title&quot;:&quot;Cats example&quot;,&quot;description&quot;:&quot;The cats<br>API description&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;contact&quot;:{}},&quot;tags&quot;:[{&quot;name&quot;:&quot;cats&quot;,&quot;description&quot;:&quot;&quot;}],&quot;servers&quot;:[],&quot;components&quot;:{&quot;schemas&quot;:{}}}}}"><\/script>
211
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
212
+ </body>
213
+ </html>
214
+ `;
215
+ fetch.mockResolvedValue(createFetchResponse(html));
216
+ const result = await resolve("https://example.com/reference");
217
+ expect(result).toStrictEqual({
218
+ openapi: "3.0.0",
219
+ paths: {
220
+ "/": {
221
+ get: {
222
+ operationId: "AppController_getHello",
223
+ parameters: [],
224
+ responses: { "200": { description: "" } }
225
+ }
226
+ }
227
+ },
228
+ info: {
229
+ title: "Cats example",
230
+ description: "The cats<br>API description",
231
+ version: "1.0",
232
+ contact: {}
233
+ },
234
+ tags: [{ name: "cats", description: "" }],
235
+ servers: [],
236
+ components: { schemas: {} }
237
+ });
238
+ });
239
+ it("finds embedded OpenAPI document URLs (JSON)", async () => {
240
+ const html = `<!DOCTYPE html>
241
+ <html>
242
+ <head />
243
+ <body>
244
+ <script
245
+ id="api-reference"
246
+ type="application/json"
247
+ data-configuration="{&quot;spec&quot;:{&quot;url&quot;:&quot;/openapi.json&quot;}}">
248
+ <\/script>
249
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
250
+ </body>
251
+ </html>
252
+ `;
253
+ fetch.mockResolvedValue(createFetchResponse(html));
254
+ const result = await resolve("https://example.com/reference");
255
+ expect(result).toBe("https://example.com/openapi.json");
256
+ });
257
+ it("finds embedded OpenAPI document URLs (JSON) unescaped", async () => {
258
+ const html = `<!DOCTYPE html>
259
+ <html>
260
+ <head />
261
+ <body>
262
+ <script
263
+ id="api-reference"
264
+ type="application/json"
265
+ data-configuration='{"url":"/openapi.yaml","hideClientButton":false,"showSidebar":true,"theme":"default","_integration":"hono","layout":"modern","isEditable":false,"hideModels":false,"hideDownloadButton":false,"hideTestRequestButton":false,"hideSearch":false,"hideDarkModeToggle":false,"withDefaultFonts":true}'><\/script>
266
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
267
+ </body>
268
+ </html>
269
+ `;
270
+ fetch.mockResolvedValue(createFetchResponse(html));
271
+ const result = await resolve("https://example.com/reference");
272
+ expect(result).toBe("https://example.com/openapi.yaml");
273
+ });
274
+ it("finds embedded OpenAPI documents (YAML)", async () => {
275
+ const html = `<!DOCTYPE html>
276
+ <html>
277
+ <head />
278
+ <body>
279
+
280
+ <script
281
+ id="api-reference"
282
+ type="application/json"
283
+ data-configuration="{&amp;quot;_integration&amp;quot;:&amp;quot;hono&amp;quot;,&amp;quot;spec&amp;quot;:{&amp;quot;content&amp;quot;:&amp;quot;openapi: 3.1.0
284
+ info:
285
+ title: Scalar Galaxy
286
+ description: |
287
+ The Scalar Galaxy is an example OpenAPI specification to test OpenAPI tools and libraries. It\u2019s a fictional universe with fictional planets and fictional data. Get all the data for [all planets](#tag/planets/GET/planets).
288
+
289
+ ## Resources
290
+
291
+ * https://github.com/scalar/scalar
292
+ * https://github.com/OAI/OpenAPI-Specification
293
+ * https://scalar.com
294
+
295
+ ## Markdown Support
296
+
297
+ All descriptions *can* contain ~~tons of text~~ **Markdown**. [If GitHub supports the syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax), chances are we\u2019re supporting it, too. You can even create [internal links to reference endpoints](#tag/authentication/POST/user/signup).
298
+
299
+ &lt;details&gt;
300
+ &lt;summary&gt;Examples&lt;/summary&gt;
301
+
302
+ **Blockquotes**
303
+
304
+ &gt; I love OpenAPI. &lt;3
305
+
306
+ **Tables**
307
+
308
+ | Feature | Availability |
309
+ | ---------------- | ------------ |
310
+ | Markdown Support | \u2713 |
311
+
312
+ **Accordion**
313
+
314
+ \`\`\`html
315
+ &lt;details&gt;
316
+ &lt;summary&gt;Using Details Tags&lt;/summary&gt;
317
+ &lt;p&gt;HTML Example&lt;/p&gt;
318
+ &lt;/details&gt;
319
+ \`\`\`
320
+
321
+ **Images**
322
+
323
+ Yes, there\u2019s support for images, too!
324
+
325
+ ![Empty placeholder image showing the width/height](https://images.placeholders.dev/?width=1280&amp;height=720)
326
+
327
+ &lt;/details&gt;
328
+ version: 1.0.0
329
+ contact:
330
+ name: Marc from Scalar
331
+ url: https://scalar.com
332
+ email: marc@scalar.com
333
+ servers:
334
+ - url: https://galaxy.scalar.com
335
+ - url: &#39;{protocol}://void.scalar.com/{path}&#39;
336
+ description: Responds with your request data
337
+ variables:
338
+ protocol:
339
+ enum:
340
+ - https
341
+ default: https
342
+ path:
343
+ default: &#39;&#39;
344
+ security:
345
+ - bearerAuth: []
346
+ - basicAuth: []
347
+ - apiKeyQuery: []
348
+ - apiKeyHeader: []
349
+ - apiKeyCookie: []
350
+ - oAuth2: []
351
+ tags:
352
+ - name: Authentication
353
+ description:
354
+ Some endpoints are public, but some require authentication. We provide
355
+ all the required endpoints to create an account and authorize yourself.
356
+ - name: Planets
357
+ description: Everything about planets
358
+ paths:
359
+ &#39;/planets&#39;:
360
+ get:
361
+ tags:
362
+ - Planets
363
+ summary: Get all planets
364
+ description: It\u2019s easy to say you know them all, but do you really? Retrieve all the planets and check whether you missed one.
365
+ operationId: getAllData
366
+ security:
367
+ - {}
368
+ parameters:
369
+ - &#39;$ref&#39;: &#39;#/components/parameters/limit&#39;
370
+ - &#39;$ref&#39;: &#39;#/components/parameters/offset&#39;
371
+ responses:
372
+ &#39;200&#39;:
373
+ description: OK
374
+ content:
375
+ application/json:
376
+ schema:
377
+ allOf:
378
+ - type: object
379
+ properties:
380
+ data:
381
+ type: array
382
+ items:
383
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
384
+ - &#39;$ref&#39;: &#39;#/components/schemas/PaginatedResource&#39;
385
+ post:
386
+ tags:
387
+ - Planets
388
+ summary: Create a planet
389
+ description: Time to play god and create a new planet. What do you think? Ah, don\u2019t think too much. What could go wrong anyway?
390
+ operationId: createPlanet
391
+ requestBody:
392
+ description: Planet
393
+ content:
394
+ application/json:
395
+ schema:
396
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
397
+ responses:
398
+ &#39;201&#39;:
399
+ description: Created
400
+ content:
401
+ application/json:
402
+ schema:
403
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
404
+ &#39;400&#39;:
405
+ description: Bad Request
406
+ content:
407
+ application/json:
408
+ schema:
409
+ &#39;$ref&#39;: &#39;#/components/responses/BadRequest&#39;
410
+ &#39;403&#39;:
411
+ description: Forbidden
412
+ content:
413
+ application/json:
414
+ schema:
415
+ &#39;$ref&#39;: &#39;#/components/responses/Forbidden&#39;
416
+ &#39;/planets/{planetId}&#39;:
417
+ get:
418
+ tags:
419
+ - Planets
420
+ summary: Get a planet
421
+ description: You\u2019ll better learn a little bit more about the planets. It might come in handy once space travel is available for everyone.
422
+ operationId: getPlanet
423
+ security:
424
+ - {}
425
+ parameters:
426
+ - &#39;$ref&#39;: &#39;#/components/parameters/planetId&#39;
427
+ responses:
428
+ &#39;200&#39;:
429
+ description: Planet Found
430
+ content:
431
+ application/json:
432
+ schema:
433
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
434
+ &#39;404&#39;:
435
+ description: Planet Not Found
436
+ content:
437
+ application/json:
438
+ schema:
439
+ &#39;$ref&#39;: &#39;#/components/responses/NotFound&#39;
440
+ put:
441
+ tags:
442
+ - Planets
443
+ summary: Update a planet
444
+ description: Sometimes you make mistakes, that\u2019s fine. No worries, you can update all planets.
445
+ operationId: updatePlanet
446
+ requestBody:
447
+ description: New information about the planet
448
+ content:
449
+ application/json:
450
+ schema:
451
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
452
+ parameters:
453
+ - &#39;$ref&#39;: &#39;#/components/parameters/planetId&#39;
454
+ responses:
455
+ &#39;200&#39;:
456
+ description: OK
457
+ content:
458
+ application/json:
459
+ schema:
460
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
461
+ &#39;400&#39;:
462
+ description: Bad Request
463
+ content:
464
+ application/json:
465
+ schema:
466
+ &#39;$ref&#39;: &#39;#/components/responses/BadRequest&#39;
467
+ &#39;403&#39;:
468
+ description: Forbidden
469
+ content:
470
+ application/json:
471
+ schema:
472
+ &#39;$ref&#39;: &#39;#/components/responses/Forbidden&#39;
473
+ &#39;404&#39;:
474
+ description: Not Found
475
+ content:
476
+ application/json:
477
+ schema:
478
+ &#39;$ref&#39;: &#39;#/components/responses/NotFound&#39;
479
+ delete:
480
+ tags:
481
+ - Planets
482
+ summary: Delete a planet
483
+ operationId: deletePlanet
484
+ description:
485
+ This endpoint was used to delete planets. Unfortunately, that caused
486
+ a lot of trouble for planets with life. So, this endpoint is now deprecated
487
+ and should not be used anymore.
488
+ deprecated: true
489
+ parameters:
490
+ - &#39;$ref&#39;: &#39;#/components/parameters/planetId&#39;
491
+ responses:
492
+ &#39;204&#39;:
493
+ description: No Content
494
+ &#39;404&#39;:
495
+ description: Not Found
496
+ &#39;/planets/{planetId}/image&#39;:
497
+ post:
498
+ tags:
499
+ - Planets
500
+ summary: Upload an image to a planet
501
+ description: Got a crazy good photo of a planet? Share it with the world!
502
+ operationId: uploadImage
503
+ parameters:
504
+ - &#39;$ref&#39;: &#39;#/components/parameters/planetId&#39;
505
+ requestBody:
506
+ content:
507
+ multipart/form-data:
508
+ schema:
509
+ type: object
510
+ properties:
511
+ image:
512
+ type: string
513
+ format: binary
514
+ responses:
515
+ &#39;200&#39;:
516
+ description: Image uploaded
517
+ content:
518
+ application/json:
519
+ schema:
520
+ type: object
521
+ properties:
522
+ message:
523
+ type: string
524
+ examples:
525
+ - Image uploaded successfully
526
+ &#39;400&#39;:
527
+ description: Bad Upload Request
528
+ content:
529
+ application/json:
530
+ schema:
531
+ &#39;$ref&#39;: &#39;#/components/responses/BadRequest&#39;
532
+ &#39;403&#39;:
533
+ description: Upload Forbidden
534
+ content:
535
+ application/json:
536
+ schema:
537
+ &#39;$ref&#39;: &#39;#/components/responses/Forbidden&#39;
538
+ &#39;404&#39;:
539
+ description: Failed to Upload Image, Planet Not Found
540
+ content:
541
+ application/json:
542
+ schema:
543
+ &#39;$ref&#39;: &#39;#/components/responses/NotFound&#39;
544
+ &#39;/user/signup&#39;:
545
+ post:
546
+ tags:
547
+ - Authentication
548
+ summary: Create a user
549
+ description: Time to create a user account, eh?
550
+ operationId: createUser
551
+ security:
552
+ - {}
553
+ requestBody:
554
+ content:
555
+ application/json:
556
+ schema:
557
+ &#39;$ref&#39;: &#39;#/components/schemas/NewUser&#39;
558
+ examples:
559
+ Marc:
560
+ value:
561
+ name: Marc
562
+ email: marc@scalar.com
563
+ password: i-love-scalar
564
+ Cam:
565
+ value:
566
+ name: Cam
567
+ email: cam@scalar.com
568
+ password: scalar-is-cool
569
+ responses:
570
+ &#39;201&#39;:
571
+ description: Created
572
+ content:
573
+ application/json:
574
+ schema:
575
+ &#39;$ref&#39;: &#39;#/components/schemas/User&#39;
576
+ &#39;400&#39;:
577
+ description: Bad Request
578
+ content:
579
+ application/json:
580
+ schema:
581
+ &#39;$ref&#39;: &#39;#/components/responses/BadRequest&#39;
582
+ &#39;/auth/token&#39;:
583
+ post:
584
+ tags:
585
+ - Authentication
586
+ summary: Get a token
587
+ description: Yeah, this is the boring security stuff. Just get your super secret token and move on.
588
+ operationId: getToken
589
+ security:
590
+ - {}
591
+ requestBody:
592
+ content:
593
+ application/json:
594
+ schema:
595
+ &#39;$ref&#39;: &#39;#/components/schemas/Credentials&#39;
596
+ responses:
597
+ &#39;201&#39;:
598
+ description: Token Created
599
+ content:
600
+ application/json:
601
+ schema:
602
+ &#39;$ref&#39;: &#39;#/components/schemas/Token&#39;
603
+ &#39;/me&#39;:
604
+ get:
605
+ tags:
606
+ - Authentication
607
+ summary: Get authenticated user
608
+ description: Find yourself they say. That\u2019s what you can do here.
609
+ operationId: getMe
610
+ security:
611
+ - basicAuth: []
612
+ - oAuth2:
613
+ - read:account
614
+ - bearerAuth: []
615
+ - apiKeyHeader: []
616
+ - apiKeyQuery: []
617
+ responses:
618
+ &#39;200&#39;:
619
+ description: OK
620
+ content:
621
+ application/json:
622
+ schema:
623
+ &#39;$ref&#39;: &#39;#/components/schemas/User&#39;
624
+ &#39;401&#39;:
625
+ description: Unauthorized
626
+ content:
627
+ application/json:
628
+ schema:
629
+ &#39;$ref&#39;: &#39;#/components/responses/Unauthorized&#39;
630
+ &#39;403&#39;:
631
+ description: Forbidden
632
+ content:
633
+ application/json:
634
+ schema:
635
+ &#39;$ref&#39;: &#39;#/components/responses/Forbidden&#39;
636
+ webhooks:
637
+ newPlanet:
638
+ post:
639
+ tags:
640
+ - Planets
641
+ requestBody:
642
+ description: Information about a new planet
643
+ content:
644
+ application/json:
645
+ schema:
646
+ &#39;$ref&#39;: &#39;#/components/schemas/Planet&#39;
647
+ responses:
648
+ &#39;200&#39;:
649
+ description:
650
+ Return a 200 status to indicate that the data was received
651
+ successfully
652
+ components:
653
+ securitySchemes:
654
+ bearerAuth:
655
+ type: http
656
+ scheme: bearer
657
+ basicAuth:
658
+ type: http
659
+ scheme: basic
660
+ apiKeyHeader:
661
+ type: apiKey
662
+ in: header
663
+ name: X-API-Key
664
+ apiKeyQuery:
665
+ type: apiKey
666
+ in: query
667
+ name: api_key
668
+ apiKeyCookie:
669
+ type: apiKey
670
+ in: cookie
671
+ name: api_key
672
+ oAuth2:
673
+ type: oauth2
674
+ flows:
675
+ authorizationCode:
676
+ authorizationUrl: https://galaxy.scalar.com/oauth/authorize
677
+ tokenUrl: https://galaxy.scalar.com/oauth/token
678
+ scopes:
679
+ read:account: read your account information
680
+ write:planets: modify planets in your account
681
+ read:planets: read your planets
682
+ clientCredentials:
683
+ tokenUrl: https://galaxy.scalar.com/oauth/token
684
+ scopes:
685
+ read:account: read your account information
686
+ write:planets: modify planets in your account
687
+ read:planets: read your planets
688
+ # Legacy
689
+ implicit:
690
+ authorizationUrl: https://galaxy.scalar.com/oauth/authorize
691
+ scopes:
692
+ read:account: read your account information
693
+ write:planets: modify planets in your account
694
+ read:planets: read your planets
695
+ # Legacy
696
+ password:
697
+ tokenUrl: https://galaxy.scalar.com/oauth/token
698
+ scopes:
699
+ read:account: read your account information
700
+ write:planets: modify planets in your account
701
+ read:planets: read your planets
702
+ parameters:
703
+ planetId:
704
+ name: planetId
705
+ in: path
706
+ required: true
707
+ schema:
708
+ type: integer
709
+ format: int64
710
+ examples:
711
+ - 1
712
+ limit:
713
+ name: limit
714
+ in: query
715
+ description: The number of items to return
716
+ required: false
717
+ schema:
718
+ type: integer
719
+ format: int64
720
+ default: 10
721
+ offset:
722
+ name: offset
723
+ in: query
724
+ description:
725
+ The number of items to skip before starting to collect the result
726
+ set
727
+ required: false
728
+ schema:
729
+ type: integer
730
+ format: int64
731
+ default: 0
732
+ responses:
733
+ BadRequest:
734
+ description: Bad Request
735
+ content:
736
+ application/json:
737
+ schema:
738
+ &#39;$ref&#39;: &#39;#/components/schemas/Error&#39;
739
+ Forbidden:
740
+ description: Forbidden
741
+ content:
742
+ application/json:
743
+ schema:
744
+ &#39;$ref&#39;: &#39;#/components/schemas/Error&#39;
745
+ NotFound:
746
+ description: NotFound
747
+ content:
748
+ application/json:
749
+ schema:
750
+ &#39;$ref&#39;: &#39;#/components/schemas/Error&#39;
751
+ Unauthorized:
752
+ description: Unauthorized
753
+ content:
754
+ application/json:
755
+ schema:
756
+ &#39;$ref&#39;: &#39;#/components/schemas/Error&#39;
757
+ schemas:
758
+ NewUser:
759
+ type: object
760
+ required:
761
+ - name
762
+ - email
763
+ - password
764
+ properties:
765
+ name:
766
+ type: string
767
+ examples:
768
+ - Hans
769
+ - Brynn
770
+ email:
771
+ type: string
772
+ format: email
773
+ examples:
774
+ - hans@scalar.com
775
+ - brynn@scalar.com
776
+ password:
777
+ type: string
778
+ minLength: 8
779
+ examples:
780
+ - i-love-scalar
781
+ - scalar-is-cool
782
+ User:
783
+ type: object
784
+ required:
785
+ - id
786
+ - name
787
+ - email
788
+ properties:
789
+ id:
790
+ type: integer
791
+ format: int64
792
+ examples:
793
+ - 1
794
+ name:
795
+ type: string
796
+ examples:
797
+ - Marc
798
+ email:
799
+ type: string
800
+ format: email
801
+ examples:
802
+ - marc@scalar.com
803
+ Credentials:
804
+ type: object
805
+ required:
806
+ - email
807
+ - password
808
+ properties:
809
+ email:
810
+ type: string
811
+ format: email
812
+ examples:
813
+ - marc@scalar.com
814
+ password:
815
+ type: string
816
+ examples:
817
+ - i-love-scalar
818
+ Token:
819
+ type: object
820
+ properties:
821
+ token:
822
+ type: string
823
+ examples:
824
+ - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
825
+ Planet:
826
+ type: object
827
+ required:
828
+ - id
829
+ - name
830
+ properties:
831
+ id:
832
+ type: integer
833
+ format: int64
834
+ examples:
835
+ - 1
836
+ x-variable: planetId
837
+ name:
838
+ type: string
839
+ examples:
840
+ - Mars
841
+ description:
842
+ type:
843
+ - string
844
+ - &#39;null&#39;
845
+ examples:
846
+ - The red planet
847
+ image:
848
+ type: string
849
+ nullable: true
850
+ examples:
851
+ - https://cdn.scalar.com/photos/mars.jpg
852
+ creator:
853
+ &#39;$ref&#39;: &#39;#/components/schemas/User&#39;
854
+ PaginatedResource:
855
+ type: object
856
+ properties:
857
+ meta:
858
+ type: object
859
+ properties:
860
+ limit:
861
+ type: integer
862
+ format: int64
863
+ examples:
864
+ - 10
865
+ offset:
866
+ type: integer
867
+ format: int64
868
+ examples:
869
+ - 0
870
+ total:
871
+ type: integer
872
+ format: int64
873
+ examples:
874
+ - 100
875
+ next:
876
+ type:
877
+ - string
878
+ - &#39;null&#39;
879
+ examples:
880
+ - &#39;/planets?limit=10&amp;offset=10&#39;
881
+ Error:
882
+ type: object
883
+ description: RFC 7807 (https://datatracker.ietf.org/doc/html/rfc7807)
884
+ properties:
885
+ type:
886
+ type: string
887
+ examples:
888
+ - https://example.com/errors/generic-error
889
+ title:
890
+ type: string
891
+ examples:
892
+ - Something went wrong here.
893
+ status:
894
+ type: integer
895
+ format: int64
896
+ examples:
897
+ - 403
898
+ detail:
899
+ type: string
900
+ examples:
901
+ - Unfortunately, we can\u2019t provide further information.
902
+ &amp;quot;},&amp;quot;pageTitle&amp;quot;:&amp;quot;Scalar Galaxy&amp;quot;}"><\/script>
903
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
904
+ </body>
905
+ </html>`;
906
+ fetch.mockResolvedValue(createFetchResponse(html));
907
+ const result = await resolve("https://example.com/reference");
908
+ expect(result).toMatchObject({
909
+ openapi: "3.1.0",
910
+ info: {
911
+ title: "Scalar Galaxy"
912
+ }
913
+ });
914
+ });
915
+ it("transforms GitHub URLs to raw file URLs", async () => {
916
+ const result = await resolve("https://github.com/outline/openapi/blob/main/spec3.yml");
917
+ expect(result).toBe("https://raw.githubusercontent.com/outline/openapi/refs/heads/main/spec3.yml");
918
+ });
919
+ it("finds embedded OpenAPI document in script tag (JSON)", async () => {
920
+ const html = `<!DOCTYPE html>
921
+ <html>
922
+ <head />
923
+ <body>
924
+ <script
925
+ id="api-reference"
926
+ type="application/json">
927
+ {"openapi":"3.1.0","info":{"title":"Hello World","version":"1.0"}}
928
+ <\/script>
929
+ </body>
930
+ </html>
931
+ `;
932
+ fetch.mockResolvedValue(createFetchResponse(html));
933
+ const result = await resolve("https://example.com/reference");
934
+ expect(result).toStrictEqual({
935
+ openapi: "3.1.0",
936
+ info: {
937
+ title: "Hello World",
938
+ version: "1.0"
939
+ }
940
+ });
941
+ });
942
+ it("finds embedded OpenAPI document in script tag, even if it contains HTML tags (JSON)", async () => {
943
+ const html = `<!DOCTYPE html>
944
+ <html>
945
+ <head />
946
+ <body>
947
+ <script
948
+ id="api-reference"
949
+ type="application/json">{"openapi":"3.0.0","paths":{"/v1/projects/{ref}/sessions/tags":{"type":"string","pattern":"/^\\s*([a-z0-9_-]+(\\s*,+\\s*)?)*\\s*$/i"}}}<\/script>
950
+ </body>
951
+ </html>
952
+ `;
953
+ fetch.mockResolvedValue(createFetchResponse(html));
954
+ const result = await resolve("https://example.com/reference");
955
+ expect(result).toStrictEqual({
956
+ openapi: "3.0.0",
957
+ paths: {
958
+ "/v1/projects/{ref}/sessions/tags": {
959
+ pattern: "/^\\s*([a-z0-9_-]+(\\s*,+\\s*)?)*\\s*$/i",
960
+ type: "string"
961
+ }
962
+ }
963
+ });
964
+ });
965
+ it("finds embedded OpenAPI document in script tag (YAML)", async () => {
966
+ const html = `<!DOCTYPE html>
967
+ <html>
968
+ <head />
969
+ <body>
970
+ <script
971
+ id="api-reference"
972
+ type="application/yaml">
973
+ openapi: 3.1.0
974
+ info:
975
+ title: Hello World
976
+ version: '1.0'
977
+ <\/script>
978
+ </body>
979
+ </html>
980
+ `;
981
+ fetch.mockResolvedValue(createFetchResponse(html));
982
+ const result = await resolve("https://example.com/reference");
983
+ expect(result).toStrictEqual({
984
+ openapi: "3.1.0",
985
+ info: {
986
+ title: "Hello World",
987
+ version: "1.0"
988
+ }
989
+ });
990
+ });
991
+ it("finds OpenAPI URL in HTML link text", async () => {
992
+ const html = `<!DOCTYPE html>
993
+ <html>
994
+ <body>
995
+ <p>Fetching spec from <a href="https://example.com/v1/openapi.yml" rel="nofollow" target="_blank">https://example.com/v1/openapi.yml</a></p>
996
+ </body>
997
+ </html>
998
+ `;
999
+ fetch.mockResolvedValue(createFetchResponse(html));
1000
+ const result = await resolve("https://example.com/reference");
1001
+ expect(result).toBe("https://example.com/v1/openapi.yml");
1002
+ });
1003
+ it("finds URL in script configuration", async () => {
1004
+ const html = `<!DOCTYPE html>
1005
+ <html>
1006
+ <head />
1007
+ <body>
1008
+ <script>
1009
+ var configuration = {
1010
+ isEditable: false,
1011
+ layout: "classic",
1012
+ darkMode: false,
1013
+ searchHotKey: "nope",
1014
+ url: "/docs/files/openapi.json",
1015
+ }
1016
+ var apiReference = document.getElementById('api-reference')
1017
+ apiReference.dataset.configuration = JSON.stringify(configuration)
1018
+ <\/script>
1019
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.20"><\/script>
1020
+ </body>
1021
+ </html>
1022
+ `;
1023
+ fetch.mockResolvedValue(createFetchResponse(html));
1024
+ const result = await resolve("https://example.com/reference");
1025
+ expect(result).toBe("https://example.com/docs/files/openapi.json");
1026
+ });
1027
+ it("returns URL if it directly returns an OpenAPI document", async () => {
1028
+ const openApiDoc = {
1029
+ openapi: "3.0.0",
1030
+ info: {
1031
+ title: "Test API",
1032
+ version: "1.0.0"
1033
+ },
1034
+ paths: {}
1035
+ };
1036
+ fetch.mockResolvedValue(createFetchResponse(JSON.stringify(openApiDoc)));
1037
+ const result = await resolve("https://example.com/swagger/json");
1038
+ expect(result).toBe("https://example.com/swagger/json");
1039
+ });
1040
+ it("finds the URL in an escaped JS object", async () => {
1041
+ fetch.mockResolvedValue(
1042
+ createFetchResponse(
1043
+ `<html>\\"$L8e\\",null,{\\"configuration\\":{\\"spec\\":{\\"url\\":\\"https://raw.githubusercontent.com/Foo/Bar/main/api/foobar.json\\"}},\\"initialRequest`
1044
+ )
1045
+ );
1046
+ const result = await resolve("https://example.com/foo");
1047
+ expect(result).toBe("https://raw.githubusercontent.com/Foo/Bar/main/api/foobar.json");
1048
+ });
1049
+ });
1050
+ //# sourceMappingURL=resolve.test.js.map