mirror of
https://github.com/morten-olsen/http.md.git
synced 2026-02-08 00:46:28 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b74a28989 | ||
|
|
ad342e5f10 | ||
|
|
5d485acc97 | ||
|
|
68f5025527 | ||
|
|
a9a7bae28f | ||
|
|
b800290d72 | ||
|
|
d7b6a3880e | ||
|
|
0eff8cf603 | ||
|
|
1b2d345420 |
7
.github/workflows/main.yaml
vendored
7
.github/workflows/main.yaml
vendored
@@ -92,7 +92,7 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
attestations: write
|
attestations: write
|
||||||
id-token: write
|
id-token: write
|
||||||
@@ -135,3 +135,8 @@ jobs:
|
|||||||
pnpm publish --no-git-checks --access public
|
pnpm publish --no-git-checks --access public
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: "docs: generated README"
|
||||||
|
file_pattern: "*.md"
|
||||||
|
|||||||
92
README.md
92
README.md
@@ -110,9 +110,9 @@ HTTP/200 OK
|
|||||||
access-control-allow-credentials: true
|
access-control-allow-credentials: true
|
||||||
access-control-allow-origin: *
|
access-control-allow-origin: *
|
||||||
connection: keep-alive
|
connection: keep-alive
|
||||||
content-length: 559
|
content-length: 555
|
||||||
content-type: application/json
|
content-type: application/json
|
||||||
date: Sun, 18 May 2025 17:17:15 GMT
|
date: Sun, 18 May 2025 19:12:17 GMT
|
||||||
server: gunicorn/19.9.0
|
server: gunicorn/19.9.0
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -129,12 +129,12 @@ server: gunicorn/19.9.0
|
|||||||
"Host": "httpbin.org",
|
"Host": "httpbin.org",
|
||||||
"Sec-Fetch-Mode": "cors",
|
"Sec-Fetch-Mode": "cors",
|
||||||
"User-Agent": "node",
|
"User-Agent": "node",
|
||||||
"X-Amzn-Trace-Id": "Root=1-682a161b-6f8d778138665a8f22ffbe94"
|
"X-Amzn-Trace-Id": "Root=1-682a3111-131bcbff690b03fd64aa4617"
|
||||||
},
|
},
|
||||||
"json": {
|
"json": {
|
||||||
"greeting": "Hello, http.md!"
|
"greeting": "Hello, http.md!"
|
||||||
},
|
},
|
||||||
"origin": "185.181.220.204",
|
"origin": "23.96.180.7",
|
||||||
"url": "https://httpbin.org/post"
|
"url": "https://httpbin.org/post"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,14 +264,14 @@ Within your markdown document, the following variables are available in the Hand
|
|||||||
**1. Using a value from a previous response in a new request:**
|
**1. Using a value from a previous response in a new request:**
|
||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```http id=createItem json
|
```http #createItem,json
|
||||||
POST https://httpbin.org/post
|
POST https://httpbin.org/post
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"name": "My New Item"}
|
{"name": "My New Item"}
|
||||||
```
|
```
|
||||||
|
|
||||||
The new item ID is: {{responses.createItem.body.json.name}}
|
The new item ID is: {{response.body.json.name}}
|
||||||
|
|
||||||
Now, let's fetch the item using a (mocked) ID from the response:
|
Now, let's fetch the item using a (mocked) ID from the response:
|
||||||
|
|
||||||
@@ -283,6 +283,61 @@ GET https://httpbin.org/anything/{{responses.createItem.body.json.name}}
|
|||||||
|
|
||||||
````
|
````
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Output</summary>
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```http
|
||||||
|
POST https://httpbin.org/post
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{"name": "My New Item"}
|
||||||
|
```
|
||||||
|
|
||||||
|
The new item ID is: My New Item
|
||||||
|
|
||||||
|
Now, let's fetch the item using a (mocked) ID from the response:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET https://httpbin.org/anything/My New Item
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP/200 OK
|
||||||
|
access-control-allow-credentials: true
|
||||||
|
access-control-allow-origin: *
|
||||||
|
connection: keep-alive
|
||||||
|
content-length: 451
|
||||||
|
content-type: application/json
|
||||||
|
date: Sun, 18 May 2025 19:12:18 GMT
|
||||||
|
server: gunicorn/19.9.0
|
||||||
|
|
||||||
|
{
|
||||||
|
"args": {},
|
||||||
|
"data": "",
|
||||||
|
"files": {},
|
||||||
|
"form": {},
|
||||||
|
"headers": {
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Accept-Encoding": "br, gzip, deflate",
|
||||||
|
"Accept-Language": "*",
|
||||||
|
"Host": "httpbin.org",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"User-Agent": "node",
|
||||||
|
"X-Amzn-Trace-Id": "Root=1-682a3112-4bbb29111129c1556c487ca1"
|
||||||
|
},
|
||||||
|
"json": null,
|
||||||
|
"method": "GET",
|
||||||
|
"origin": "23.96.180.7",
|
||||||
|
"url": "https://httpbin.org/anything/My New Item"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
*(Note: `httpbin.org/post` wraps the JSON sent in a "json" field in its response. If your API returns the ID directly at the root of the JSON body, you'd use `{{responses.createItem.body.id}}` assuming the `createItem` request had the `json` option.)*
|
*(Note: `httpbin.org/post` wraps the JSON sent in a "json" field in its response. If your API returns the ID directly at the root of the JSON body, you'd use `{{responses.createItem.body.id}}` assuming the `createItem` request had the `json` option.)*
|
||||||
|
|
||||||
**2. Displaying a status code in markdown text:**
|
**2. Displaying a status code in markdown text:**
|
||||||
@@ -292,7 +347,7 @@ GET https://httpbin.org/anything/{{responses.createItem.body.json.name}}
|
|||||||
GET https://httpbin.org/status/201
|
GET https://httpbin.org/status/201
|
||||||
```
|
```
|
||||||
|
|
||||||
The request to `/status/201` completed with status code: **{{response.status}}**.
|
The request to `/status/201` completed with status code: ****.
|
||||||
````
|
````
|
||||||
|
|
||||||
## Managing Documents
|
## Managing Documents
|
||||||
@@ -324,7 +379,7 @@ Let's include some shared requests:
|
|||||||
|
|
||||||
::md[./_shared_requests.md]
|
::md[./_shared_requests.md]
|
||||||
|
|
||||||
The shared GET request returned: {{responses.sharedGetRequest.status}}
|
The shared GET request returned:
|
||||||
|
|
||||||
Now, a request specific to this document:
|
Now, a request specific to this document:
|
||||||
|
|
||||||
@@ -332,7 +387,7 @@ Now, a request specific to this document:
|
|||||||
POST https://httpbin.org/post
|
POST https://httpbin.org/post
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"dataFromMain": "someValue", "sharedUrl": "{{requests.sharedGetRequest.url}}"}
|
{"dataFromMain": "someValue", "sharedUrl": ""}
|
||||||
```
|
```
|
||||||
|
|
||||||
::response
|
::response
|
||||||
@@ -356,8 +411,8 @@ httpmd build mydoc.md output.md -i baseUrl=https://api.production.example.com -i
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```http
|
```http
|
||||||
GET {{input.baseUrl}}/users/1
|
GET /users/1
|
||||||
Authorization: Bearer {{input.apiKey}}
|
Authorization: Bearer
|
||||||
```
|
```
|
||||||
|
|
||||||
::response
|
::response
|
||||||
@@ -406,8 +461,8 @@ You can configure the behavior of each `http` code block by adding options to it
|
|||||||
|
|
||||||
````markdown
|
````markdown
|
||||||
```http id=complexRequest,json,yaml,hidden
|
```http id=complexRequest,json,yaml,hidden
|
||||||
POST {{input.apiEndpoint}}/data
|
POST /data
|
||||||
X-API-Key: {{input.apiKey}}
|
X-API-Key:
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
# Request body written in YAML, will be converted to JSON
|
# Request body written in YAML, will be converted to JSON
|
||||||
@@ -445,6 +500,17 @@ The `::md` directive embeds another markdown document.
|
|||||||
* `hidden`: If present, the actual content (markdown) of the embedded document will not be rendered in the output. However, any `http` requests within the embedded document *are still processed*, and their `request` and `response` data become available in the parent document's templating context (via `requests.id` and `responses.id`). This is useful if you only want to execute the requests from an included file (e.g., a common setup sequence) and use their results, without displaying the embedded file's content.
|
* `hidden`: If present, the actual content (markdown) of the embedded document will not be rendered in the output. However, any `http` requests within the embedded document *are still processed*, and their `request` and `response` data become available in the parent document's templating context (via `requests.id` and `responses.id`). This is useful if you only want to execute the requests from an included file (e.g., a common setup sequence) and use their results, without displaying the embedded file's content.
|
||||||
* Example: `::md[./setup_requests.md]{hidden}`
|
* Example: `::md[./setup_requests.md]{hidden}`
|
||||||
|
|
||||||
|
#### `::input[{name}]` Directive Options
|
||||||
|
|
||||||
|
The `::input` directive is used to declare expected input variables
|
||||||
|
|
||||||
|
* **Variable Name:** The first argument (required) is the name of the variable
|
||||||
|
* Example: `::input[myVariable]` will define `input.myVariable`
|
||||||
|
* `required`: If present it will require that the variable is provided
|
||||||
|
* `default={value}`: Defines the default value if no value has been provided
|
||||||
|
* `format=string|number|bool|json|date`: If provided the value will be parsed using the specified format
|
||||||
|
* \`\`
|
||||||
|
|
||||||
## Command-Line Interface (CLI)
|
## Command-Line Interface (CLI)
|
||||||
|
|
||||||
The `httpmd` tool provides the following commands:
|
The `httpmd` tool provides the following commands:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import 'dotenv/config.js';
|
||||||
import '../dist/cli/cli.js';
|
import '../dist/cli/cli.js';
|
||||||
|
|||||||
@@ -176,6 +176,13 @@ Within your markdown document, the following variables are available in the Hand
|
|||||||
|
|
||||||
::raw-md[./examples/with-template.md]
|
::raw-md[./examples/with-template.md]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Output</summary>
|
||||||
|
|
||||||
|
::raw-md[./examples/with-template.md]{render}
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
_(Note: `httpbin.org/post` wraps the JSON sent in a "json" field in its response. If your API returns the ID directly at the root of the JSON body, you'd use `{{responses.createItem.body.id}}` assuming the `createItem` request had the `json` option.)_
|
_(Note: `httpbin.org/post` wraps the JSON sent in a "json" field in its response. If your API returns the ID directly at the root of the JSON body, you'd use `{{responses.createItem.body.id}}` assuming the `createItem` request had the `json` option.)_
|
||||||
|
|
||||||
**2. Displaying a status code in markdown text:**
|
**2. Displaying a status code in markdown text:**
|
||||||
@@ -202,34 +209,18 @@ The requests from the embedded document are processed, and their `request` and `
|
|||||||
|
|
||||||
Assume `_shared_requests.md` contains:
|
Assume `_shared_requests.md` contains:
|
||||||
|
|
||||||
````markdown
|
::raw-md[./examples/_shared_requests.md]
|
||||||
```http id=sharedGetRequest
|
|
||||||
GET https://httpbin.org/get
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
Then, in `main.md`:
|
Then, in `main.md`:
|
||||||
|
|
||||||
````markdown
|
::raw-md[./examples/with-shared-requests.md]
|
||||||
# Main Document
|
|
||||||
|
|
||||||
Let's include some shared requests:
|
<details>
|
||||||
|
<summary>Output</summary>
|
||||||
|
|
||||||
::md[./_shared_requests.md]
|
::raw-md[./examples/with-shared-requests.md]{render}
|
||||||
|
|
||||||
The shared GET request returned: {{responses.sharedGetRequest.status}}
|
</details>
|
||||||
|
|
||||||
Now, a request specific to this document:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST https://httpbin.org/post
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{"dataFromMain": "someValue", "sharedUrl": "{{requests.sharedGetRequest.url}}"}
|
|
||||||
```
|
|
||||||
|
|
||||||
::response
|
|
||||||
````
|
|
||||||
|
|
||||||
When `main.md` is processed, `_shared_requests.md` will be embedded, its `sharedGetRequest` will be executed, and its data will be available for templating.
|
When `main.md` is processed, `_shared_requests.md` will be embedded, its `sharedGetRequest` will be executed, and its data will be available for templating.
|
||||||
|
|
||||||
|
|||||||
3
docs/examples/_shared_requests.md
Normal file
3
docs/examples/_shared_requests.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```http #sharedGetRequest
|
||||||
|
GET https://httpbin.org/get
|
||||||
|
```
|
||||||
18
docs/examples/with-shared-requests.md
Normal file
18
docs/examples/with-shared-requests.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Main Document
|
||||||
|
|
||||||
|
Let's include some shared requests:
|
||||||
|
|
||||||
|
::md[./_shared_requests.md]
|
||||||
|
|
||||||
|
The shared GET request returned: {{response.statusText}}
|
||||||
|
|
||||||
|
Now, a request specific to this document:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST https://httpbin.org/post
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{"dataFromMain": "someValue", "sharedUrl": "{{requests.sharedGetRequest.url}}"}
|
||||||
|
```
|
||||||
|
|
||||||
|
::response
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
```http id=createItem json
|
```http #createItem,json
|
||||||
POST https://httpbin.org/post
|
POST https://httpbin.org/post
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"name": "My New Item"}
|
{"name": "My New Item"}
|
||||||
```
|
```
|
||||||
|
|
||||||
The new item ID is: {{responses.createItem.body.json.name}}
|
The new item ID is: {{response.body.json.name}}
|
||||||
|
|
||||||
Now, let's fetch the item using a (mocked) ID from the response:
|
Now, let's fetch the item using a (mocked) ID from the response:
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"blessed": "^0.1.81",
|
"blessed": "^0.1.81",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
commander:
|
commander:
|
||||||
specifier: ^14.0.0
|
specifier: ^14.0.0
|
||||||
version: 14.0.0
|
version: 14.0.0
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.5.0
|
||||||
|
version: 16.5.0
|
||||||
eventemitter3:
|
eventemitter3:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
@@ -657,6 +660,10 @@ packages:
|
|||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||||
|
|
||||||
|
dotenv@16.5.0:
|
||||||
|
resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
emoji-regex@8.0.0:
|
emoji-regex@8.0.0:
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
@@ -2077,6 +2084,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
|
|
||||||
|
dotenv@16.5.0: {}
|
||||||
|
|
||||||
emoji-regex@8.0.0: {}
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
emojilib@2.4.0: {}
|
emojilib@2.4.0: {}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Response = {
|
|||||||
statusText: string;
|
statusText: string;
|
||||||
headers: Record<string, string>;
|
headers: Record<string, string>;
|
||||||
body?: string;
|
body?: string;
|
||||||
|
rawBody?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddRequestOptios = {
|
type AddRequestOptios = {
|
||||||
@@ -36,7 +37,7 @@ class Context {
|
|||||||
|
|
||||||
constructor(options: ContextOptions = {}) {
|
constructor(options: ContextOptions = {}) {
|
||||||
this.input = options.input || {};
|
this.input = options.input || {};
|
||||||
this.env = options.env || {};
|
this.env = options.env || process.env;
|
||||||
this.requests = options.requests || {};
|
this.requests = options.requests || {};
|
||||||
this.responses = options.responses || {};
|
this.responses = options.responses || {};
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/execution/handlers/handlers.code.ts
Normal file
34
src/execution/handlers/handlers.code.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Handlebars from "handlebars";
|
||||||
|
import { ExecutionHandler } from "../execution.js";
|
||||||
|
|
||||||
|
const codeHandler: ExecutionHandler = ({
|
||||||
|
node,
|
||||||
|
addStep,
|
||||||
|
}) => {
|
||||||
|
if (node.type !== 'code' || node.lang === 'http') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const optionParts = node.meta?.split(',') || [];
|
||||||
|
const options = Object.fromEntries(
|
||||||
|
optionParts.filter(Boolean).map((option) => {
|
||||||
|
const [key, value] = option.split('=');
|
||||||
|
return [key.trim(), value?.trim() || true];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
addStep({
|
||||||
|
type: 'code',
|
||||||
|
node,
|
||||||
|
action: async ({ context }) => {
|
||||||
|
node.meta = undefined;
|
||||||
|
if (options['no-tmpl'] === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const template = Handlebars.compile(node.value);
|
||||||
|
const content = template(context);
|
||||||
|
node.value = content;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { codeHandler };
|
||||||
@@ -6,85 +6,90 @@ const httpHandler: ExecutionHandler = ({
|
|||||||
node,
|
node,
|
||||||
addStep,
|
addStep,
|
||||||
}) => {
|
}) => {
|
||||||
if (node.type === 'code') {
|
if (node.type !== 'code' || node.lang !== 'http') {
|
||||||
if (node.lang !== 'http') {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const optionParts = node.meta?.split(',') || [];
|
|
||||||
const options = Object.fromEntries(
|
|
||||||
optionParts.filter(Boolean).map((option) => {
|
|
||||||
const [key, value] = option.split('=');
|
|
||||||
return [key.trim(), value?.trim() || true];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
addStep({
|
|
||||||
type: 'http',
|
|
||||||
node,
|
|
||||||
action: async ({ context }) => {
|
|
||||||
if (options.disable === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const template = Handlebars.compile(node.value);
|
|
||||||
const content = template(context);
|
|
||||||
const [head, body] = content.split('\n\n');
|
|
||||||
const [top, ...headerItems] = head.split('\n');
|
|
||||||
const [method, url] = top.split(' ');
|
|
||||||
|
|
||||||
const headers = Object.fromEntries(
|
|
||||||
headerItems.map((header) => {
|
|
||||||
const [key, value] = header.split(':');
|
|
||||||
return [key.trim(), value?.trim() || ''];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let parsedBody = body;
|
|
||||||
if (options.format === 'yaml') {
|
|
||||||
try {
|
|
||||||
const parsed = YAML.parse(body);
|
|
||||||
parsedBody = JSON.stringify(parsed);
|
|
||||||
} catch (error) {
|
|
||||||
parsedBody = `Error parsing YAML: ${error}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
body
|
|
||||||
});
|
|
||||||
|
|
||||||
let responseText = await response.text();
|
|
||||||
if (options.json) {
|
|
||||||
try {
|
|
||||||
responseText = JSON.parse(responseText);
|
|
||||||
} catch (e) {
|
|
||||||
responseText = `Error parsing JSON: ${e}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node.value = content;
|
|
||||||
node.meta = undefined;
|
|
||||||
|
|
||||||
context.addRequest({
|
|
||||||
id: options.id?.toString(),
|
|
||||||
request: {
|
|
||||||
method,
|
|
||||||
url,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
|
||||||
body: responseText,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
const optionParts = node.meta?.split(',') || [];
|
||||||
|
const options = Object.fromEntries(
|
||||||
|
optionParts.filter(Boolean).map((option) => {
|
||||||
|
const [key, value] = option.split('=');
|
||||||
|
return [key.trim(), value?.trim() || true];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let id = options.id?.toString();
|
||||||
|
const idPart = optionParts.find((option) => option.startsWith('#'));
|
||||||
|
if (idPart) {
|
||||||
|
id = idPart.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
addStep({
|
||||||
|
type: 'http',
|
||||||
|
node,
|
||||||
|
action: async ({ context }) => {
|
||||||
|
if (options.disable === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const template = Handlebars.compile(node.value);
|
||||||
|
const content = template(context);
|
||||||
|
const [head, body] = content.split('\n\n');
|
||||||
|
const [top, ...headerItems] = head.split('\n');
|
||||||
|
const [method, ...urlParts] = top.split(' ');
|
||||||
|
const url = urlParts.join(' ').trim();
|
||||||
|
|
||||||
|
const headers = Object.fromEntries(
|
||||||
|
headerItems.map((header) => {
|
||||||
|
const [key, value] = header.split(':');
|
||||||
|
return [key.trim(), value?.trim() || ''];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let parsedBody = body;
|
||||||
|
if (options.format === 'yaml') {
|
||||||
|
try {
|
||||||
|
const parsed = YAML.parse(body);
|
||||||
|
parsedBody = JSON.stringify(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
parsedBody = `Error parsing YAML: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawBody = await response.text();
|
||||||
|
let responseText = rawBody;
|
||||||
|
if (options.json) {
|
||||||
|
try {
|
||||||
|
responseText = JSON.parse(responseText);
|
||||||
|
} catch (e) {
|
||||||
|
responseText = `Error parsing JSON: ${e}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.value = content;
|
||||||
|
node.meta = undefined;
|
||||||
|
|
||||||
|
context.addRequest({
|
||||||
|
id,
|
||||||
|
request: {
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
|
body: responseText,
|
||||||
|
rawBody: rawBody,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { httpHandler };
|
export { httpHandler };
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ExecutionHandler } from "../execution.js";
|
import { ExecutionHandler } from "../execution.js";
|
||||||
import { fileHandler } from "./handlers.file.js";
|
import { fileHandler } from "./handlers.md.js";
|
||||||
import { httpHandler } from "./handlers.http.js";
|
import { httpHandler } from "./handlers.http.js";
|
||||||
import { inputHandler } from "./handlers.input.js";
|
import { inputHandler } from "./handlers.input.js";
|
||||||
import { rawMdHandler } from "./handlers.raw-md.js";
|
import { rawMdHandler } from "./handlers.raw-md.js";
|
||||||
import { responseHandler } from "./handlers.response.js";
|
import { responseHandler } from "./handlers.response.js";
|
||||||
import { textHandler } from "./handlers.text.js";
|
import { textHandler } from "./handlers.text.js";
|
||||||
|
import { codeHandler } from "./handlers.code.js";
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
fileHandler,
|
fileHandler,
|
||||||
@@ -13,6 +14,7 @@ const handlers = [
|
|||||||
textHandler,
|
textHandler,
|
||||||
inputHandler,
|
inputHandler,
|
||||||
rawMdHandler,
|
rawMdHandler,
|
||||||
|
codeHandler,
|
||||||
] satisfies ExecutionHandler[];
|
] satisfies ExecutionHandler[];
|
||||||
|
|
||||||
export { handlers };
|
export { handlers };
|
||||||
|
|||||||
Reference in New Issue
Block a user