mcprigor 1.4.0 → 1.5.0
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 +19 -2
- package/dist/audit.d.ts +44 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +157 -0
- package/dist/audit.js.map +1 -0
- package/dist/cli.js +243 -10
- package/dist/cli.js.map +1 -1
- package/dist/composition.d.ts +42 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +130 -0
- package/dist/composition.js.map +1 -0
- package/dist/coverage.d.ts +33 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +83 -0
- package/dist/coverage.js.map +1 -0
- package/dist/export.d.ts +4 -0
- package/dist/export.d.ts.map +1 -1
- package/dist/export.js +56 -0
- package/dist/export.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/language.js +1 -1
- package/dist/language.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -1
- package/dist/loader.js.map +1 -1
- package/dist/monitor.d.ts +22 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +71 -0
- package/dist/monitor.js.map +1 -0
- package/dist/perf.d.ts +37 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +90 -0
- package/dist/perf.js.map +1 -0
- package/dist/publish.d.ts +19 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +58 -0
- package/dist/publish.js.map +1 -0
- package/dist/qa-language.d.ts.map +1 -1
- package/dist/qa-language.js +46 -1
- package/dist/qa-language.js.map +1 -1
- package/dist/reporters.d.ts +3 -1
- package/dist/reporters.d.ts.map +1 -1
- package/dist/reporters.js +21 -3
- package/dist/reporters.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +31 -15
- package/dist/runner.js.map +1 -1
- package/dist/schema.d.ts +47 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +10 -1
- package/dist/schema.js.map +1 -1
- package/dist/timeline.d.ts +18 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +59 -0
- package/dist/timeline.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +35 -3
- package/dist/workspace.js.map +1 -1
- package/docs/CLI-REFERENCE.md +113 -1
- package/docs/COVERAGE.md +37 -0
- package/docs/ENGINEER-SETUP.md +1 -1
- package/docs/EVIDENCE.md +11 -0
- package/docs/GITHUB-ACTION.md +82 -0
- package/docs/LANGUAGE-SPEC.md +28 -7
- package/docs/MONITORING.md +62 -0
- package/docs/MULTI-SERVER-COMPOSITIONS.md +103 -0
- package/docs/PERFORMANCE-GOVERNANCE.md +85 -0
- package/docs/PLAIN-LANGUAGE-COOKBOOK.md +28 -0
- package/docs/PUBLISHING.md +53 -0
- package/docs/QA-WORKSPACE.md +5 -1
- package/docs/README.md +7 -0
- package/docs/SECURITY-AUDIT.md +77 -0
- package/editors/mcpr-language.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/syntaxes/mcpr.tmLanguage.json +2 -2
- package/package.json +1 -2
- package/workspace-assets/app.js +35 -4
- package/workspace-assets/index.html +3 -0
- package/workspace-assets/style.css +2 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Shareable hosted reports
|
|
2
|
+
|
|
3
|
+
> Available since `1.5.0`.
|
|
4
|
+
|
|
5
|
+
Turn a test run into a URL anyone can open — no repository access, no CI login:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export NETLIFY_AUTH_TOKEN=... # personal or CI token
|
|
9
|
+
mcprigor publish tests/catalog.mcpr --site your-netlify-site
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`publish` runs the suite, builds the readable HTML report **with the clickable request/response session timeline**, deploys it to your Netlify site with the dependency-free digest API, waits until the deploy is live, and prints the shareable URL:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Published report: https://68b1c2--your-site.netlify.app
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Each publish is a normal Netlify deploy of your own site, so access control, custom domains, deploy previews, and retention follow your existing hosting configuration. Unchanged files are skipped automatically via content digests.
|
|
19
|
+
|
|
20
|
+
## Options
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
mcprigor publish suite.mcpr --site SITE [--include-json] [--test NAME] [--env qa]
|
|
24
|
+
mcprigor publish suite.mcpr --out reports/latest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `--site` — Netlify site ID or name. The token comes only from `NETLIFY_AUTH_TOKEN` (or `MCPRIGOR_PUBLISH_TOKEN`); tokens are never accepted as command-line flags.
|
|
28
|
+
- `--out DIR` — write the same bundle to a local directory instead of (or in addition to) hosting it. Serve it from any static host: S3, GitHub Pages, nginx, an artifact store.
|
|
29
|
+
- `--include-json` — also publish `result.json` for dashboards and programmatic consumers.
|
|
30
|
+
- `--test`, `--env`, `--command`, `--url` — the same run-selection options as `mcprigor test`.
|
|
31
|
+
|
|
32
|
+
The exit code still reflects the run (`0` passed, `1` failed), so `publish` can replace `test` in a pipeline step that both gates and shares.
|
|
33
|
+
|
|
34
|
+
## Publishing from the QA workspace UI
|
|
35
|
+
|
|
36
|
+
The visual workspace (`mcprigor workspace`) exposes the same features without the command line:
|
|
37
|
+
|
|
38
|
+
- **HTML report** — every finished test run has an `HTML report` button that opens the full report, including the clickable session timeline, in a new tab.
|
|
39
|
+
- **Publish** — start the workspace with hosting configured and a `Publish` button appears next to the export buttons:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export MCPRIGOR_PUBLISH_SITE=your-netlify-site
|
|
43
|
+
export NETLIFY_AUTH_TOKEN=...
|
|
44
|
+
mcprigor workspace
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Clicking it deploys the selected run's report and opens the shareable URL; a `View published ↗` link stays on the run for re-opening or copying. Without both variables the button is hidden and the API answers with a clear configuration message — the token itself never reaches the browser.
|
|
48
|
+
|
|
49
|
+
## Security
|
|
50
|
+
|
|
51
|
+
- The report is produced by the same pipeline as `--html`: secrets and configured redaction patterns are removed **before** the report exists.
|
|
52
|
+
- Publishing makes the report as public as the target site. Use a password-protected or team-restricted Netlify site for internal results.
|
|
53
|
+
- The hosting token is read from the environment at the last moment and is never echoed, logged, or stored.
|
package/docs/QA-WORKSPACE.md
CHANGED
|
@@ -30,7 +30,7 @@ An empty folder shows a three-step welcome screen. Choose **+ New test file**
|
|
|
30
30
|
2. Edit the plain-language scenario. The editor provides syntax highlighting, line numbers, and autocomplete: top-level declarations at the start of a line, actions and assertions when indented, and comparison phrases after `Expect "field"`. Accept with `Tab` or `Enter`; force the list open with `Ctrl+Space`.
|
|
31
31
|
3. Choose **Validate** (`Ctrl/⌘+S` saves, `Ctrl/⌘+Enter` runs). A wording problem highlights the offending line and moves the cursor to it.
|
|
32
32
|
4. Choose **▶ Run tests** or **Parity**.
|
|
33
|
-
5. Review the results panel: per-file pass/fail with durations; select a file for its full report.
|
|
33
|
+
5. Review the results panel: per-file pass/fail with durations; select a file for its full report. Drag either panel divider to resize the file list, editor, and results areas; widths persist across reloads, arrow keys resize a focused divider, and double-click resets the layout.
|
|
34
34
|
|
|
35
35
|
The editor marks unsaved changes; running or validating saves them first. If the file changed elsewhere after you opened it, the workspace refuses to overwrite it and asks you to reload.
|
|
36
36
|
|
|
@@ -52,6 +52,10 @@ Test runs are recorded in `.mcprigor/workspace-history.jsonl` (most recent 2000
|
|
|
52
52
|
|
|
53
53
|
One search box filters all three tabs. It matches suite names, test names, and error text, and highlights matches, so you can answer questions like "when did `delivered` start failing?" without leaving the browser.
|
|
54
54
|
|
|
55
|
+
Completed runs expose **PDF**, **CSV**, and **JUnit XML** downloads. The Trends tab exports a rich trends PDF, aggregate CSV, or raw history CSV. PDF reports include summary cards, pass-rate visuals, per-test detail, failures, durations, and evidence identifiers.
|
|
56
|
+
|
|
57
|
+
An **HTML report** button opens the full report — including the clickable request/response session timeline — in a new tab. When the workspace is started with `MCPRIGOR_PUBLISH_SITE` and `NETLIFY_AUTH_TOKEN` set, a **Publish** button also appears: one click hosts the report at a shareable static URL and keeps a `View published ↗` link on the run. See [Shareable hosted reports](PUBLISHING.md).
|
|
58
|
+
|
|
55
59
|
## What is available
|
|
56
60
|
|
|
57
61
|
- Creating, renaming, and editing `.mcpr` suites (YAML and JSON suites are listed and editable too)
|
package/docs/README.md
CHANGED
|
@@ -18,6 +18,12 @@ Choose the path that matches your role.
|
|
|
18
18
|
- [State and dependencies](STATE-AND-DEPENDENCIES.md) — share outputs across tests and runs.
|
|
19
19
|
- [Data engineering](DATA-ENGINEERING.md) — types, filters, joins, samples, and caches.
|
|
20
20
|
- [Transport parity](TRANSPORT-PARITY.md) — compare stdio and Streamable HTTP.
|
|
21
|
+
- [Performance governance](PERFORMANCE-GOVERNANCE.md) — call limits, percentile budgets, and regression gates.
|
|
22
|
+
- [Multi-server compositions](MULTI-SERVER-COMPOSITIONS.md) — route tests across a fleet and gate combined drift.
|
|
23
|
+
- [Coverage](COVERAGE.md) — find untested MCP surfaces and input-schema branches.
|
|
24
|
+
- [Scheduled monitoring](MONITORING.md) — continuously test production HTTP endpoints and notify webhooks.
|
|
25
|
+
- [GitHub Action](GITHUB-ACTION.md) — test, drift, flaky warnings, and rich pull-request comments.
|
|
26
|
+
- [Shareable hosted reports](PUBLISHING.md) — `mcprigor publish` turns a run into a static report URL.
|
|
21
27
|
|
|
22
28
|
## Contracts and evidence
|
|
23
29
|
|
|
@@ -30,6 +36,7 @@ Choose the path that matches your role.
|
|
|
30
36
|
|
|
31
37
|
- [Extension SDK](EXTENSION-SDK.md)
|
|
32
38
|
- [Stable error model](ERROR-MODEL.md)
|
|
39
|
+
- [Deterministic security audit](SECURITY-AUDIT.md) — built-in probe pack, severity gate, and rich reports.
|
|
33
40
|
- [Security and retention](SECURITY-AND-RETENTION.md)
|
|
34
41
|
- [Compatibility policy](COMPATIBILITY.md)
|
|
35
42
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Deterministic security audit
|
|
2
|
+
|
|
3
|
+
> Available since `1.5.0`.
|
|
4
|
+
|
|
5
|
+
`mcprigor audit` runs a fixed security and permissions probe pack against an MCP server and produces an auditable score. No AI chooses payloads or interprets outcomes.
|
|
6
|
+
|
|
7
|
+
## Run the safe default pack
|
|
8
|
+
|
|
9
|
+
Use a suite's configured target:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mcprigor audit tests/server.mcpr
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or supply a target directly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
mcprigor audit --url https://qa.example.com/mcp
|
|
19
|
+
mcprigor audit --command "node dist/server.js"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The non-destructive default probes:
|
|
23
|
+
|
|
24
|
+
| Probe | What MCP Rigor verifies |
|
|
25
|
+
|---|---|
|
|
26
|
+
| Malformed parameters | Invalid `tools/call` parameters are rejected |
|
|
27
|
+
| Tool-name spoofing | An unknown administrative-looking tool cannot be invoked |
|
|
28
|
+
| Oversized payload | A deterministic 1 MiB payload is rejected |
|
|
29
|
+
| Path traversal | Resource reads reject Unix, Windows, and relative traversal URIs |
|
|
30
|
+
| Tool inventory | Advertised tools are listed for opt-in injection/canary testing |
|
|
31
|
+
|
|
32
|
+
## Opt into reviewed tool calls
|
|
33
|
+
|
|
34
|
+
MCP Rigor never guesses that a tool is safe. Tool execution is disabled unless you allow an exact name:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mcprigor audit tests/server.mcpr \
|
|
38
|
+
--allow-tool search \
|
|
39
|
+
--allow-tool summarize
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Only allow read-only, non-destructive tools in a disposable test environment. An allowed tool receives a fixed prompt-injection payload containing a deterministic canary. MCP Rigor checks whether the response follows or reflects the payload and whether it exposes the canary. Report evidence replaces the canary with `[CANARY REDACTED]`.
|
|
43
|
+
|
|
44
|
+
## CI severity gate
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mcprigor audit tests/server.mcpr --fail-on high
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Values are `critical`, `high`, `medium`, `low`, and `none`; the default is `high`. The command exits nonzero when a failed finding meets or exceeds the selected severity.
|
|
51
|
+
|
|
52
|
+
Scoring starts at 100:
|
|
53
|
+
|
|
54
|
+
- critical: −35
|
|
55
|
+
- high: −20
|
|
56
|
+
- medium: −10
|
|
57
|
+
- low: −4
|
|
58
|
+
- skipped: no deduction
|
|
59
|
+
|
|
60
|
+
Skipped probes remain visible with the exact `--allow-tool` needed. They are not represented as passes.
|
|
61
|
+
|
|
62
|
+
## Reports
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
mcprigor audit tests/server.mcpr \
|
|
66
|
+
--pdf reports/security.pdf \
|
|
67
|
+
--json reports/security.json \
|
|
68
|
+
--csv reports/security.csv
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The rich PDF includes score, grade, severity cards, per-finding status, explanations, and bounded evidence. Markdown output is available with `--markdown`.
|
|
72
|
+
|
|
73
|
+
## Interpreting results
|
|
74
|
+
|
|
75
|
+
The audit is deterministic black-box evidence, not a certification or a replacement for threat modeling. A passing probe proves the observed server rejected that exact payload under that exact configuration. Review skipped probes, environment permissions, downstream model behavior, and business authorization separately.
|
|
76
|
+
|
|
77
|
+
Run intrusive tool probes only in an isolated environment. Do not expose production mutation tools through `--allow-tool`.
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"name": "keyword.control.version.mcpr"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"match": "^\\s*(Suite|Server|Server options|MCP URL|Compare target|Target options for|Default timeout|Redact|Snapshots|Ignore snapshot paths|Client behavior|Import flows from|Flow|Inputs|Outputs|Test|Scenario|Id|Depends on|Require|Require protocol|Variables|Skip|Setup|Steps|Cleanup|Data source):?",
|
|
15
|
+
"match": "^\\s*(Suite|Server|Named server|Server options|Server options for|MCP URL|Compare target|Target options for|Default timeout|Budget|Redact|Snapshots|Ignore snapshot paths|Client behavior|Import flows from|Flow|Inputs|Outputs|Test|Scenario|Id|Depends on|Require|Require protocol|Variables|Skip|Setup|Steps|Cleanup|Data source):?",
|
|
16
16
|
"name": "keyword.control.mcpr"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
"match": "^\\s*(Call tool|When the server asks for input|When the server requests sampling|Read resource|Get prompt|Send|Expect|Save|Export|Set|Use flow|For each row|Wait up to|Wait for notification|Subscribe to resource|Unsubscribe from resource|Set log level|List all|Get task|List tasks|Cancel task|From CSV|From JSON|From YAML|From Excel|From REST|Column|Derive|Keep rows where|Sample|Cache this source)\\b",
|
|
19
|
+
"match": "^\\s*(Call tool|On server|When the server asks for input|When the server requests sampling|Read resource|Get prompt|Send|Expect|Save|Export|Set|Use flow|For each row|Wait up to|Wait for notification|Subscribe to resource|Unsubscribe from resource|Set log level|List all|Get task|List tasks|Cancel task|From CSV|From JSON|From YAML|From Excel|From REST|Column|Derive|Keep rows where|Sample|Cache this source)\\b",
|
|
20
20
|
"name": "keyword.other.mcpr"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mcprigor",
|
|
3
3
|
"displayName": "MCP Rigor",
|
|
4
4
|
"description": "Syntax highlighting and inline validation for MCP Rigor (.mcpr) natural-language MCP test suites.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.4.0",
|
|
6
6
|
"publisher": "fusionone",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"repository": { "type": "git", "url": "https://github.com/FusionOnePlatform/mcprigor" },
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"name": "keyword.control.version.mcpr"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"match": "^\\s*(Suite|Server|Server options|MCP URL|Compare target|Target options for|Default timeout|Redact|Snapshots|Ignore snapshot paths|Client behavior|Import flows from|Flow|Inputs|Outputs|Test|Scenario|Id|Depends on|Require|Require protocol|Variables|Skip|Setup|Steps|Cleanup|Data source):?",
|
|
15
|
+
"match": "^\\s*(Suite|Server|Named server|Server options|Server options for|MCP URL|Compare target|Target options for|Default timeout|Budget|Redact|Snapshots|Ignore snapshot paths|Client behavior|Import flows from|Flow|Inputs|Outputs|Test|Scenario|Id|Depends on|Require|Require protocol|Variables|Skip|Setup|Steps|Cleanup|Data source):?",
|
|
16
16
|
"name": "keyword.control.mcpr"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
"match": "^\\s*(Call tool|When the server asks for input|When the server requests sampling|Read resource|Get prompt|Send|Expect|Save|Export|Set|Use flow|For each row|Wait up to|Wait for notification|Subscribe to resource|Unsubscribe from resource|Set log level|List all|Get task|List tasks|Cancel task|From CSV|From JSON|From YAML|From Excel|From REST|Column|Derive|Keep rows where|Sample|Cache this source)\\b",
|
|
19
|
+
"match": "^\\s*(Call tool|On server|When the server asks for input|When the server requests sampling|Read resource|Get prompt|Send|Expect|Save|Export|Set|Use flow|For each row|Wait up to|Wait for notification|Subscribe to resource|Unsubscribe from resource|Set log level|List all|Get task|List tasks|Cancel task|From CSV|From JSON|From YAML|From Excel|From REST|Column|Derive|Keep rows where|Sample|Cache this source)\\b",
|
|
20
20
|
"name": "keyword.other.mcpr"
|
|
21
21
|
},
|
|
22
22
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcprigor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Plain-language MCP testing with isolated extensions and a local QA workspace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
49
49
|
"ajv": "^8.18.0",
|
|
50
|
-
"mcprigor": "^1.3.1",
|
|
51
50
|
"yaml": "^2.8.2"
|
|
52
51
|
},
|
|
53
52
|
"overrides": {
|
package/workspace-assets/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const state = { csrf: '', path: '', etag: '', dirty: false, files: [], selected: new Set(), run: null, history: [], tab: 'run', runSel: -1 };
|
|
1
|
+
const state = { csrf: '', path: '', etag: '', dirty: false, files: [], selected: new Set(), run: null, history: [], tab: 'run', runSel: -1, canPublish: false, publishing: false };
|
|
2
2
|
const $ = id => document.getElementById(id);
|
|
3
3
|
|
|
4
4
|
/* ---------- API ---------- */
|
|
@@ -14,6 +14,7 @@ async function api(path, options = {}) {
|
|
|
14
14
|
async function start() {
|
|
15
15
|
const boot = await api('/api/v1/bootstrap');
|
|
16
16
|
state.csrf = boot.csrf;
|
|
17
|
+
state.canPublish = (boot.capabilities || []).includes('publish');
|
|
17
18
|
$('version').textContent = `v${boot.version}`;
|
|
18
19
|
$('workspace').textContent = `Folder: ${boot.root}`;
|
|
19
20
|
$('connection').classList.add('ok');
|
|
@@ -613,16 +614,46 @@ start().catch(show);
|
|
|
613
614
|
function updateExportBar() {
|
|
614
615
|
const run = state.run;
|
|
615
616
|
const bar = $('export-run');
|
|
616
|
-
const index = state.runSel
|
|
617
|
+
const index = state.runSel >= 0 ? state.runSel : 0;
|
|
617
618
|
const item = run && run.items && run.items[index];
|
|
618
|
-
|
|
619
|
+
const ready = run && run.mode === 'test' && item && item.status !== 'running' && item.tests;
|
|
620
|
+
bar.hidden = !ready;
|
|
621
|
+
const publish = $('publish-run');
|
|
622
|
+
const link = $('published-link');
|
|
623
|
+
publish.hidden = !(ready && state.canPublish);
|
|
624
|
+
publish.disabled = state.publishing;
|
|
625
|
+
publish.textContent = state.publishing ? 'Publishing…' : (item && item.publishedUrl ? 'Publish again' : 'Publish');
|
|
626
|
+
link.hidden = !(ready && item.publishedUrl);
|
|
627
|
+
if (item && item.publishedUrl) link.href = item.publishedUrl;
|
|
619
628
|
}
|
|
620
629
|
function exportRun(format) {
|
|
621
630
|
const run = state.run;
|
|
622
631
|
if (!run) return;
|
|
623
|
-
const index = state.runSel
|
|
632
|
+
const index = state.runSel >= 0 ? state.runSel : 0;
|
|
624
633
|
window.open(`/api/v1/export/run?id=${encodeURIComponent(run.id)}&item=${index}&format=${format}`, '_blank');
|
|
625
634
|
}
|
|
635
|
+
async function publishRun() {
|
|
636
|
+
const run = state.run;
|
|
637
|
+
if (!run || state.publishing) return;
|
|
638
|
+
const index = state.runSel >= 0 ? state.runSel : 0;
|
|
639
|
+
state.publishing = true; updateExportBar();
|
|
640
|
+
try {
|
|
641
|
+
const value = await api('/api/v1/publish', { method: 'POST', body: JSON.stringify({ id: run.id, item: index }) });
|
|
642
|
+
const item = run.items[index];
|
|
643
|
+
if (item) item.publishedUrl = value.url;
|
|
644
|
+
window.open(value.url, '_blank', 'noopener');
|
|
645
|
+
} catch (error) {
|
|
646
|
+
toast(error.message || 'Publish failed', true);
|
|
647
|
+
} finally {
|
|
648
|
+
state.publishing = false; updateExportBar();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
$('export-run-html').onclick = () => {
|
|
652
|
+
const run = state.run;
|
|
653
|
+
if (!run) return;
|
|
654
|
+
window.open(`/api/v1/report?id=${encodeURIComponent(run.id)}&item=${state.runSel >= 0 ? state.runSel : 0}`, '_blank');
|
|
655
|
+
};
|
|
656
|
+
$('publish-run').onclick = publishRun;
|
|
626
657
|
$('export-run-pdf').onclick = () => exportRun('pdf');
|
|
627
658
|
$('export-run-csv').onclick = () => exportRun('csv');
|
|
628
659
|
$('export-run-junit').onclick = () => exportRun('junit');
|
|
@@ -95,6 +95,9 @@
|
|
|
95
95
|
<button id="export-run-pdf" class="ghost small" type="button">PDF</button>
|
|
96
96
|
<button id="export-run-csv" class="ghost small" type="button">CSV</button>
|
|
97
97
|
<button id="export-run-junit" class="ghost small" type="button">JUnit XML</button>
|
|
98
|
+
<button id="export-run-html" class="ghost small" type="button">HTML report</button>
|
|
99
|
+
<button id="publish-run" class="ghost small" type="button" hidden>Publish</button>
|
|
100
|
+
<a id="published-link" class="published-link" target="_blank" rel="noopener" hidden>View published ↗</a>
|
|
98
101
|
</div>
|
|
99
102
|
<div id="run-list"></div>
|
|
100
103
|
<pre id="output" class="output"><span class="muted">Nothing has run yet.
|
|
@@ -232,3 +232,5 @@ nav .suite-row button .rn:hover{opacity:1}
|
|
|
232
232
|
.export-bar{display:flex;align-items:center;gap:6px;padding:8px 14px;border-bottom:1px solid var(--line-soft)}
|
|
233
233
|
.export-label{font-size:11px;color:var(--text-dim);margin-right:2px}
|
|
234
234
|
.export-bar .small{font-size:11px;padding:3px 10px}
|
|
235
|
+
.published-link{font-size:11px;margin-left:4px;color:var(--accent, #2563eb);text-decoration:none}
|
|
236
|
+
.published-link:hover{text-decoration:underline}
|