@scalar/import 0.0.3 → 0.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @scalar/import
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 2275977: feat: import from way more sources, leverage the proxy more
8
+ - f2361b3: fix: resolve JSON/YAML URLs
9
+
3
10
  ## 0.0.3
4
11
 
5
12
  ### Patch Changes
package/dist/resolve.d.ts CHANGED
@@ -2,5 +2,10 @@
2
2
  * Find an OpenAPI document URL in the HTML of @scalar/api-reference and other places.
3
3
  * This is useful to open the OpenAPI document from basically any source.
4
4
  */
5
- export declare function resolve(value?: string | null): Promise<string | Record<string, any> | undefined>;
5
+ export declare function resolve(value?: string | null, options?: {
6
+ /**
7
+ * Fetch function to use instead of the global fetch. Use this to intercept requests.
8
+ */
9
+ fetch?: (url: string) => Promise<Response>;
10
+ }): Promise<string | Record<string, any> | undefined>;
6
11
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +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,GAAG,IAAI,GACpB,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,CA0DnD"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,CAAC,EAAE;IACR;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,GACA,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,CAmHnD"}
package/dist/resolve.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { parse } from 'yaml';
2
+
1
3
  /**
2
4
  * Find an OpenAPI document URL in the HTML of @scalar/api-reference and other places.
3
5
  * This is useful to open the OpenAPI document from basically any source.
4
6
  */
5
- async function resolve(value) {
7
+ async function resolve(value, options) {
6
8
  // URLs
7
9
  if (value?.startsWith('http://') || value?.startsWith('https://')) {
8
10
  // Transform GitHub URLs to raw file URLs
@@ -26,14 +28,69 @@ async function resolve(value) {
26
28
  }
27
29
  // Fetch URL
28
30
  try {
29
- const result = await fetch(value);
31
+ let result = await (options?.fetch
32
+ ? options.fetch(value)
33
+ : fetch(value, {
34
+ cache: 'no-cache',
35
+ }));
36
+ // If the custom fetch failed, try again with regular fetch
37
+ if (!result.ok && options?.fetch) {
38
+ result = await fetch(value, {
39
+ cache: 'no-cache',
40
+ });
41
+ }
30
42
  if (result.ok) {
31
43
  const content = await result.text();
32
- const urlOrPath = parseHtml(content);
33
- if (urlOrPath) {
34
- return makeRelativeUrlsAbsolute(value, urlOrPath);
44
+ // Check if content is directly JSON/YAML
45
+ try {
46
+ // Try parsing as JSON
47
+ const jsonContent = JSON.parse(content);
48
+ if (jsonContent.openapi || jsonContent.swagger) {
49
+ return value;
50
+ }
51
+ }
52
+ catch {
53
+ // Try parsing as YAML
54
+ try {
55
+ const yamlContent = parse(content);
56
+ if (yamlContent?.openapi || yamlContent?.swagger) {
57
+ return value;
58
+ }
59
+ }
60
+ catch {
61
+ // Not YAML
62
+ }
63
+ }
64
+ const urlOrPathOrDocument = parseHtml(content);
65
+ // Document (string)
66
+ if (typeof urlOrPathOrDocument === 'string') {
67
+ try {
68
+ // JSON?
69
+ return JSON.parse(urlOrPathOrDocument);
70
+ }
71
+ catch {
72
+ // No JSON
73
+ try {
74
+ // YAML?
75
+ const yaml = parse(urlOrPathOrDocument);
76
+ if (typeof yaml === 'object') {
77
+ return yaml;
78
+ }
79
+ }
80
+ catch {
81
+ // Not YAML
82
+ }
83
+ }
35
84
  }
36
- // New: Check for embedded OpenAPI document
85
+ // Document (object)
86
+ if (typeof urlOrPathOrDocument === 'object') {
87
+ return urlOrPathOrDocument;
88
+ }
89
+ // Relative or absolute URL
90
+ if (urlOrPathOrDocument) {
91
+ return makeRelativeUrlsAbsolute(value, urlOrPathOrDocument);
92
+ }
93
+ // Check for embedded OpenAPI document
37
94
  const embeddedSpec = parseEmbeddedOpenApi(content);
38
95
  if (embeddedSpec) {
39
96
  return embeddedSpec;
@@ -57,10 +114,13 @@ function parseHtml(html) {
57
114
  if (!html?.includes('<')) {
58
115
  return undefined;
59
116
  }
60
- // data-url="*"
61
- const dataUrlMatch = html.match(/data-url=["']([^"']+)["']/);
62
- if (dataUrlMatch?.[1]) {
63
- return dataUrlMatch[1];
117
+ // id="api-reference" data-url="*"
118
+ const dataUrlMatch = html
119
+ .match(/id=["']api-reference["'][\s\S]*?data-url=["']([^"']+)["']/)
120
+ ?.slice(1)
121
+ .find(Boolean);
122
+ if (dataUrlMatch) {
123
+ return dataUrlMatch;
64
124
  }
65
125
  // spec-url="*"
66
126
  const specUrlMatch = html.match(/spec-url=["']([^"']+)["']/);
@@ -73,9 +133,52 @@ function parseHtml(html) {
73
133
  return redocInit[1];
74
134
  }
75
135
  // &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];
136
+ const doubleEncodedConfigurationUrl = html.match(/&amp;quot;url&amp;quot;:&amp;quot;([^;]+)&amp;quot;/);
137
+ if (doubleEncodedConfigurationUrl?.[1]) {
138
+ return doubleEncodedConfigurationUrl[1];
139
+ }
140
+ // &amp;quot;url&amp;quot;:&amp;quot;MY_CUSTOM_URL&amp;quot;
141
+ const encodedConfigurationUrl = html.match(/&quot;url&quot;:&quot;([^;]+)&quot;/);
142
+ if (encodedConfigurationUrl?.[1]) {
143
+ return encodedConfigurationUrl[1];
144
+ }
145
+ // Try to find embedded OpenAPI document in script tag first
146
+ const scriptContent = parseScriptContent(html);
147
+ if (scriptContent) {
148
+ return scriptContent;
149
+ }
150
+ // Check for configuration in script tag
151
+ const scriptConfigMatch = html.match(/url:\s*["']([^"']+)["']/i);
152
+ if (scriptConfigMatch?.[1]) {
153
+ return scriptConfigMatch?.[1];
154
+ }
155
+ // Check for OpenAPI URLs in the HTML
156
+ const linkMatch = html.match(/<a[^>]*href=["']([^"']+\.(?:yaml|yml|json))["'][^>]*>/i);
157
+ if (linkMatch?.[1]) {
158
+ return linkMatch[1];
159
+ }
160
+ return undefined;
161
+ }
162
+ /**
163
+ * Parse OpenAPI document directly from script tag content
164
+ */
165
+ function parseScriptContent(html) {
166
+ const match = html.match(/<script[^>]*id="api-reference"[^>]*>([\s\S]*?)<\/script>/);
167
+ if (!match?.[1])
168
+ return undefined;
169
+ try {
170
+ const content = match[1].trim();
171
+ if (content) {
172
+ try {
173
+ return JSON.parse(content);
174
+ }
175
+ catch {
176
+ return parse(content);
177
+ }
178
+ }
179
+ }
180
+ catch (error) {
181
+ console.error('[@scalar/import] Failed to parse script content:', error);
79
182
  }
80
183
  return undefined;
81
184
  }
@@ -102,13 +205,19 @@ function makeRelativeUrlsAbsolute(baseUrl, path) {
102
205
  * Parse embedded OpenAPI document from HTML
103
206
  */
104
207
  function parseEmbeddedOpenApi(html) {
105
- const match = html.match(/<script[^>]*data-configuration=['"]([^'"]+)['"][^>]*>(.*?)<\/script>/);
208
+ const match = html.match(/<script[^>]*data-configuration=['"]([^'"]+)['"][^>]*>(.*?)<\/script>/s);
106
209
  if (!match)
107
210
  return undefined;
108
211
  try {
109
212
  const configString = decodeHtmlEntities(match[1]);
110
213
  const config = JSON.parse(configString);
214
+ // Handle both direct JSON content and YAML content
111
215
  if (config.spec?.content) {
216
+ // If content is a string, assume it's YAML
217
+ if (typeof config.spec.content === 'string') {
218
+ return parse(config.spec.content);
219
+ }
220
+ // If content is an object, return it directly
112
221
  return config.spec.content;
113
222
  }
114
223
  }
@@ -128,7 +237,15 @@ function decodeHtmlEntities(text) {
128
237
  '&quot;': '"',
129
238
  '&#39;': "'",
130
239
  };
131
- return text.replace(new RegExp(Object.keys(entities).join('|'), 'g'), (match) => entities[match]);
240
+ const updatedText = text
241
+ .replace(new RegExp(Object.keys(entities).join('|'), 'g'), (match) => entities[match])
242
+ .replace(/\n/g, '\\n')
243
+ .trim();
244
+ if (updatedText.startsWith('{&quot;')) {
245
+ // console.log('ok', updatedText)
246
+ return decodeHtmlEntities(updatedText);
247
+ }
248
+ return updatedText;
132
249
  }
133
250
  /**
134
251
  * Transform GitHub URLs to raw file URLs, preserving the branch information
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "postman",
17
17
  "scalar"
18
18
  ],
19
- "version": "0.0.3",
19
+ "version": "0.0.4",
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
@@ -35,6 +35,7 @@
35
35
  ],
36
36
  "module": "dist/index.js",
37
37
  "dependencies": {
38
+ "yaml": "^2.4.5",
38
39
  "@scalar/openapi-parser": "0.8.8"
39
40
  },
40
41
  "devDependencies": {