@elyracode/http-tools 0.4.6
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/README.md +57 -0
- package/extensions/index.ts +395 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @elyracode/http-tools
|
|
2
|
+
|
|
3
|
+
HTTP tools for Elyra -- API testing, live documentation fetching, and OpenAPI spec parsing.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
elyra install npm:@elyracode/http-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| `execute_http_request` | Send HTTP requests (GET, POST, PUT, PATCH, DELETE) with headers, body, and auth. Returns status, headers, and body. |
|
|
16
|
+
| `read_url_content` | Fetch a URL and convert HTML to readable text. Perfect for reading live documentation. |
|
|
17
|
+
| `inspect_openapi` | Fetch and parse OpenAPI/Swagger specs. Returns structured endpoint summaries. |
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- `/http` -- Interactive selector for all HTTP tools
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### API Testing
|
|
26
|
+
```
|
|
27
|
+
> Test POST localhost:8000/api/login with email and password
|
|
28
|
+
> Send a GET to /api/users with Bearer token authentication
|
|
29
|
+
> Test all CRUD endpoints for the products API
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The agent sends the request, reads the response, and if something fails, it can check your logs and fix the code.
|
|
33
|
+
|
|
34
|
+
### Live Documentation
|
|
35
|
+
```
|
|
36
|
+
> Read the Laravel Reverb docs and show me how to set it up
|
|
37
|
+
> Fetch the latest React 19 changelog
|
|
38
|
+
> What's new in Tailwind CSS 4?
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The agent fetches live documentation, so it always has current information -- even for features released after its training cutoff.
|
|
42
|
+
|
|
43
|
+
### OpenAPI / Swagger
|
|
44
|
+
```
|
|
45
|
+
> Parse this OpenAPI spec and generate a PHP client with Guzzle
|
|
46
|
+
> Show me all user-related endpoints from the GitHub API
|
|
47
|
+
> Build TypeScript types from this Swagger spec
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The agent reads the entire API definition and generates integration code based on the actual schema.
|
|
51
|
+
|
|
52
|
+
## Security
|
|
53
|
+
|
|
54
|
+
- Requests include a `User-Agent: elyra-http-tools` header
|
|
55
|
+
- 30-second timeout by default (configurable)
|
|
56
|
+
- Large responses are truncated at 50KB (HTTP) or 80KB (URL content)
|
|
57
|
+
- No credentials are stored -- auth headers are passed per-request
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
export default function (elyra: ExtensionAPI): void {
|
|
5
|
+
|
|
6
|
+
// ── Tool 1: execute_http_request ──
|
|
7
|
+
// "Intelligent Postman" -- send HTTP requests and analyze responses
|
|
8
|
+
elyra.registerTool({
|
|
9
|
+
name: "execute_http_request",
|
|
10
|
+
label: "Execute HTTP Request",
|
|
11
|
+
description:
|
|
12
|
+
"Send an HTTP request to any URL and return the response. " +
|
|
13
|
+
"Use this to test API endpoints, verify responses, debug issues. " +
|
|
14
|
+
"Supports GET, POST, PUT, PATCH, DELETE with headers, body, and auth. " +
|
|
15
|
+
"Returns status code, headers, and body. Useful for testing local APIs (localhost).",
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
url: Type.String({ description: "The URL to request (e.g., http://localhost:8000/api/users)" }),
|
|
18
|
+
method: Type.Optional(
|
|
19
|
+
Type.Union(
|
|
20
|
+
[Type.Literal("GET"), Type.Literal("POST"), Type.Literal("PUT"), Type.Literal("PATCH"), Type.Literal("DELETE"), Type.Literal("HEAD"), Type.Literal("OPTIONS")],
|
|
21
|
+
{ description: "HTTP method (default: GET)" },
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
headers: Type.Optional(
|
|
25
|
+
Type.Record(Type.String(), Type.String(), {
|
|
26
|
+
description: "Request headers (e.g., { \"Authorization\": \"Bearer token\", \"Content-Type\": \"application/json\" })",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
body: Type.Optional(
|
|
30
|
+
Type.String({ description: "Request body (JSON string for POST/PUT/PATCH)" }),
|
|
31
|
+
),
|
|
32
|
+
timeout: Type.Optional(
|
|
33
|
+
Type.Number({ description: "Timeout in milliseconds (default: 30000)" }),
|
|
34
|
+
),
|
|
35
|
+
}),
|
|
36
|
+
execute: async (_toolCallId, params) => {
|
|
37
|
+
try {
|
|
38
|
+
const method = params.method ?? "GET";
|
|
39
|
+
const headers: Record<string, string> = {
|
|
40
|
+
"User-Agent": "elyra-http-tools",
|
|
41
|
+
...params.headers,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Auto-set Content-Type for body requests if not specified
|
|
45
|
+
if (params.body && !headers["Content-Type"] && !headers["content-type"]) {
|
|
46
|
+
headers["Content-Type"] = "application/json";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(params.url, {
|
|
50
|
+
method,
|
|
51
|
+
headers,
|
|
52
|
+
body: params.body ?? undefined,
|
|
53
|
+
signal: AbortSignal.timeout(params.timeout ?? 30000),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const responseHeaders: Record<string, string> = {};
|
|
57
|
+
response.headers.forEach((value, key) => {
|
|
58
|
+
responseHeaders[key] = value;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
62
|
+
let body: string;
|
|
63
|
+
|
|
64
|
+
if (contentType.includes("application/json")) {
|
|
65
|
+
const json = await response.json();
|
|
66
|
+
body = JSON.stringify(json, null, 2);
|
|
67
|
+
} else {
|
|
68
|
+
body = await response.text();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Truncate large responses
|
|
72
|
+
const truncated = body.length > 50000
|
|
73
|
+
? `${body.slice(0, 50000)}\n\n... (truncated, ${body.length} chars total)`
|
|
74
|
+
: body;
|
|
75
|
+
|
|
76
|
+
const lines: string[] = [
|
|
77
|
+
`${method} ${params.url}`,
|
|
78
|
+
`Status: ${response.status} ${response.statusText}`,
|
|
79
|
+
"",
|
|
80
|
+
"Response Headers:",
|
|
81
|
+
...Object.entries(responseHeaders).map(([k, v]) => ` ${k}: ${v}`),
|
|
82
|
+
"",
|
|
83
|
+
"Body:",
|
|
84
|
+
truncated,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
89
|
+
details: {
|
|
90
|
+
status: response.status,
|
|
91
|
+
statusText: response.statusText,
|
|
92
|
+
contentType,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: `HTTP request failed: ${msg}` }],
|
|
99
|
+
details: {},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ── Tool 2: read_url_content ──
|
|
106
|
+
// Fetch a URL and convert to readable text (strips HTML tags)
|
|
107
|
+
elyra.registerTool({
|
|
108
|
+
name: "read_url_content",
|
|
109
|
+
label: "Read URL Content",
|
|
110
|
+
description:
|
|
111
|
+
"Fetch a web page or documentation URL and return its text content. " +
|
|
112
|
+
"HTML is stripped to readable text. Use this to read live documentation, " +
|
|
113
|
+
"blog posts, changelogs, or any web content that may be newer than your training data. " +
|
|
114
|
+
"Especially useful for reading framework docs (Laravel, React, etc.) to get up-to-date information.",
|
|
115
|
+
parameters: Type.Object({
|
|
116
|
+
url: Type.String({ description: "The URL to fetch (e.g., https://laravel.com/docs/13.x/reverb)" }),
|
|
117
|
+
selector: Type.Optional(
|
|
118
|
+
Type.String({
|
|
119
|
+
description: "CSS-like content hint: 'main', 'article', 'body'. Used to focus on main content and skip navigation/footer (default: auto-detect)",
|
|
120
|
+
}),
|
|
121
|
+
),
|
|
122
|
+
}),
|
|
123
|
+
execute: async (_toolCallId, params) => {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(params.url, {
|
|
126
|
+
headers: {
|
|
127
|
+
"User-Agent": "elyra-http-tools (documentation reader)",
|
|
128
|
+
Accept: "text/html, text/plain, application/json, text/markdown",
|
|
129
|
+
},
|
|
130
|
+
signal: AbortSignal.timeout(30000),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: `Failed to fetch ${params.url}: ${response.status} ${response.statusText}` }],
|
|
136
|
+
details: {},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
141
|
+
let text: string;
|
|
142
|
+
|
|
143
|
+
if (contentType.includes("application/json")) {
|
|
144
|
+
const json = await response.json();
|
|
145
|
+
text = JSON.stringify(json, null, 2);
|
|
146
|
+
} else {
|
|
147
|
+
const html = await response.text();
|
|
148
|
+
if (contentType.includes("text/html")) {
|
|
149
|
+
text = htmlToText(html, params.selector);
|
|
150
|
+
} else {
|
|
151
|
+
text = html;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Truncate
|
|
156
|
+
const truncated = text.length > 80000
|
|
157
|
+
? `${text.slice(0, 80000)}\n\n... (truncated, ${text.length} chars total)`
|
|
158
|
+
: text;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: `# Content from ${params.url}\n\n${truncated}` }],
|
|
162
|
+
details: { url: params.url, length: text.length },
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: "text", text: `Failed to read ${params.url}: ${msg}` }],
|
|
168
|
+
details: {},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ── Tool 3: inspect_openapi ──
|
|
175
|
+
// Fetch and parse an OpenAPI/Swagger spec
|
|
176
|
+
elyra.registerTool({
|
|
177
|
+
name: "inspect_openapi",
|
|
178
|
+
label: "Inspect OpenAPI Spec",
|
|
179
|
+
description:
|
|
180
|
+
"Fetch an OpenAPI/Swagger specification from a URL and return a structured summary " +
|
|
181
|
+
"of all endpoints, methods, parameters, request bodies, and response schemas. " +
|
|
182
|
+
"Use this when the user wants to build an API client, generate SDK code, " +
|
|
183
|
+
"or understand a third-party API structure.",
|
|
184
|
+
parameters: Type.Object({
|
|
185
|
+
url: Type.String({
|
|
186
|
+
description: "URL to the OpenAPI/Swagger JSON or YAML spec (e.g., https://petstore.swagger.io/v2/swagger.json)",
|
|
187
|
+
}),
|
|
188
|
+
filter: Type.Optional(
|
|
189
|
+
Type.String({
|
|
190
|
+
description: "Filter endpoints by path prefix (e.g., '/users', '/orders'). Omit for all endpoints.",
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
}),
|
|
194
|
+
execute: async (_toolCallId, params) => {
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch(params.url, {
|
|
197
|
+
headers: {
|
|
198
|
+
"User-Agent": "elyra-http-tools",
|
|
199
|
+
Accept: "application/json, application/yaml, text/yaml",
|
|
200
|
+
},
|
|
201
|
+
signal: AbortSignal.timeout(30000),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: `Failed to fetch spec: ${response.status} ${response.statusText}` }],
|
|
207
|
+
details: {},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const spec = await response.json() as Record<string, unknown>;
|
|
212
|
+
const info = spec.info as Record<string, unknown> | undefined;
|
|
213
|
+
const paths = spec.paths as Record<string, Record<string, unknown>> | undefined;
|
|
214
|
+
|
|
215
|
+
if (!paths) {
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: "text", text: "Invalid OpenAPI spec: no 'paths' found." }],
|
|
218
|
+
details: {},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const lines: string[] = [
|
|
223
|
+
`# OpenAPI: ${(info?.title as string) ?? "Unknown API"}`,
|
|
224
|
+
`Version: ${(info?.version as string) ?? "unknown"}`,
|
|
225
|
+
`Description: ${(info?.description as string) ?? "none"}`,
|
|
226
|
+
`Base URL: ${extractBaseUrl(spec)}`,
|
|
227
|
+
"",
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
let endpointCount = 0;
|
|
231
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
232
|
+
if (params.filter && !path.startsWith(params.filter)) continue;
|
|
233
|
+
|
|
234
|
+
for (const [method, details] of Object.entries(methods)) {
|
|
235
|
+
if (method.startsWith("x-") || typeof details !== "object" || !details) continue;
|
|
236
|
+
const op = details as Record<string, unknown>;
|
|
237
|
+
endpointCount++;
|
|
238
|
+
const summary = (op.summary as string) ?? "";
|
|
239
|
+
const operationId = (op.operationId as string) ?? "";
|
|
240
|
+
lines.push(`## ${method.toUpperCase()} ${path}`);
|
|
241
|
+
if (summary) lines.push(`Summary: ${summary}`);
|
|
242
|
+
if (operationId) lines.push(`Operation: ${operationId}`);
|
|
243
|
+
|
|
244
|
+
// Parameters
|
|
245
|
+
const parameters = op.parameters as Array<Record<string, unknown>> | undefined;
|
|
246
|
+
if (parameters && parameters.length > 0) {
|
|
247
|
+
lines.push("Parameters:");
|
|
248
|
+
for (const param of parameters) {
|
|
249
|
+
const required = param.required ? " (required)" : "";
|
|
250
|
+
lines.push(` - ${param.name} [${param.in}]: ${param.description ?? (param.schema as Record<string, unknown>)?.type ?? "unknown"}${required}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Request body
|
|
255
|
+
const requestBody = op.requestBody as Record<string, unknown> | undefined;
|
|
256
|
+
if (requestBody) {
|
|
257
|
+
const content = requestBody.content as Record<string, Record<string, unknown>> | undefined;
|
|
258
|
+
if (content) {
|
|
259
|
+
const jsonSchema = content["application/json"]?.schema;
|
|
260
|
+
if (jsonSchema) {
|
|
261
|
+
lines.push(`Request Body: ${JSON.stringify(jsonSchema, null, 2).slice(0, 500)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Response codes
|
|
267
|
+
const responses = op.responses as Record<string, Record<string, unknown>> | undefined;
|
|
268
|
+
if (responses) {
|
|
269
|
+
const codes = Object.keys(responses).join(", ");
|
|
270
|
+
lines.push(`Responses: ${codes}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
lines.push("");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
lines.unshift(`Endpoints: ${endpointCount}${params.filter ? ` (filtered by ${params.filter})` : ""}`);
|
|
278
|
+
|
|
279
|
+
const text = lines.join("\n");
|
|
280
|
+
const truncated = text.length > 80000
|
|
281
|
+
? `${text.slice(0, 80000)}\n\n... (truncated)`
|
|
282
|
+
: text;
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: truncated }],
|
|
286
|
+
details: { endpointCount, title: info?.title },
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
290
|
+
return {
|
|
291
|
+
content: [{ type: "text", text: `Failed to parse OpenAPI spec: ${msg}` }],
|
|
292
|
+
details: {},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── Command: /http ──
|
|
299
|
+
elyra.registerCommand("http", {
|
|
300
|
+
description: "HTTP tools: test APIs, fetch docs, parse OpenAPI specs",
|
|
301
|
+
handler: async (_args: string, ctx) => {
|
|
302
|
+
const options = [
|
|
303
|
+
"Test API -- send an HTTP request to test an endpoint",
|
|
304
|
+
"Read URL -- fetch web content or documentation",
|
|
305
|
+
"OpenAPI -- parse a Swagger/OpenAPI spec",
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
const selected = await ctx.ui.select("HTTP Tools", options);
|
|
309
|
+
if (!selected) return;
|
|
310
|
+
|
|
311
|
+
if (selected.startsWith("Test API")) {
|
|
312
|
+
elyra.sendUserMessage("I want to test an API endpoint. Ask me for the URL, method, and any headers or body to send.");
|
|
313
|
+
} else if (selected.startsWith("Read URL")) {
|
|
314
|
+
elyra.sendUserMessage("I want to read content from a URL. Ask me which URL to fetch.");
|
|
315
|
+
} else if (selected.startsWith("OpenAPI")) {
|
|
316
|
+
elyra.sendUserMessage("I want to parse an OpenAPI spec. Ask me for the URL to the spec.");
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Strip HTML tags and extract readable text content.
|
|
324
|
+
* Simple but effective for documentation pages.
|
|
325
|
+
*/
|
|
326
|
+
function htmlToText(html: string, selectorHint?: string): string {
|
|
327
|
+
let content = html;
|
|
328
|
+
|
|
329
|
+
// Try to extract main content area
|
|
330
|
+
if (selectorHint) {
|
|
331
|
+
const tagMatch = content.match(new RegExp(`<${selectorHint}[^>]*>([\\s\\S]*?)<\\/${selectorHint}>`, "i"));
|
|
332
|
+
if (tagMatch) content = tagMatch[1];
|
|
333
|
+
} else {
|
|
334
|
+
// Auto-detect: prefer <main>, then <article>, then <body>
|
|
335
|
+
for (const tag of ["main", "article"]) {
|
|
336
|
+
const match = content.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i"));
|
|
337
|
+
if (match) {
|
|
338
|
+
content = match[1];
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Remove script and style blocks
|
|
345
|
+
content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
346
|
+
content = content.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
347
|
+
content = content.replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, "");
|
|
348
|
+
content = content.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, "");
|
|
349
|
+
content = content.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, "");
|
|
350
|
+
|
|
351
|
+
// Convert common elements to text
|
|
352
|
+
content = content.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n## $1\n");
|
|
353
|
+
content = content.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, "\n$1\n");
|
|
354
|
+
content = content.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "- $1\n");
|
|
355
|
+
content = content.replace(/<br\s*\/?>/gi, "\n");
|
|
356
|
+
content = content.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, "\n```\n$1\n```\n");
|
|
357
|
+
content = content.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
|
|
358
|
+
content = content.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, "$2 ($1)");
|
|
359
|
+
content = content.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, "**$1**");
|
|
360
|
+
content = content.replace(/<em[^>]*>([\s\S]*?)<\/em>/gi, "*$1*");
|
|
361
|
+
|
|
362
|
+
// Strip remaining HTML tags
|
|
363
|
+
content = content.replace(/<[^>]+>/g, "");
|
|
364
|
+
|
|
365
|
+
// Clean up HTML entities
|
|
366
|
+
content = content.replace(/&/g, "&");
|
|
367
|
+
content = content.replace(/</g, "<");
|
|
368
|
+
content = content.replace(/>/g, ">");
|
|
369
|
+
content = content.replace(/"/g, '"');
|
|
370
|
+
content = content.replace(/'/g, "'");
|
|
371
|
+
content = content.replace(/ /g, " ");
|
|
372
|
+
|
|
373
|
+
// Clean up whitespace
|
|
374
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
375
|
+
content = content.trim();
|
|
376
|
+
|
|
377
|
+
return content;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function extractBaseUrl(spec: Record<string, unknown>): string {
|
|
381
|
+
// OpenAPI 3.x
|
|
382
|
+
const servers = spec.servers as Array<Record<string, unknown>> | undefined;
|
|
383
|
+
if (servers && servers.length > 0) {
|
|
384
|
+
return (servers[0].url as string) ?? "";
|
|
385
|
+
}
|
|
386
|
+
// Swagger 2.x
|
|
387
|
+
const host = spec.host as string | undefined;
|
|
388
|
+
const basePath = spec.basePath as string | undefined;
|
|
389
|
+
const schemes = spec.schemes as string[] | undefined;
|
|
390
|
+
if (host) {
|
|
391
|
+
const scheme = schemes?.[0] ?? "https";
|
|
392
|
+
return `${scheme}://${host}${basePath ?? ""}`;
|
|
393
|
+
}
|
|
394
|
+
return "";
|
|
395
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elyracode/http-tools",
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Elyra extension for HTTP requests -- API testing, live docs fetching, and OpenAPI parsing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"elyra-package",
|
|
8
|
+
"http",
|
|
9
|
+
"api-testing",
|
|
10
|
+
"openapi",
|
|
11
|
+
"swagger",
|
|
12
|
+
"fetch"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Knut W. Horne",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/kwhorne/elyra.git",
|
|
19
|
+
"directory": "packages/http-tools"
|
|
20
|
+
},
|
|
21
|
+
"elyra": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./extensions/index.ts"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@elyracode/coding-agent": "*",
|
|
28
|
+
"typebox": "*"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "echo 'nothing to clean'",
|
|
32
|
+
"build": "echo 'nothing to build'",
|
|
33
|
+
"check": "echo 'nothing to check'"
|
|
34
|
+
}
|
|
35
|
+
}
|