diff --git a/packages/server/src/services/openapi-parser.ts b/packages/server/src/services/openapi-parser.ts index cfdc00e..452b687 100644 --- a/packages/server/src/services/openapi-parser.ts +++ b/packages/server/src/services/openapi-parser.ts @@ -115,8 +115,19 @@ function parseOpenApi3Endpoints(api: OpenApiDoc): { endpoints: ParsedEndpoint[]; } export async function parseOpenApiDocument(input: string | object): Promise { - // Parse and dereference all $refs inline - const api = await SwaggerParser.dereference(input as any, { + let specInput: string | object = input; + + // If input is a URL, fetch the content first so that swagger-parser + // works on a plain object and doesn't need network access for $ref resolution + if (typeof input === 'string' && input.startsWith('http')) { + const res = await fetch(input); + if (!res.ok) throw new Error(`Failed to fetch spec from URL: ${res.status} ${res.statusText}`); + specInput = await res.json(); + } + + // Bundle resolves all $refs into a single document, then dereference inlines them + const bundled = await SwaggerParser.bundle(specInput as any) as OpenAPI.Document; + const api = await SwaggerParser.dereference(bundled, { dereference: { circular: 'ignore' }, }) as OpenAPI.Document; diff --git a/packages/web/src/pages/ImportDialog.tsx b/packages/web/src/pages/ImportDialog.tsx index 674b909..271a111 100644 --- a/packages/web/src/pages/ImportDialog.tsx +++ b/packages/web/src/pages/ImportDialog.tsx @@ -31,20 +31,14 @@ export default function ImportDialog({ onClose }: { onClose: () => void }) { setLoading(true); setError(''); try { - let spec: unknown; - + let body: Record; if (mode === 'url') { - // Fetch from browser (can access local network) instead of letting server fetch - const res = await fetch(url); - if (!res.ok) throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`); - const text = await res.text(); - try { spec = JSON.parse(text); } catch { spec = text; } + body = { specUrl: url }; } else { - try { spec = JSON.parse(fileContent); } catch { spec = fileContent; } + try { body = { spec: JSON.parse(fileContent) }; } catch { body = { spec: fileContent }; } } - const data = await apiFetch('/projects', { - method: 'POST', body: JSON.stringify({ spec }), + method: 'POST', body: JSON.stringify(body), }); setResult(data); queryClient.invalidateQueries({ queryKey: ['projects'] });