feat(scenario): let context.downloadFile save data: URLs as run artifacts

Scenario steps can now persist in-script generated content (e.g. an
obtained VC's JWT) as a run file via context.downloadFile('data:...'),
without needing a network fetch. Bumps to 1.8.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Andrii Arsenin
2026-09-15 10:37:22 +03:00
co-authored by Claude Sonnet 5
parent 502a3e4f2c
commit 622dfdbfb0
4 changed files with 31 additions and 1 deletions
+12
View File
@@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file.
## 1.8.0 - 2026-09-15
Changes since 1.7.1:
### Added
- `context.downloadFile()` now accepts `data:` URIs, so scenario steps can save in-script generated content (e.g. a credential JWT) as a run artifact without a network fetch.
### Changed
- Documented the full scenario step `context` file API (`getScenarioFiles`, `downloadFile`) in `docs/scenario.md`.
## 1.7.1 - 2026-04-21
Changes since 1.7.0:
+2
View File
@@ -36,6 +36,8 @@ Available in scope:
- `context.dumpDom(selector?)`: simplified DOM snapshot
- `context.log(...args)`, `context.warn(...args)`, `context.error(...args)`: structured step logs
- `context.runSnippet(name, ...args)`: execute stored snippet code with the same context
- `context.getScenarioFiles(opts?)`: list files uploaded to the scenario (`limit`, `offset`)
- `context.downloadFile(url, opts?)`: fetch `url` and save the result as a run artifact (requires a real scenario run — throws when invoked ad hoc, e.g. via the `exec_code` MCP tool). `opts` may include `method`, `headers`, `body`, `filename`. `url` also accepts a `data:` URI (`data:<mediaType>;base64,<data>` or `data:<mediaType>,<percent-encoded data>`) to save content generated in-script (e.g. a credential JWT) directly, without an actual network fetch.
- `console`: proxied to run logs (`log`, `warn`, `error`, etc.)
`validateCode` additionally receives `result` in scope, which is the value returned by `execCode`.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "liqa",
"version": "1.7.1",
"version": "1.8.0",
"private": true,
"workspaces": [
"server",
+16
View File
@@ -5,6 +5,18 @@ import { URL } from "url";
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB
const TIMEOUT_MS = 30000; // 30 seconds
function decodeDataUrl(urlStr: string): Buffer {
const match = /^data:([^,]*),(.*)$/s.exec(urlStr);
if (!match) {
throw new Error("Malformed data: URL");
}
const [, meta, data] = match;
if (meta.endsWith(";base64")) {
return Buffer.from(data, "base64");
}
return Buffer.from(decodeURIComponent(data), "utf-8");
}
export async function downloadFile(
urlStr: string,
options?: {
@@ -13,6 +25,10 @@ export async function downloadFile(
body?: string;
},
): Promise<Buffer> {
if (urlStr.startsWith("data:")) {
return decodeDataUrl(urlStr);
}
const url = new URL(urlStr);
const protocol = url.protocol === "https:" ? https : http;
const method = options?.method ?? "GET";