@scalar/import 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ # @scalar/import
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e930134: feat: new @scalar/import package :)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Scalar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Scalar Import
2
+
3
+ [![Version](https://img.shields.io/npm/v/%40scalar/import)](https://www.npmjs.com/package/@scalar/import)
4
+ [![Downloads](https://img.shields.io/npm/dm/%40scalar/import)](https://www.npmjs.com/package/@scalar/import)
5
+ [![License](https://img.shields.io/npm/l/%40scalar%2Fimport)](https://www.npmjs.com/package/@scalar/import)
6
+ [![Discord](https://img.shields.io/discord/1135330207960678410?style=flat&color=5865F2)](https://discord.gg/scalar)
7
+
8
+ Pass an URL to an OpenAPI document, a Swagger document, a Postman collection, a Scalar API reference, a Scalar Sandbox link … basically anything, and retrieve an OpenAPI document.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @scalar/import
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Find any OpenAPI/Swagger document URL in any content:
19
+
20
+ ```ts
21
+ import { resolve } from '@scalar/import'
22
+
23
+ // Get the Swagger 2.0 URL from Swagger UI
24
+ const result = await resolve('https://petstore.swagger.io/')
25
+
26
+ // https://petstore.swagger.io/v2/swagger.json
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - Resolves URLs to OpenAPI specifications from various sources
32
+ - Supports JSON and YAML formats (`.json`, `.yaml`, `.yml`)
33
+ - Extracts OpenAPI specification URLs from HTML content, including:
34
+ - Scalar API Reference `<script>` tags
35
+ - Redoc HTML and JavaScript implementations
36
+ - Works with different quote styles and data attribute formats
37
+ - Robust error handling for various HTML structures
38
+ - Transforms GitHub URLs to raw file URLs
39
+ - Handles Scalar Sandbox URLs
40
+
41
+ ### Examples
42
+
43
+ | Input | Output | Description |
44
+ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------- |
45
+ | https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.json | Same as input | Returns JSON URLs as-is |
46
+ | https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml or \*.yml | Same as input | Returns YAML URLs as-is |
47
+ | https://sandbox.scalar.com/p/dlw8v | https://sandbox.scalar.com/files/dlw8v/openapi.yaml | Resolves sandbox URLs to specific file paths |
48
+ | https://github.com/owner/repo/blob/main/openapi.yaml | https://raw.githubusercontent.com/owner/repo/refs/heads/main/openapi.yaml | Transforms GitHub URLs to raw file URLs |
49
+ | HTML with data-url attribute | URL from data-url attribute | Extracts URL from HTML script tag with data-url attribute |
50
+ | HTML with relative URL `/openapi.yaml` | https://example.com/openapi.yaml | Resolves relative URLs to absolute URLs |
51
+ | HTML with JSON configuration | URL from JSON configuration | Extracts URL from JSON configuration in data-configuration attribute |
52
+ | Redoc HTML | URL from spec-url attribute | Extracts URL from Redoc's spec-url attribute |
53
+ | HTML with embedded OpenAPI | Parsed OpenAPI object | Extracts and parses embedded OpenAPI JSON from HTML |
54
+
55
+ ## Community
56
+
57
+ We are API nerds. You too? Let’s chat on Discord: <https://discord.gg/scalar>
58
+
59
+ ## License
60
+
61
+ The source code in this repository is licensed under [MIT](https://github.com/scalar/openapi-parser/blob/main/LICENSE).
@@ -0,0 +1,2 @@
1
+ export * from './resolve.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { resolve } from './resolve.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Find an OpenAPI document URL in the HTML of @scalar/api-reference and other places.
3
+ * This is useful to open the OpenAPI document from basically any source.
4
+ */
5
+ export declare function resolve(value?: string): Promise<string | Record<string, any> | undefined>;
6
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,CA0DnD"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Find an OpenAPI document URL in the HTML of @scalar/api-reference and other places.
3
+ * This is useful to open the OpenAPI document from basically any source.
4
+ */
5
+ async function resolve(value) {
6
+ // URLs
7
+ if (value?.startsWith('http://') || value?.startsWith('https://')) {
8
+ // Transform GitHub URLs to raw file URLs
9
+ const githubRawUrl = transformGitHubUrl(value);
10
+ if (githubRawUrl) {
11
+ return githubRawUrl;
12
+ }
13
+ // https://*.json
14
+ if (value?.toLowerCase().endsWith('.json')) {
15
+ return value;
16
+ }
17
+ // https://*.yaml
18
+ if (value?.toLowerCase().endsWith('.yaml') ||
19
+ value?.toLowerCase().endsWith('.yml')) {
20
+ return value;
21
+ }
22
+ // https://sandbox.scalar.com
23
+ const sandboxUrl = value.match(/https:\/\/sandbox\.scalar\.com\/(p|e)\/([a-z0-9]+)/);
24
+ if (sandboxUrl?.[2]) {
25
+ return `https://sandbox.scalar.com/files/${sandboxUrl[2]}/openapi.yaml`;
26
+ }
27
+ // Fetch URL
28
+ try {
29
+ const result = await fetch(value);
30
+ if (result.ok) {
31
+ const content = await result.text();
32
+ const urlOrPath = parseHtml(content);
33
+ if (urlOrPath) {
34
+ return makeRelativeUrlsAbsolute(value, urlOrPath);
35
+ }
36
+ // New: Check for embedded OpenAPI document
37
+ const embeddedSpec = parseEmbeddedOpenApi(content);
38
+ if (embeddedSpec) {
39
+ return embeddedSpec;
40
+ }
41
+ }
42
+ else {
43
+ console.warn(`[@scalar/import] Failed to fetch ${value}`);
44
+ }
45
+ }
46
+ catch (error) {
47
+ console.error(`[@scalar/import] Failed to fetch ${value}`, error);
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+ /**
53
+ * Go through the HTML and try to find the OpenAPI document URL
54
+ */
55
+ function parseHtml(html) {
56
+ // Check whether it could be HTML
57
+ if (!html?.includes('<')) {
58
+ return undefined;
59
+ }
60
+ // data-url="*"
61
+ const dataUrlMatch = html.match(/data-url=["']([^"']+)["']/);
62
+ if (dataUrlMatch?.[1]) {
63
+ return dataUrlMatch[1];
64
+ }
65
+ // spec-url="*"
66
+ const specUrlMatch = html.match(/spec-url=["']([^"']+)["']/);
67
+ if (specUrlMatch?.[1]) {
68
+ return specUrlMatch[1];
69
+ }
70
+ // Redoc.init('*')
71
+ const redocInit = html.match(/Redoc.init\(["']([^"']+)["']/);
72
+ if (redocInit?.[1]) {
73
+ return redocInit[1];
74
+ }
75
+ // &amp;quot;url&amp;quot;:&amp;quot;MY_CUSTOM_URL&amp;quot;
76
+ const configurationUrl = html.match(/&amp;quot;url&amp;quot;:&amp;quot;([^;]+)&amp;quot;/);
77
+ if (configurationUrl?.[1]) {
78
+ return configurationUrl[1];
79
+ }
80
+ return undefined;
81
+ }
82
+ /**
83
+ * URLs can be relative, but we need absolute URLs eventually.
84
+ */
85
+ function makeRelativeUrlsAbsolute(baseUrl, path) {
86
+ // Check whether the path is already absolute
87
+ if (path.startsWith('http://') || path.startsWith('https://')) {
88
+ return path;
89
+ }
90
+ // Combine the URL and the relative path
91
+ try {
92
+ const { href } = new URL(path, baseUrl);
93
+ return href;
94
+ }
95
+ catch (error) {
96
+ // Return original path if URL creation fails
97
+ console.error('[makeRelativeUrlsAbsolute] Error combining URLs:', error);
98
+ return path;
99
+ }
100
+ }
101
+ /**
102
+ * Parse embedded OpenAPI document from HTML
103
+ */
104
+ function parseEmbeddedOpenApi(html) {
105
+ const match = html.match(/<script[^>]*data-configuration=['"]([^'"]+)['"][^>]*>(.*?)<\/script>/);
106
+ if (!match)
107
+ return undefined;
108
+ try {
109
+ const configString = decodeHtmlEntities(match[1]);
110
+ const config = JSON.parse(configString);
111
+ if (config.spec?.content) {
112
+ return config.spec.content;
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error('[@scalar/import] Failed to parse embedded OpenAPI document:', error);
117
+ }
118
+ return undefined;
119
+ }
120
+ /**
121
+ * Decode HTML entities in a string
122
+ */
123
+ function decodeHtmlEntities(text) {
124
+ const entities = {
125
+ '&amp;': '&',
126
+ '&lt;': '<',
127
+ '&gt;': '>',
128
+ '&quot;': '"',
129
+ '&#39;': "'",
130
+ };
131
+ return text.replace(new RegExp(Object.keys(entities).join('|'), 'g'), (match) => entities[match]);
132
+ }
133
+ /**
134
+ * Transform GitHub URLs to raw file URLs, preserving the branch information
135
+ */
136
+ function transformGitHubUrl(url) {
137
+ const githubRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/;
138
+ const match = url.match(githubRegex);
139
+ if (match) {
140
+ const [, owner, repo, branch, path] = match;
141
+ return `https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${path}`;
142
+ }
143
+ return undefined;
144
+ }
145
+
146
+ export { resolve };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@scalar/import",
3
+ "description": "Import any file, URL or content to get an OpenAPI document",
4
+ "license": "MIT",
5
+ "author": "Scalar (https://github.com/scalar)",
6
+ "homepage": "https://github.com/scalar/scalar",
7
+ "bugs": "https://github.com/scalar/scalar/issues/new/choose",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/scalar/scalar.git",
11
+ "directory": "packages/import"
12
+ },
13
+ "keywords": [
14
+ "openapi",
15
+ "swagger",
16
+ "postman",
17
+ "scalar"
18
+ ],
19
+ "version": "0.0.1",
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "module": "dist/index.js",
37
+ "dependencies": {
38
+ "@scalar/openapi-parser": "0.8.7"
39
+ },
40
+ "devDependencies": {
41
+ "vite": "^5.2.10",
42
+ "@scalar/build-tooling": "0.1.11"
43
+ },
44
+ "scripts": {
45
+ "build": "scalar-build-rollup",
46
+ "dev": "nodemon --exec \"vite-node playground/index.ts\" --ext ts --quiet",
47
+ "lint:check": "eslint .",
48
+ "lint:fix": "eslint . --fix",
49
+ "test": "vitest",
50
+ "types:build": "scalar-types-build",
51
+ "types:check": "scalar-types-check"
52
+ }
53
+ }