diff --git a/.github/workflows/CI_Execution.yml b/.github/workflows/CI_Execution.yml index c5d03246..35bb7abf 100644 --- a/.github/workflows/CI_Execution.yml +++ b/.github/workflows/CI_Execution.yml @@ -124,7 +124,9 @@ jobs: sf plugins link . yarn run test:nuts - name: Archive NUTS results + if: always() uses: actions/upload-artifact@v4 with: name: nuts-report-${{ matrix.os }} path: mochawesome-report + if-no-files-found: warn diff --git a/README.md b/README.md index 8b519961..18fccb64 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Version](https://img.shields.io/npm/v/@provartesting/provardx-cli.svg)](https://npmjs.org/package/@provartesting/provardx-cli) [![Downloads/week](https://img.shields.io/npm/dw/@provartesting/provardx-cli.svg)](https://npmjs.org/package/@provartesting/provardx-cli) [![License](https://img.shields.io/npm/l/@provartesting/provardx-cli.svg)](https://github.com/ProvarTesting/provardx-cli/blob/main/LICENSE.md) +[![Get Access](https://img.shields.io/badge/Quality%20Hub%20API-Get%20Access-blue)](https://aqqlrlhga7.execute-api.us-east-1.amazonaws.com/dev/auth/request-access) # What is the ProvarDX CLI? @@ -10,10 +11,12 @@ The Provar DX CLI is a Salesforce CLI plugin for Provar customers who want to au # Installation, Update, and Uninstall +**Requires Node.js 18–24 (LTS 22 recommended).** Node 25+ is not yet supported due to a breaking change in a transitive dependency. Check with `node --version`. + Install the plugin ```sh-session -$ sf plugins install @provartesting/provardx-cli +$ sf plugins install @provartesting/provardx-cli@beta ``` Update plugins @@ -30,33 +33,56 @@ $ sf plugins uninstall @provartesting/provardx-cli # MCP Server (AI-Assisted Quality) -The Provar DX CLI includes a built-in **Model Context Protocol (MCP) server** that connects AI assistants (Claude Desktop, Claude Code, Cursor) directly to your Provar project. Once connected, an AI agent can inspect your project structure, generate Page Objects and test cases, validate every level of the test hierarchy with quality scores that match the Provar Quality Hub API, and work with NitroX (Hybrid Model) component page objects for LWC, Screen Flow, Industry Components, Experience Cloud, and HTML5. +The Provar DX CLI includes a built-in **Model Context Protocol (MCP) server** that connects AI assistants (Claude Desktop, Claude Code, Cursor) directly to your Provar project. Once connected, an AI agent can inspect your project structure, generate Page Objects and test cases, validate every level of the test hierarchy with quality scores, and work with NitroX (Hybrid Model) component page objects for LWC, Screen Flow, Industry Components, Experience Cloud, and HTML5. + +Validation runs in two modes: **local only** (structural rules, no key required) or **Quality Hub API** (170+ rules, quality scoring — requires a `pv_k_` API key). Don't have an account? **[Request access](https://aqqlrlhga7.execute-api.us-east-1.amazonaws.com/dev/auth/request-access)**. + +## Quick setup + +**Requires:** Provar Automation IDE installed with an activated license. ```sh -sf provar mcp start --allowed-paths /path/to/your/provar/project +# 1. Install the plugin (if not already installed) +sf plugins install @provartesting/provardx-cli@beta + +# 2. (Optional) Authenticate for full 170+ rule validation +sf provar auth login ``` -📖 **See [docs/mcp.md](https://github.com/ProvarTesting/provardx-cli/blob/main/docs/mcp.md) for full setup and tool documentation.** +**Claude Code** — run once to register the server: -## License Validation +```sh +claude mcp add provar -s user -- sf provar mcp start --allowed-paths /path/to/your/provar/project +``` -The MCP server verifies your Provar license before accepting any connections. Validation is automatic — no extra flags are required for standard usage. +**Claude Desktop** — add to your config file and restart the app: -**How it works:** +- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +- Windows: `%APPDATA%\Claude\claude_desktop_config.json` -1. **Auto-detection** — the server reads `~/Provar/.licenses/*.properties` (the same files written by Provar's IDE plugins). If a valid, activated license is found the server starts immediately. -2. **Cache** — successful validations are cached at `~/Provar/.licenses/.mcp-license-cache.json` (2 h TTL). Subsequent starts within the TTL window skip the disk scan. -3. **Grace fallback** — if the IDE license files cannot be found or read and the cache is stale (but ≤ 48 h old), the server starts with a warning on stderr using the cached result so CI pipelines are not broken by transient local file-access issues. -4. **Fail closed** — if no valid license is detected the command exits with a non-zero exit code and a clear error message. +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` -**`NODE_ENV=test` fast-path:** +> **Windows (Claude Desktop):** Use `sf.cmd` instead of `sf` if the server fails to start. -When `NODE_ENV=test` the validation step is skipped entirely. This is intended only for the plugin's own unit-test suite. +📖 **[docs/mcp.md](https://github.com/ProvarTesting/provardx-cli/blob/main/docs/mcp.md) — full setup, all 35+ tools, troubleshooting.** --- # Commands +- [`sf provar auth login`](#sf-provar-auth-login) +- [`sf provar auth rotate`](#sf-provar-auth-rotate) +- [`sf provar auth status`](#sf-provar-auth-status) +- [`sf provar auth clear`](#sf-provar-auth-clear) - [`sf provar mcp start`](#sf-provar-mcp-start) - [`sf provar config get`](#sf-provar-config-get) - [`sf provar config set`](#sf-provar-config-set) @@ -84,6 +110,99 @@ When `NODE_ENV=test` the validation step is skipped entirely. This is intended o - [`sf provar manager test run report`](#sf-provar-manager-test-run-report) _(deprecated — use `sf provar quality-hub test run report`)_ - [`sf provar manager test run abort`](#sf-provar-manager-test-run-abort) _(deprecated — use `sf provar quality-hub test run abort`)_ +## `sf provar auth login` + +Log in to Provar Quality Hub and store your API key. + +``` +USAGE + $ sf provar auth login [--url ] + +FLAGS + --url= Override the Quality Hub API base URL (for non-production environments). + +DESCRIPTION + Opens a browser to the Provar login page. After you authenticate, your API key is + stored at ~/.provar/credentials.json and used automatically by the Provar MCP tools + and CI/CD integrations. The key is valid for approximately 90 days. + + For CI/CD pipelines (GitHub Actions, Jenkins, etc.) where a browser cannot open: + run sf provar auth login once on your local machine, copy the api_key value from + ~/.provar/credentials.json, and store it as the PROVAR_API_KEY environment variable + or secret in your pipeline. Rotate the secret every ~90 days when the key expires. + + Don't have an account? Request access at: + https://aqqlrlhga7.execute-api.us-east-1.amazonaws.com/dev/auth/request-access + +EXAMPLES + Log in interactively: + + $ sf provar auth login + + Log in against a staging environment: + + $ sf provar auth login --url https://dev.api.example.com +``` + +## `sf provar auth rotate` + +Rotate your stored API key without re-authenticating via browser. + +``` +USAGE + $ sf provar auth rotate + +DESCRIPTION + Exchanges your current pv_k_ key for a new one atomically. The old key is + invalidated immediately. The new key is written to ~/.provar/credentials.json. + + Use this to rotate your key on a regular schedule (~every 90 days) without + going through the browser login flow. If your current key is already expired, + run sf provar auth login instead. + +EXAMPLES + Rotate the stored API key: + + $ sf provar auth rotate +``` + +## `sf provar auth status` + +Show the current API key configuration and validate it against Quality Hub. + +``` +USAGE + $ sf provar auth status + +DESCRIPTION + Reports whether an API key is configured, where it came from (environment variable + or credentials file), and performs a live check against the Quality Hub API to + confirm the key is still valid. + +EXAMPLES + Check auth status: + + $ sf provar auth status +``` + +## `sf provar auth clear` + +Remove the stored API key. + +``` +USAGE + $ sf provar auth clear + +DESCRIPTION + Deletes ~/.provar/credentials.json and revokes the key server-side. After clearing, + the MCP tools fall back to local validation mode. Has no effect if no key is stored. + +EXAMPLES + Remove the stored key: + + $ sf provar auth clear +``` + ## `sf provar mcp start` Start a local MCP server for Provar tools over stdio transport. @@ -136,6 +255,11 @@ TOOLS EXPOSED provar.testplan.add-instance — wire a test case into a plan suite by writing a .testinstance file provar.testplan.create-suite — create a new test suite directory with .planitem inside a plan provar.testplan.remove-instance — remove a .testinstance file from a plan suite + provar.nitrox.discover — discover projects containing NitroX (Hybrid Model) page objects + provar.nitrox.read — read NitroX .po.json files and return parsed content + provar.nitrox.validate — validate a NitroX .po.json against schema rules + provar.nitrox.generate — generate a new NitroX .po.json from a component description + provar.nitrox.patch — apply a JSON merge-patch to an existing NitroX .po.json file EXAMPLES Start MCP server (accepts stdio connections from Claude Desktop / Cursor): diff --git a/docs/mcp-pilot-guide.md b/docs/mcp-pilot-guide.md index d5038d74..20354f05 100644 --- a/docs/mcp-pilot-guide.md +++ b/docs/mcp-pilot-guide.md @@ -25,9 +25,9 @@ The server runs **locally on your machine**. It does not phone home, transmit yo | --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | Provar Automation IDE | ≥ 2.x | Must be installed with an **activated licence** on the same machine. The MCP server reads the licence from `~/Provar/.licenses/`. | | Salesforce CLI (`sf`) | ≥ 2.x | `npm install -g @salesforce/cli` | -| Provar DX CLI plugin | ≥ 1.5.0 | `sf plugins install @provartesting/provardx-cli` | +| Provar DX CLI plugin | ≥ 1.5.0 | `sf plugins install @provartesting/provardx-cli@beta` | | An MCP-compatible AI client | — | Claude Desktop, Claude Code, or Cursor | -| Node.js | ≥ 18 | Installed automatically with the SF CLI | +| Node.js | 18–24 | Installed automatically with the SF CLI. **Node 25+ is not supported** — a transitive dependency crashes on startup. Use Node 22 LTS. | --- @@ -48,7 +48,7 @@ sf --version ### 2. Install the Provar DX CLI plugin ```sh -sf plugins install @provartesting/provardx-cli +sf plugins install @provartesting/provardx-cli@beta ``` Verify: @@ -165,6 +165,9 @@ Prompt your AI assistant: - `validity_score` and `quality_score` both returned (0–100) - Specific rule violations called out (e.g. TC_010 missing test case ID, TC_001 missing XML declaration) - Best-practices suggestions (e.g. hardcoded credentials, missing step descriptions) +- `validation_source: "local"` if no API key is configured, `"quality_hub"` if authenticated + +> **Tip:** Run `sf provar auth login` before this scenario to unlock Quality Hub API validation (170+ rules). Without a key the tool still returns useful results using local rules only. --- @@ -226,6 +229,24 @@ Pre-requisite: `sf org login web -a MyQHOrg` then `sf provar quality-hub connect --- +### Scenario 8: Quality Hub API Validation + +**Goal:** Confirm that `provar.testcase.validate` upgrades from local rules to the full Quality Hub API ruleset when an API key is present. + +**Setup:** Run `sf provar auth login` and complete the browser login, then confirm with `sf provar auth status`. + +> "Validate the test case at `/path/to/project/tests/LoginTest.testcase` and tell me what validation_source was used." + +**What to look for:** + +- `validation_source: "quality_hub"` in the response — confirms the API path is active +- `quality_score` reflecting the full 170+ rule evaluation +- If the API is unreachable, `validation_source: "local_fallback"` and a `validation_warning` field explaining why + +**To reset and test the fallback:** run `sf provar auth clear`, repeat the prompt, and verify `validation_source` reverts to `"local"`. + +--- + ### Scenario 7: NitroX (Hybrid Model) Page Object Generation **Goal:** Have the AI discover, understand, and generate NitroX component page objects. @@ -323,7 +344,9 @@ The MCP server uses **stdio transport** exclusively. Communication travels over ### Credential handling -The Quality Hub and Automation tools invoke `sf` subprocesses. Salesforce org credentials are managed entirely by the Salesforce CLI and stored in its own credential store. The Provar MCP server never reads, parses, or transmits those credentials. +**Salesforce org credentials** — the Quality Hub and Automation tools invoke `sf` subprocesses. Salesforce org credentials are managed entirely by the Salesforce CLI and stored in its own credential store (`~/.sf/`). The Provar MCP server never reads, parses, or transmits those credentials. + +**Provar API key** — the `provar.testcase.validate` tool optionally reads a `pv_k_` API key to enable Quality Hub API validation. The key is stored at `~/.provar/credentials.json` (written by `sf provar auth login`) or read from the `PROVAR_API_KEY` environment variable. The key is sent to the Provar Quality Hub API only when a validation request is made — it is never logged or written anywhere other than `~/.provar/credentials.json`. ### Path policy enforcement @@ -393,7 +416,7 @@ After editing `claude_desktop_config.json`, you must fully restart Claude Deskto **Server starts but immediately exits** -Check that the SF CLI plugin is installed: `sf plugins | grep provardx`. If missing, run `sf plugins install @provartesting/provardx-cli`. +Check that the SF CLI plugin is installed: `sf plugins | grep provardx`. If missing, run `sf plugins install @provartesting/provardx-cli@beta`. --- diff --git a/docs/mcp.md b/docs/mcp.md index e74c9237..70960e07 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -57,6 +57,60 @@ The Provar DX CLI ships with a built-in **Model Context Protocol (MCP) server** --- +## Prerequisites + +- **Node.js 18–24** (LTS 22 recommended). Node 25+ is not supported — a transitive dependency (`buffer-equal-constant-time`) crashes on startup. Check with `node --version`. +- **Salesforce CLI** (`sf`) ≥ 2.x +- **Provar Automation IDE** installed with an activated license (see [License requirement](#license-requirement) below) + +## Quick start + +```sh +# 1. Install the plugin +sf plugins install @provartesting/provardx-cli@beta + +# 2. (Optional) Authenticate for full 170+ rule validation +sf provar auth login + +# 3. Connect your AI assistant — pick one client below +``` + +**Claude Code** (one-time, works across all your projects): + +```sh +claude mcp add provar -s user -- sf provar mcp start --allowed-paths /path/to/your/provar/project +``` + +**Claude Desktop** — edit your config file, then restart the app: + +- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +- Windows: `%APPDATA%\Claude\claude_desktop_config.json` + +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` + +> **Windows (Claude Desktop):** If `sf` is not found, use `sf.cmd` as the command instead. + +**Verify it's working** — ask your AI assistant: _"Call provardx.ping with message hello"_. You should get `{ "message": "hello" }` back. + +--- + +## License requirement + +The MCP server requires **Provar Automation IDE** to be installed on the same machine with an activated license. At startup the server reads `~/Provar/.licenses/*.properties` and verifies that at least one license is in the `Activated` state and was last verified online within the past 48 hours. + +If the license check fails, the server exits with a clear error message explaining the reason (not found, stale, or expired). Open Provar Automation IDE to refresh the license online, then retry. + +--- + ## Starting the server ```sh @@ -85,12 +139,22 @@ sf provar mcp start -a /workspace/project-a -a /workspace/project-b ## Client configuration -### Claude Desktop +### Claude Code -Add a `provar` entry to your Claude Desktop MCP configuration file. +The simplest approach is the `claude mcp add` CLI command: + +```sh +# User-scoped — works across all your projects +claude mcp add provar -s user -- sf provar mcp start --allowed-paths /path/to/your/provar/project -**macOS / Linux:** `~/Library/Application Support/Claude/claude_desktop_config.json` -**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +# Project-scoped, shared — creates .mcp.json in project root (commit to source control) +claude mcp add provar -s project -- sf provar mcp start --allowed-paths /path/to/your/provar/project + +# Project-scoped, private — stored in .claude/settings.local.json (not committed) +claude mcp add provar -s local -- sf provar mcp start --allowed-paths /path/to/your/provar/project +``` + +You can also edit `.mcp.json` at your project root directly for the shared project config: ```json { @@ -103,11 +167,12 @@ Add a `provar` entry to your Claude Desktop MCP configuration file. } ``` -Restart Claude Desktop after saving the file. The Provar tools will appear in the tool list. +### Claude Desktop -### Claude Code +Add a `provar` entry to your Claude Desktop MCP configuration file. -Add the server to your project's `.claude/mcp.json` (project-scoped) or `~/.claude/mcp.json` (user-scoped): +- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` ```json { @@ -120,11 +185,9 @@ Add the server to your project's `.claude/mcp.json` (project-scoped) or `~/.clau } ``` -Alternatively, run directly from a Claude Code session: +> **Windows:** If `sf` is not found, use `sf.cmd` as the command. Claude Desktop may not inherit the full shell PATH, so `sf.cmd` (the npm-installed wrapper) is more reliable. -``` -/mcp add provar sf provar mcp start --allowed-paths /path/to/project -``` +Restart Claude Desktop after saving the file. The Provar tools will appear in the tool list. ### Cursor / other MCP clients @@ -132,11 +195,70 @@ Any MCP client that supports the **stdio transport** can use this server. Point --- -## License requirement +## Authentication — Quality Hub API -The MCP server requires **Provar Automation IDE** to be installed on the same machine with an activated license. At startup the server reads `~/Provar/.licenses/*.properties` and verifies that at least one icense is in the `Activated` state and was last verified online within the past 48 hours. +The `provar.testcase.validate` tool can run in two modes depending on whether an API key is configured. -If the license check fails, the server exits with a clear error message explaining the reason (not found, stale, or expired). Open Provar Automation IDE to refresh the license online, then retry. +| Mode | When | What you get | +| ------------------- | ------------------ | --------------------------------------------------- | +| **Quality Hub API** | API key configured | 170+ rules, quality score, tier-specific thresholds | +| **Local only** | No key | Structural/schema rules only | + +The `validation_source` field in every `provar.testcase.validate` response tells you which mode fired: + +| Value | Meaning | +| ---------------- | ------------------------------------------------------------------------------------------------- | +| `quality_hub` | Full API validation — key is valid and the API responded | +| `local` | No key configured — local rules only | +| `local_fallback` | Key is configured but the API was unreachable or returned an error — local rules used as fallback | + +When `validation_source` is `local_fallback`, a `validation_warning` field is also returned explaining why. + +### Configuring an API key + +**Don't have an account?** Request access at the self-service form: + + +**Interactive login (recommended):** + +```sh +sf provar auth login +``` + +Opens a browser to the Provar login page. After you authenticate, the key is stored automatically at `~/.provar/credentials.json`. + +**Check current status:** + +```sh +sf provar auth status +``` + +**CI/CD — environment variable:** + +```sh +export PROVAR_API_KEY=pv_k_your_key_here +``` + +The env var takes priority over any stored key. Keys must start with `pv_k_` — any other value is ignored. + +**Rotate stored key (no browser required):** + +```sh +sf provar auth rotate +``` + +**Remove stored key:** + +```sh +sf provar auth clear +``` + +### Environment variables + +| Variable | Purpose | Default | +| ------------------------ | ------------------------------------- | ------------------------------------------------- | +| `PROVAR_API_KEY` | API key for Quality Hub validation | None — falls back to `~/.provar/credentials.json` | +| `PROVAR_QUALITY_HUB_URL` | Override the Quality Hub API base URL | Dev API Gateway URL (`/dev`) | --- @@ -293,19 +415,21 @@ Validates an XML test case for schema correctness (validity score) and best prac **Output** -| Field | Type | Description | -| -------------------------------- | -------------- | ------------------------------------------------------------------------- | -| `is_valid` | boolean | `true` if zero ERROR-level schema violations | -| `validity_score` | number (0–100) | Schema compliance score (100 − errorCount × 20) | -| `quality_score` | number (0–100) | Best-practices score (weighted deduction formula) | -| `error_count` | integer | Schema error count | -| `warning_count` | integer | Schema warning count | -| `step_count` | integer | Number of `` steps | -| `test_case_id` | string | Value of the `id` attribute | -| `test_case_name` | string | Value of the `name` attribute | -| `issues` | array | Schema issues with `rule_id`, `severity`, `message` | -| `best_practices_violations` | array | Best-practices violations with `rule_id`, `severity`, `weight`, `message` | -| `best_practices_rules_evaluated` | integer | How many best-practices rules were checked | +| Field | Type | Description | +| -------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------ | +| `is_valid` | boolean | `true` if zero ERROR-level schema violations | +| `validity_score` | number (0–100) | Schema compliance score (100 − errorCount × 20) | +| `quality_score` | number (0–100) | Best-practices score (weighted deduction formula) | +| `error_count` | integer | Schema error count | +| `warning_count` | integer | Schema warning count | +| `step_count` | integer | Number of `` steps | +| `test_case_id` | string | Value of the `id` attribute | +| `test_case_name` | string | Value of the `name` attribute | +| `issues` | array | Schema issues with `rule_id`, `severity`, `message` | +| `best_practices_violations` | array | Best-practices violations with `rule_id`, `severity`, `weight`, `message` | +| `best_practices_rules_evaluated` | integer | How many best-practices rules were checked | +| `validation_source` | string | `quality_hub`, `local`, or `local_fallback` — see Authentication section | +| `validation_warning` | string | Present when `validation_source` is `local` (onboarding) or `local_fallback` (explains why API failed) | **Key schema rules:** TC_001 (missing XML declaration), TC_002 (malformed XML), TC_003 (wrong root element), TC_010/011/012 (missing/invalid id/guid), TC_031 (invalid apiCall guid), TC_034/035 (non-integer testItemId). @@ -1050,27 +1174,27 @@ Scan a set of directories for Provar projects (identified by a `.testproject` ma By default the tool scans `cwd`. If no project is found there it widens the search to `~/git` and `~/Provar`. -| Input | Type | Required | Default | Description | -| ----------------- | --------- | -------- | ------------------------ | -------------------------------------------------------------- | -| `search_roots` | string[] | no | `[cwd()]` | Directories to scan; falls back to `~/git`, `~/Provar` if empty and cwd has no project | -| `max_depth` | number | no | `6` | Maximum directory depth for `.testproject` search (max 20) | -| `include_packages`| boolean | no | `true` | Return `nitroXPackages/` package names in output | +| Input | Type | Required | Default | Description | +| ------------------ | -------- | -------- | --------- | -------------------------------------------------------------------------------------- | +| `search_roots` | string[] | no | `[cwd()]` | Directories to scan; falls back to `~/git`, `~/Provar` if empty and cwd has no project | +| `max_depth` | number | no | `6` | Maximum directory depth for `.testproject` search (max 20) | +| `include_packages` | boolean | no | `true` | Return `nitroXPackages/` package names in output | -| Output field | Description | -| ------------------ | -------------------------------------------------------- | -| `projects` | Array of project result objects (see below) | -| `searched_roots` | Directories actually searched | +| Output field | Description | +| ---------------- | ------------------------------------------- | +| `projects` | Array of project result objects (see below) | +| `searched_roots` | Directories actually searched | Each project result: -| Field | Description | -| ------------------- | --------------------------------------------------- | -| `project_path` | Absolute path to the project root | -| `nitrox_dir` | Absolute path to `nitroX/`, or `null` | -| `nitrox_file_count` | Number of `.po.json` files found | -| `nitrox_files` | Full paths to each `.po.json` | -| `packages_dir` | Absolute path to `nitroXPackages/`, or `null` | -| `packages` | Array of `{ path, name? }` package entries | +| Field | Description | +| ------------------- | --------------------------------------------- | +| `project_path` | Absolute path to the project root | +| `nitrox_dir` | Absolute path to `nitroX/`, or `null` | +| `nitrox_file_count` | Number of `.po.json` files found | +| `nitrox_files` | Full paths to each `.po.json` | +| `packages_dir` | Absolute path to `nitroXPackages/`, or `null` | +| `packages` | Array of `{ path, name? }` package entries | Directories named `node_modules`, `.git`, or any hidden directory (`.`-prefixed) are skipped. @@ -1080,17 +1204,17 @@ Directories named `node_modules`, `.git`, or any hidden directory (`.`-prefixed) Read one or more NitroX `.po.json` files and return their parsed content for context or training. Provide specific `file_paths` or a `project_path` to read all files from a project's `nitroX/` directory. -| Input | Type | Required | Default | Description | -| -------------- | -------- | ----------------- | ------- | -------------------------------------------------------- | -| `file_paths` | string[] | one of these two | — | Specific `.po.json` paths to read | -| `project_path` | string | one of these two | — | Provar project root — reads all files from `nitroX/` | -| `max_files` | number | no | `20` | Cap on files returned to avoid context overflow | +| Input | Type | Required | Default | Description | +| -------------- | -------- | ---------------- | ------- | ---------------------------------------------------- | +| `file_paths` | string[] | one of these two | — | Specific `.po.json` paths to read | +| `project_path` | string | one of these two | — | Provar project root — reads all files from `nitroX/` | +| `max_files` | number | no | `20` | Cap on files returned to avoid context overflow | -| Output field | Description | -| ------------- | ------------------------------------------------------------------------ | +| Output field | Description | +| ------------- | ------------------------------------------------------------------------------------ | | `files` | Array of `{ file_path, content, size_bytes }` (or `{ file_path, error }` on failure) | -| `truncated` | `true` when more files exist than `max_files` | -| `total_found` | Total number of `.po.json` files discovered before the cap | +| `truncated` | `true` when more files exist than `max_files` | +| `total_found` | Total number of `.po.json` files discovered before the cap | Path policy is enforced per-file. A missing or unparseable file returns an `error` field inside the file entry rather than failing the whole call. @@ -1104,33 +1228,33 @@ Validate a NitroX `.po.json` (Hybrid Model component page object) against the FA Score formula: `100 − (20 × errors) − (5 × warnings) − (1 × infos)`, minimum 0. -| Input | Type | Required | Description | -| ----------- | ------ | -------------- | ----------------------------------- | -| `content` | string | one of these | JSON string to validate | -| `file_path` | string | one of these | Path to a `.po.json` file | +| Input | Type | Required | Description | +| ----------- | ------ | ------------ | ------------------------- | +| `content` | string | one of these | JSON string to validate | +| `file_path` | string | one of these | Path to a `.po.json` file | -| Output field | Description | -| ------------- | ---------------------------------------- | -| `valid` | `true` when no ERROR-severity issues | -| `score` | 0–100 | -| `issue_count` | Total issues | -| `issues` | Array of `ValidationIssue` (see below) | +| Output field | Description | +| ------------- | -------------------------------------- | +| `valid` | `true` when no ERROR-severity issues | +| `score` | 0–100 | +| `issue_count` | Total issues | +| `issues` | Array of `ValidationIssue` (see below) | **Validation rules:** -| Rule | Severity | Description | -| ----- | -------- | --------------------------------------------------------------------------- | -| NX000 | ERROR | Content is not valid JSON or not a JSON object | -| NX001 | ERROR | `componentId` is missing or not a valid UUID | -| NX002 | ERROR | Root component (no `parentId`) missing `name`, `type`, `pageStructureElement`, or `fieldDetailsElement` | -| NX003 | ERROR | `tagName` contains whitespace | +| Rule | Severity | Description | +| ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| NX000 | ERROR | Content is not valid JSON or not a JSON object | +| NX001 | ERROR | `componentId` is missing or not a valid UUID | +| NX002 | ERROR | Root component (no `parentId`) missing `name`, `type`, `pageStructureElement`, or `fieldDetailsElement` | +| NX003 | ERROR | `tagName` contains whitespace | | NX004 | ERROR | Interaction missing required field (`defaultInteraction`, `implementations` ≥ 1, `interactionType`, `name`, `testStepTitlePattern`, `title`) | -| NX005 | ERROR | Implementation missing `javaScriptSnippet` | -| NX006 | ERROR | Selector missing `xpath` | -| NX007 | WARNING | Element missing `type` | -| NX008 | WARNING | `comparisonType` not one of `"equals"`, `"starts-with"`, `"contains"` | -| NX009 | INFO | Interaction `name` contains characters outside `[A-Za-z0-9 ]` | -| NX010 | INFO | `bodyTagName` contains whitespace | +| NX005 | ERROR | Implementation missing `javaScriptSnippet` | +| NX006 | ERROR | Selector missing `xpath` | +| NX007 | WARNING | Element missing `type` | +| NX008 | WARNING | `comparisonType` not one of `"equals"`, `"starts-with"`, `"contains"` | +| NX009 | INFO | Interaction `name` contains characters outside `[A-Za-z0-9 ]` | +| NX010 | INFO | `bodyTagName` contains whitespace | **Error codes:** `MISSING_INPUT`, `NX000`, `FILE_NOT_FOUND`, `PATH_NOT_ALLOWED` @@ -1142,29 +1266,29 @@ Generate a new NitroX `.po.json` from a component description. All `componentId` Applicable to any component type: LWC, Screen Flow, Industry Components, Experience Cloud, HTML5. -| Input | Type | Required | Default | Description | -| ----------------------- | -------- | -------- | --------- | -------------------------------------------------------- | -| `name` | string | yes | — | Path-like name, e.g. `/com/force/myapp/ButtonComponent` | -| `tag_name` | string | yes | — | LWC or HTML tag, e.g. `lightning-button`, `c-my-cmp` | -| `type` | string | no | `"Block"` | `"Block"` or `"Page"` | -| `page_structure_element`| boolean | no | `true` | Whether this is a page structure element | -| `field_details_element` | boolean | no | `false` | Whether this is a field details element | -| `parameters` | object[] | no | — | Qualifier parameters (see below) | -| `elements` | object[] | no | — | Child elements (see below) | -| `output_path` | string | no | — | File path to write when `dry_run=false` | -| `overwrite` | boolean | no | `false` | Overwrite existing file | -| `dry_run` | boolean | no | `true` | Return JSON without writing | +| Input | Type | Required | Default | Description | +| ------------------------ | -------- | -------- | --------- | ------------------------------------------------------- | +| `name` | string | yes | — | Path-like name, e.g. `/com/force/myapp/ButtonComponent` | +| `tag_name` | string | yes | — | LWC or HTML tag, e.g. `lightning-button`, `c-my-cmp` | +| `type` | string | no | `"Block"` | `"Block"` or `"Page"` | +| `page_structure_element` | boolean | no | `true` | Whether this is a page structure element | +| `field_details_element` | boolean | no | `false` | Whether this is a field details element | +| `parameters` | object[] | no | — | Qualifier parameters (see below) | +| `elements` | object[] | no | — | Child elements (see below) | +| `output_path` | string | no | — | File path to write when `dry_run=false` | +| `overwrite` | boolean | no | `false` | Overwrite existing file | +| `dry_run` | boolean | no | `true` | Return JSON without writing | **Parameter object:** `{ name, value, comparisonType?: "equals"|"starts-with"|"contains", default?: boolean }` **Element object:** `{ label, type_ref, tag_name?, parameters?, selector_xpath? }` -| Output field | Description | -| ------------ | ---------------------------------------- | -| `content` | Generated JSON string (pretty-printed) | +| Output field | Description | +| ------------ | --------------------------------------------- | +| `content` | Generated JSON string (pretty-printed) | | `file_path` | Resolved absolute path (if `output_path` set) | -| `written` | `true` when file was written to disk | -| `dry_run` | Echo of the `dry_run` input | +| `written` | `true` when file was written to disk | +| `dry_run` | Echo of the `dry_run` input | **Error codes:** `FILE_EXISTS`, `PATH_NOT_ALLOWED`, `PATH_TRAVERSAL`, `GENERATE_ERROR` @@ -1176,19 +1300,19 @@ Apply a [JSON merge-patch (RFC 7396)](https://www.rfc-editor.org/rfc/rfc7396) to Patch semantics: a key with a `null` value removes that key; any other value replaces it (or recursively merges if both target and patch values are objects). -| Input | Type | Required | Default | Description | -| --------------- | ------- | -------- | ------- | ------------------------------------------------------------- | -| `file_path` | string | yes | — | Path to the existing `.po.json` | -| `patch` | object | yes | — | JSON merge-patch to apply | -| `dry_run` | boolean | no | `true` | Return merged result without writing | -| `validate_after`| boolean | no | `true` | Run NX validation; blocks write if errors found | - -| Output field | Description | -| ------------ | ----------------------------------------------- | -| `content` | Merged JSON string (pretty-printed) | -| `file_path` | Absolute path of the file | -| `written` | `true` when file was written | -| `dry_run` | Echo of the `dry_run` input | +| Input | Type | Required | Default | Description | +| ---------------- | ------- | -------- | ------- | ----------------------------------------------- | +| `file_path` | string | yes | — | Path to the existing `.po.json` | +| `patch` | object | yes | — | JSON merge-patch to apply | +| `dry_run` | boolean | no | `true` | Return merged result without writing | +| `validate_after` | boolean | no | `true` | Run NX validation; blocks write if errors found | + +| Output field | Description | +| ------------ | ------------------------------------------------------ | +| `content` | Merged JSON string (pretty-printed) | +| `file_path` | Absolute path of the file | +| `written` | `true` when file was written | +| `dry_run` | Echo of the `dry_run` input | | `validation` | Validation result (present when `validate_after=true`) | When `validate_after=true` and the merged content has errors, the write is blocked and the tool returns `isError=true` with code `VALIDATION_FAILED`. Set `validate_after=false` to force-write despite errors. diff --git a/docs/provar-mcp-public-docs.md b/docs/provar-mcp-public-docs.md new file mode 100644 index 00000000..5355e231 --- /dev/null +++ b/docs/provar-mcp-public-docs.md @@ -0,0 +1,387 @@ +# Provar MCP + +> **Beta:** Provar MCP is currently in Beta. This is offered to all Provar users at no additional cost, and is an open source project hosted on GitHub [here](https://github.com/ProvarTesting/provardx-cli/). General Availability is coming soon. We welcome feedback via [GitHub Issues](https://github.com/ProvarTesting/provardx-cli/issues). + +--- + +## What is Provar MCP? + +Provar MCP is an AI-assisted quality layer built directly into the Provar DX CLI. It implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) — an open standard that lets AI assistants call tools on your behalf — and exposes a rich set of Provar project operations to AI clients such as **Claude Desktop**, **Claude Code**, and **Cursor**. + +Once connected, your AI assistant can: + +- Inspect your Provar Automation project and surface coverage gaps +- Generate Java Page Objects and XML test case skeletons +- Validate every level of the test hierarchy (test cases, suites, plans, and the full project) against 30+ quality rules +- Set up and manage your `provardx-properties.json` run configuration +- Trigger Provar Automation test runs and Provar Quality Hub managed runs — all from inside a chat session + +The MCP server runs **entirely on your local machine**. No project files, test code, or credentials are transmitted to Provar servers. + +--- + +## Prerequisites + +Before you can use Provar MCP, ensure the following are in place: + +| Requirement | Version | Notes | +| ----------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Provar Automation** | ≥ 2.18.2 or ≥ 3.0.6 | Must be installed with an **activated license** on the same machine. The MCP server reads license state from `~/Provar/.licenses/`. | +| **Salesforce CLI (`sf`)** | ≥ 2.x | Install with `npm install -g @salesforce/cli` | +| **Provar DX CLI plugin** | ≥ 1.5.0-beta | Install with `sf plugins install @provartesting/provardx-cli` | +| **Node.js** | ≥ 18 | Installed automatically with the Salesforce CLI | +| **An MCP-compatible AI client** | — | Claude Desktop, Claude Code (VS Code / CLI), or Cursor | +| **An existing Provar Automation project** | — | The MCP server works best when pointed at a real project directory. Project context (connections, environments, Page Objects, test cases) is what the AI reads and reasons over. | + +### License requirements + +Provar MCP requires an active **Provar Automation** license on the machine where the server runs. Validation is automatic: + +1. The server reads `~/Provar/.licenses/*.properties` — the same files written by the Provar Automation IDE — and checks that a license is activated and was last verified online within 48 hours. +2. Successful validations are cached for 2 hours, so frequent server restarts do not cause repeated disk reads. +3. If no valid license is found, the server exits immediately with a clear error message. Open Provar Automation IDE and ensure your license is activated, then retry. + +> There is no separate MCP license. Your existing Provar Automation license covers MCP usage. + +--- + +## Installation + +### Step 1 — Install the Salesforce CLI + +```sh +npm install -g @salesforce/cli +sf --version +``` + +### Step 2 — Install the Provar DX CLI plugin + +```sh +sf plugins install @provartesting/provardx-cli +sf provar mcp start --help +``` + +### Step 3 — Authenticate with Quality Hub (optional, recommended) + +Run `sf provar auth login` to connect your Provar account and unlock full Quality Hub API validation (170+ rules, quality scoring). Without this, the MCP server runs in local-only mode using structural rules. + +```sh +sf provar auth login +``` + +This opens a browser to the Provar login page. After you authenticate, your API key is stored at `~/.provar/credentials.json` and picked up automatically by the MCP server on every subsequent tool call. + +For CI/CD pipelines (GitHub Actions, Jenkins, etc.) where a browser cannot open: run `sf provar auth login` once on your local machine, copy the `api_key` value from `~/.provar/credentials.json`, and store it as the `PROVAR_API_KEY` environment variable or secret in your pipeline. The key is valid for approximately 90 days — rotate the secret when it expires by running `sf provar auth login` again locally. + +### Step 4 — Configure your AI client + +#### Claude Desktop + +Edit the Claude Desktop MCP configuration file: + +- **macOS / Linux:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` + +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` + +Restart Claude Desktop after saving. The Provar tools will appear in the tool list automatically. + +#### Claude Code (VS Code / CLI) + +Add to your project's `.claude/mcp.json`: + +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` + +Or add directly from a Claude Code session: + +``` +/mcp add provar sf provar mcp start --allowed-paths /path/to/project +``` + +#### Cursor + +In Cursor Settings → MCP, add: + +```json +{ + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } +} +``` + +> **Important:** Set `--allowed-paths` to the root of your Provar Automation project directory. This is the folder containing your `.testproject` file. The server will only read and write files within this boundary. + +--- + +## Verify the connection + +Once your AI client is configured, ask it: + +> "Call provardx.ping with message 'hello'" + +Expected response: + +```json +{ "pong": "hello", "ts": "2026-04-07T...", "server": "provar-mcp@1.0.0" } +``` + +If this fails, see the [Troubleshooting](#troubleshooting) section. + +--- + +## Use cases + +### Inspect your project + +Get an instant inventory of your Provar project — file counts, coverage gaps, and missing configurations. + +**Prompt:** + +> "Use provar.project.inspect on my project at `/workspace/MyProvarProject` and tell me what you find — how many test cases are there, and which ones aren't covered by any test plan?" + +**What you get back:** + +- Total test case count, suite structure, Page Object count +- A list of test cases not referenced by any test plan (coverage gaps) +- Whether a `provardx-properties.json` config file exists + +--- + +### Validate a test case + +Score an existing test case for schema compliance and best-practice quality issues. + +**Prompt:** + +> "Validate the test case at `/workspace/MyProvarProject/tests/regression/LoginTest.testcase` and explain any issues." + +**What you get back:** + +- `validity_score` (schema compliance, 0–100) and `quality_score` (best practices, 0–100) +- Specific rule violations with IDs, severities, and descriptions +- Actionable suggestions (e.g. "Add a missing XML declaration", "Test case ID is not a valid UUID") +- `validation_source` — `"quality_hub"` if authenticated, `"local"` if no API key is configured + +> **Get more:** Run `sf provar auth login` once to unlock Quality Hub API validation (170+ rules). Without a key the tool still returns useful results using local structural rules. + +--- + +### Generate a Page Object + +Have the AI scaffold a new Java Page Object for a Salesforce page with correct annotations and `@FindBy` stubs. + +**Prompt:** + +> "Generate a Salesforce Page Object for the Account Detail page. Include fields for Account Name (input), Industry (select), and a Save button. Write it to `/workspace/MyProvarProject/src/pageobjects/accounts/AccountDetailPage.java`." + +**What you get back:** + +- A valid Java file with `@SalesforcePage` annotation +- `@FindBy` annotations for each field using sensible locator strategies +- File written to disk (use `dry_run: true` in the tool call to preview without writing) + +--- + +### Generate a test case + +Scaffold a new XML test case with a proper UUID, sequential step IDs, and a clean structure ready for Provar Automation. + +**Prompt:** + +> "Generate a test case called 'Verify Account Creation' with steps for navigating to the Accounts page, clicking New, filling in Account Name, and saving. Write it to `/workspace/MyProvarProject/tests/smoke/VerifyAccountCreation.testcase`." + +--- + +### Set up your run configuration + +Let the AI create and validate a `provardx-properties.json` — the properties file that tells the Provar DX CLI how to run your tests. + +**Prompt:** + +> "Generate a `provardx-properties.json` at `/workspace/MyProvarProject/provardx-properties.json` with projectPath set to `/workspace/MyProvarProject` and provarHome set to `/Applications/Provar`. Then validate it and tell me if anything is missing." + +--- + +### Validate the full project hierarchy + +Get a single quality score for your entire project — test cases, suites, plans, connections, environments, and cross-cutting rules all evaluated together. + +**Prompt:** + +> "Validate the full test project at `/workspace/MyProvarProject`. The project has connections named `SandboxOrg` and `ProdOrg`, and environments `QA` and `UAT`. Give me a quality report." + +**What you get back:** + +- Overall project quality score (0–100) +- Test plan coverage percentage +- Breakdown of violations by rule ID +- Per-plan quality scores + +--- + +### Trigger a Provar Automation test run + +Ask the AI to run your local Provar Automation test suite and report results. + +**Prompt:** + +> "Load the properties file at `/workspace/MyProvarProject/provardx-properties.json`, compile the project, then run the tests and tell me the results." + +**The AI will chain:** + +1. `provar.automation.config.load` — registers the properties file +2. `provar.automation.compile` — compiles Page Objects +3. `provar.automation.testrun` — executes the test run +4. `provar.testrun.report.locate` — finds the JUnit/HTML report paths + +--- + +### Trigger a Quality Hub managed test run + +Kick off a managed test run via Provar Quality Hub and poll until it completes. + +**Pre-requisite:** Authenticate the Salesforce CLI against your Quality Hub org first: + +```sh +sf org login web -a MyQHOrg +sf provar quality-hub connect -o MyQHOrg +``` + +**Prompt:** + +> "Connect to the Quality Hub org MyQHOrg, start a test run using config file `config/smoke-run.json`, and poll every 30 seconds until it completes or fails." + +**The AI will chain:** + +1. `provar.qualityhub.connect` — connects to the org +2. `provar.qualityhub.testrun` — triggers the run +3. `provar.qualityhub.testrun.report` — polls status in a loop +4. Reports final pass/fail status and a summary of results + +--- + +### Root cause analysis after a test run failure + +After a failed run, ask the AI to classify failures and identify patterns. + +**Prompt:** + +> "My test run just finished. Analyse the results at `/workspace/MyProvarProject/Results/` and classify any failures — tell me which are pre-existing issues and which look like new regressions." + +**What you get back:** + +- Classified failure categories (environment issue, assertion failure, locator issue, etc.) +- Identification of Page Objects involved in failures +- Suggested next steps + +--- + +### Create a Quality Hub defect from a failed test + +Turn a failed test execution directly into a Quality Hub defect, without leaving your AI chat. + +**Prompt:** + +> "The test 'LoginTest' failed in the last run. Create a defect in Quality Hub for it." + +--- + +## Available tools (reference) + +| Tool | What it does | +| ------------------------------------- | ---------------------------------------------------------------- | +| `provardx.ping` | Sanity check — verifies the server is running | +| `provar.project.inspect` | Inventory project artefacts and surface coverage gaps | +| `provar.project.validate` | Full project quality validation from disk | +| `provar.pageobject.generate` | Generate a Java Page Object skeleton | +| `provar.pageobject.validate` | Validate Page Object quality (30+ rules) | +| `provar.testcase.generate` | Generate an XML test case skeleton | +| `provar.testcase.validate` | Validate test case XML (schema + best-practices scores) | +| `provar.testsuite.validate` | Validate a test suite hierarchy | +| `provar.testplan.validate` | Validate a test plan with metadata completeness checks | +| `provar.testplan.add-instance` | Wire a test case into a plan suite | +| `provar.testplan.create-suite` | Create a new test suite inside a plan | +| `provar.testplan.remove-instance` | Remove a test instance from a plan suite | +| `provar.properties.generate` | Generate a `provardx-properties.json` from the standard template | +| `provar.properties.read` | Read and parse a `provardx-properties.json` | +| `provar.properties.set` | Update fields in a `provardx-properties.json` | +| `provar.properties.validate` | Validate a `provardx-properties.json` against the schema | +| `provar.ant.generate` | Generate an ANT `build.xml` for CI/CD pipeline execution | +| `provar.ant.validate` | Validate an ANT `build.xml` | +| `provar.automation.setup` | Detect or download/install Provar Automation binaries | +| `provar.automation.config.load` | Register a properties file as the active config | +| `provar.automation.compile` | Compile Page Objects after changes | +| `provar.automation.metadata.download` | Download Salesforce metadata into the project | +| `provar.automation.testrun` | Trigger a local Provar Automation test run | +| `provar.qualityhub.connect` | Connect to a Quality Hub org | +| `provar.qualityhub.display` | Display connected Quality Hub org info | +| `provar.qualityhub.testrun` | Trigger a Quality Hub managed test run | +| `provar.qualityhub.testrun.report` | Poll test run status | +| `provar.qualityhub.testrun.abort` | Abort an in-progress test run | +| `provar.qualityhub.testcase.retrieve` | Retrieve test cases by user story or component | +| `provar.qualityhub.defect.create` | Create Quality Hub defects from failed executions | +| `provar.testrun.report.locate` | Resolve JUnit/HTML report paths after a run | +| `provar.testrun.rca` | Classify failures and detect regressions | + +--- + +## Security + +- **Local only.** The MCP server communicates via stdio — no TCP port is opened, no network listener is started. +- **Path-scoped.** All file operations are restricted to the directories you specify via `--allowed-paths`. Path traversal (`../`) is blocked. +- **No data exfiltration.** Project files, test code, and credentials are never transmitted to Provar servers. +- **Credential safety.** Quality Hub and Automation tools invoke the Salesforce CLI as a subprocess. Org credentials stay in the SF CLI's own credential store and are never read or logged by the MCP server. +- **Audit log.** Every tool invocation is logged to stderr with a unique request ID in structured JSON format. Capture stderr to maintain an audit trail. + +--- + +## Troubleshooting + +**"No activated Provar license found" / `LICENSE_NOT_FOUND`** +Open Provar Automation IDE → Help → Manage License → ensure the license is Activated. Then restart the MCP server. + +**"Warning: license validated from offline cache" (on stderr)** +The server started successfully but the license cache is over 2 hours old. This is a warning only. If the cache exceeds 48 hours without a successful online re-validation, the next startup will fail. Restart the server while Provar Automation IDE is connected to the internet to refresh the cache. + +**`SF_NOT_FOUND` error from Quality Hub / Automation tools** +The `sf` CLI binary is not on the PATH that the MCP server sees (common with macOS GUI apps). Use the full binary path in your MCP config: + +```json +{ "command": "/usr/local/bin/sf", "args": ["provar", "mcp", "start", "--allowed-paths", "..."] } +``` + +**`PATH_NOT_ALLOWED` error** +The path passed to a tool is outside the `--allowed-paths` root. Update `--allowed-paths` in your client config and restart the server. + +**Tools not appearing in Claude Desktop** +After editing `claude_desktop_config.json`, fully quit and reopen Claude Desktop (Cmd+Q on macOS, not just close the window). + +**Server starts then immediately exits** +Check the plugin is installed: `sf plugins | grep provardx`. If missing: `sf plugins install @provartesting/provardx-cli`. + +--- + +## Support + +- **Bug reports and feature requests (COMING SOON):** [github.com/ProvarTesting/provardx-cli/issues](https://github.com/ProvarTesting/provardx-cli/issues) +- **Provar Automation / Quality Hub support:** Contact Provar Support through your usual channel or through the [Provar Success Portal](https://success.provartesting.com/). diff --git a/docs/university-of-provar-mcp-course.md b/docs/university-of-provar-mcp-course.md new file mode 100644 index 00000000..8fc62667 --- /dev/null +++ b/docs/university-of-provar-mcp-course.md @@ -0,0 +1,614 @@ +# University of Provar — Provar MCP Course + +**Course title:** AI-Assisted Quality with Provar MCP +**Format:** Self-paced, hands-on labs +**Audience:** Provar Automation users who want to accelerate test authoring and quality analysis using AI assistants +**Status:** Beta — course content will be updated as Provar MCP reaches General Availability + +--- + +## Course overview + +This course teaches you to use **Provar MCP** — the AI-powered extension to the Provar DX CLI — to speed up every stage of the Provar Automation workflow: from inspecting a project and generating Page Objects, to running tests and triaging failures, all from inside an AI chat session. + +By the end of this course you will be able to: + +- Connect an AI assistant (Claude, Cursor) to your Provar Automation project +- Ask the AI to inspect projects, validate quality, and surface gaps — without writing a single command +- Generate Page Objects and test case skeletons using natural-language prompts +- Trigger test runs and analyse results through the AI interface +- Use Provar Quality Hub managed runs from the AI chat + +**Estimated time:** 4–5 hours across all modules + +--- + +## Prerequisites + +Before starting this course, you should have: + +- **Provar Automation** installed with an activated license (≥ v2.18.2 or 3.0.6) +- **An existing Provar Automation project** on your local machine (the AI reads project context from disk — the richer the project, the more useful the results) +- **Salesforce CLI (`sf`)** installed: `npm install -g @salesforce/cli` +- **Provar DX CLI plugin** installed: `sf plugins install @provartesting/provardx-cli` +- **One of:** Claude Desktop, Claude Code (VS Code or CLI), or Cursor + +If you haven't set up the Provar DX CLI before, complete the _Getting Started with Provar DX CLI_ course first. + +--- + +## Module 1: Introduction to Provar MCP + +**Learning objectives** + +- Understand what MCP is and why Provar uses it +- Describe what the Provar MCP server does and does not do +- Explain the license and project directory requirements + +### 1.1 — What is MCP? + +The **Model Context Protocol (MCP)** is an open standard created to let AI assistants call external tools safely and predictably. Instead of the AI guessing how to interact with a system, you expose a set of clearly defined tools — each with inputs, outputs, and documented behavior — and the AI calls them on your behalf. + +Provar MCP wraps the entire Provar DX CLI toolchain as MCP tools. Your AI assistant can inspect your project, generate files, validate quality, and trigger runs — all without you typing a single CLI command. + +### 1.2 — How it works + +``` +Your AI client (Claude Desktop / Claude Code / Cursor) + ↓ MCP stdio transport +Provar MCP Server (sf provar mcp start) + ↓ reads/writes files within --allowed-paths +Your Provar Automation project on disk + ↓ spawns subprocesses for test runs / Quality Hub +Salesforce CLI (sf) +``` + +The server runs on your machine. Nothing leaves your machine except outbound calls you explicitly trigger (e.g. a Quality Hub test run hitting your Salesforce org). + +### 1.3 — What you need + +- A **Provar Automation license** — the MCP server reads your existing IDE license from `~/Provar/.licenses/`. No separate license is required. +- An **existing Provar Automation project directory** — this is the `--allowed-paths` root you point the server at. The AI uses the project's Page Objects, test cases, plans, connections, and environments as context. + +> **Tip:** The more complete your Provar project is, the better the AI's suggestions will be. A project with real Page Objects, named connections, and a populated test plan gives the AI much more to work with than an empty skeleton. + +### 1.4 — Knowledge check + +1. Where does the MCP server run — on your local machine or on Provar's servers? (local) +2. Does Provar MCP require a separate license, or does it use your existing Provar Automation license? (uses existing license) +3. What flag do you use when starting the MCP server to specify which project directory the AI can access? (--allowed-paths) + +--- + +## Module 2: Installation and Setup + +**Learning objectives** + +- Install and verify the Provar DX CLI plugin +- Configure at least one MCP-compatible AI client +- Verify the connection using the ping tool + +### Lab 2.1 — Install the plugin + +Open a terminal and run: + +```sh +sf plugins install @provartesting/provardx-cli +``` + +Verify the MCP command is available: + +```sh +sf provar mcp start --help +``` + +You should see a list of flags and tool descriptions. If you see an error, confirm the Salesforce CLI is installed: `sf --version`. + +### Lab 2.2 — Configure Claude Desktop + +1. Find the Claude Desktop config file: + + - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` + - Windows: `%APPDATA%\Claude\claude_desktop_config.json` + +2. Add the Provar server entry, replacing `/path/to/your/provar/project` with the actual path to your project directory: + +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` + +3. Fully quit and reopen Claude Desktop. + +4. In a new conversation, look for Provar tools in the tool list. You should see entries like `provar.project.inspect`, `provar.testcase.validate`, etc. + +> **macOS note:** If `sf` is not found, use the full path. Find it with `which sf` in your terminal, then use that path as the `"command"` value. + +### Lab 2.3 — Configure Claude Code + +If you're using Claude Code (VS Code or CLI), add the server to `.claude/mcp.json` in your project root: + +```json +{ + "mcpServers": { + "provar": { + "command": "sf", + "args": ["provar", "mcp", "start", "--allowed-paths", "/path/to/your/provar/project"] + } + } +} +``` + +Alternatively, run this inside a Claude Code session: + +``` +/mcp add provar sf provar mcp start --allowed-paths /path/to/project +``` + +### Lab 2.4 — Verify the connection + +In your AI client, type: + +> "Call provardx.ping with message 'hello'" + +Expected response: + +```json +{ "pong": "hello", "ts": "2026-04-07T12:00:00Z", "server": "provar-mcp@1.0.0" } +``` + +**If this fails:** + +- Confirm `sf plugins | grep provardx` shows the plugin installed +- Confirm the `--allowed-paths` directory exists on disk +- On macOS GUI apps, use the full path to `sf` in the config + +### Lab 2.5 — Authenticate with Quality Hub (recommended) + +This step unlocks full Quality Hub API validation (170+ rules, quality scoring). Without it, the MCP server runs in local-only mode. + +```sh +sf provar auth login +``` + +A browser window opens to the Provar login page. After authenticating, confirm the key was stored: + +```sh +sf provar auth status +``` + +You should see `API key configured` with a source of `~/.provar/credentials.json`. + +> **CI/CD alternative:** Instead of the browser login, set `PROVAR_API_KEY=pv_k_your_key` in your environment. This takes priority over the stored credentials file. + +### Knowledge check + +1. After editing `claude_desktop_config.json`, what do you need to do for the changes to take effect? + _(Fully quit and reopen Claude Desktop — closing the window is not enough)_ +2. What does `provardx.ping` tell you when it responds successfully? + _(That the MCP server is running, the client is connected, and the server version)_ +3. You get a `LICENSE_NOT_FOUND` error when the server starts. What is the most likely cause and how do you fix it? + _(Provar Automation IDE license is not activated on this machine — open Provar Automation IDE, go to Help → Manage License, activate the license, then retry)_ +4. What command would you run to check whether your Quality Hub API key is valid? + _(`sf provar auth status` — it performs a live check against the Quality Hub API and shows the key source, tier, and expiry)_ + +--- + +## Module 3: Inspecting Your Project + +**Learning objectives** + +- Use `provar.project.inspect` to get a full inventory of a Provar project +- Identify test coverage gaps from inspection output +- Understand what project context the AI uses when reasoning about your tests + +### 3.1 — What inspection tells you + +`provar.project.inspect` reads your entire project directory and returns: + +| What | Why it matters | +| -------------------------------------- | --------------------------------------------------------------- | +| Test case count and paths | Baseline for coverage analysis | +| Page Object directories | Understand source structure | +| Test plan / suite / instance hierarchy | Drives the coverage calculation | +| Uncovered test case paths | Test cases not in any test plan — gaps the AI can help you fill | +| `provardx-properties.json` files | Whether run configurations are set up | +| Data source files | Whether test data exists | + +### Lab 3.1 — Inspect your project + +Point the AI at your project and ask for a summary: + +> "Use provar.project.inspect on `/path/to/your/provar/project` and give me a summary: how many test cases, suites, and plans are there? Which test cases aren't in any plan?" + +**What to observe:** + +- Review the `uncovered_test_case_paths` list — these are coverage gaps +- Check whether `provardx_properties_files` is empty (if so, you'll need to create one in Module 6) +- Note the `coverage_percent` value + +### Lab 3.2 — Coverage gap analysis + +After the basic inspection, push further: + +> "Based on the inspection, which test suites have the most uncovered tests? Suggest a plan for adding those to an existing test plan." + +The AI will reason over the coverage data and suggest specific `.testinstance` additions. + +### Knowledge check + +1. What is `coverage_percent` measuring in the inspection output? + _(The percentage of test case files that are referenced by at least one `.testinstance` in a test plan)_ +2. What is the difference between a test suite and a test plan in Provar's hierarchy? + _(A test plan is the top-level container — a directory under `plans/` with a `.planitem` file. A test suite is a named sub-directory inside a plan, also containing a `.planitem`, used to group related test instances)_ +3. If `uncovered_test_case_paths` lists 15 tests, what does that mean in practice? + _(Those 15 test cases are not wired into any test plan via a `.testinstance` file, so they will never be executed by a plan-driven run and won't contribute to Quality Hub reporting)_ + +--- + +## Module 4: Validating Test Quality + +**Learning objectives** + +- Validate a test case and interpret validity and quality scores +- Validate a Page Object against structural and locator rules +- Run a full project-level validation and understand the output + +### 4.1 — Two types of scores + +Every validation tool returns up to two scores: + +| Score | Range | What it measures | +| ---------------- | ----- | ------------------------------------------------------------------ | +| `validity_score` | 0–100 | Schema compliance — is the file structurally correct? | +| `quality_score` | 0–100 | Best practices — does the file follow Provar's quality guidelines? | + +A file can be valid (no schema errors) but have a low quality score (missing descriptions, hardcoded data, no test case ID). + +### 4.2 — Two validation modes + +The `provar.testcase.validate` tool operates in one of two modes depending on whether a Quality Hub API key is configured: + +| Mode | `validation_source` | Rules | Requires | +|---|---|---|---| +| **Quality Hub API** | `quality_hub` | 170+ rules, full quality scoring | `sf provar auth login` (once) | +| **Local only** | `local` | Structural and schema rules | Nothing | + +If a key is configured but the API is temporarily unreachable, the tool falls back to local rules and sets `validation_source: "local_fallback"` with a `validation_warning` explaining why. + +Run `sf provar auth login` before the labs in this module to get the full Quality Hub experience. If you skipped Lab 2.5, do it now. + +### Lab 4.1 — Validate a single test case + +Pick any `.testcase` file in your project and run: + +> "Validate the test case at `/path/to/project/tests/SmokeTest.testcase`. Explain each issue found and tell me how to fix them." + +**What to observe:** + +- `validity_score` — any value below 100 means schema errors are present +- `quality_score` — check the `best_practices_violations` list for actionable items +- Rule IDs like `TC_010` (missing test case ID) or `TC_001` (missing XML declaration) — these are the most common issues in new projects + +### Lab 4.2 — Validate a Page Object + +Open a `.java` Page Object file in your project and ask: + +> "Validate the Page Object at `/path/to/project/src/pageobjects/MyPage.java`. Highlight any issues with locators or annotations." + +**What to observe:** + +- `quality_score` out of 100 +- Issues flagged under rules like `PO_071`–`PO_073` (fragile XPath patterns — replace with `@id` or `By.cssSelector`) +- `PO_004` (non-PascalCase class name) — naming convention violations + +### Lab 4.3 — Full project validation + +Run a project-wide quality scan: + +> "Validate the full test project at `/path/to/project`. Give me the overall quality score, the per-plan scores, and the top 5 violation types across the project." + +**What to observe:** + +- `quality_score` for the project as a whole +- `coverage_percent` (how many test cases are in at least one plan) +- `violation_summary` — a map of rule IDs to counts, useful for spotting systemic issues +- `plan_scores` — which plans have the lowest scores and need the most attention + +### Knowledge check + +1. A test case has a `validity_score` of 100 and a `quality_score` of 62. What does this tell you? + _(The file is structurally valid XML with no schema errors, but it violates several best-practice rules — e.g. missing descriptions, hardcoded data, or no test case ID — that reduce the quality score)_ +2. Which rule ID fires when a test case is missing its XML declaration? + _(`TC_001`)_ +3. What does `PROJ-CONN-001` signal in a project-level validation? + _(A test case or test instance references a Salesforce connection name that is not defined in the project's `.testproject` file)_ + +--- + +## Module 5: Generating Test Artefacts + +**Learning objectives** + +- Generate a Java Page Object from a natural-language description +- Generate an XML test case skeleton with steps +- Use dry run mode to preview output before writing to disk + +### 5.1 — Page Object generation + +Provar Page Objects are Java classes annotated with `@Page` or `@SalesforcePage`. The `provar.pageobject.generate` tool creates a skeleton with correct structure, package declaration, and `@FindBy` field stubs — ready for you to refine and complete. + +### Lab 5.1 — Generate a Salesforce Page Object + +> "Generate a Salesforce Page Object for the Contact Detail page. Include these fields: Contact Name (input, locator type: id, locator value: 'firstName'), Email (input), Phone (input), and a Save button. The class name should be ContactDetailPage, package pageobjects.contacts. Do a dry run first — don't write to disk yet." + +**What to observe:** + +- The generated Java file with `@SalesforcePage` annotation +- `@FindBy` annotations for each field +- The `written: false` response confirming nothing was written + +Once you're happy with the output, remove the dry run instruction: + +> "Now write it to `/path/to/project/src/pageobjects/contacts/ContactDetailPage.java`." + +### Lab 5.2 — Generate a test case + +> "Generate a test case called 'Create New Contact' with the following steps: navigate to Contacts, click New, fill in the contact name, enter email and phone, click Save, and verify the record was created. Write it to `/path/to/project/tests/smoke/CreateNewContact.testcase`." + +**What to observe:** + +- Valid XML output with a generated UUID and sequential `testItemId` values +- Steps mapped to Provar API step types +- File written to disk + +### Lab 5.3 — Validate what you just generated + +Always validate generated artefacts before committing them to source control: + +> "Validate the test case we just wrote at `/path/to/project/tests/smoke/CreateNewContact.testcase`." + +If the quality score is below 80, ask: + +> "What would bring the quality score above 80? Make the changes." + +### Knowledge check + +1. What is the difference between `dry_run: true` and omitting the `output_path` parameter when generating a Page Object? + _(Both return the content without writing to disk, but `dry_run: true` makes the intent explicit and works even if an `output_path` is provided — the path is ignored. Omitting `output_path` simply means there is nowhere to write)_ +2. Why should you validate a generated test case immediately after generation? + _(Generated artefacts may be missing best-practice fields — like a test case description or step metadata — that drop the quality score below the 80-point threshold required for plan coverage to count in Quality Hub)_ +3. What annotation does `provar.pageobject.generate` use for Salesforce pages vs non-Salesforce pages? + _(`@SalesforcePage` for Salesforce pages; `@Page` for standard web pages)_ + +--- + +## Module 6: Run Configuration + +**Learning objectives** + +- Generate and validate a `provardx-properties.json` file using the AI +- Update individual properties without editing JSON by hand +- Understand the connection between the properties file and test execution + +### 6.1 — What is `provardx-properties.json`? + +This is the configuration file that tells the Provar DX CLI how and where to run tests: which Provar installation to use, which test cases or suites to run, which environment, browser, and connections to use. The MCP tools can create, read, update, and validate this file on your behalf. + +### Lab 6.1 — Generate a properties file + +> "Generate a `provardx-properties.json` at `/path/to/project/provardx-properties.json`. Set projectPath to `/path/to/project` and provarHome to `/path/to/provar/installation`. Then validate it and tell me which fields still need to be filled in." + +**What to observe:** + +- A complete properties file created from the standard template +- The validation response lists any fields still containing `${PLACEHOLDER}` values — these need real values before the file can drive a test run + +### Lab 6.2 — Update a specific property + +> "Update the `environment.testEnvironment` field in `/path/to/project/provardx-properties.json` to `QA`." + +The AI uses `provar.properties.set` to make a targeted update without touching the rest of the file. + +### Knowledge check + +1. What does `provar.automation.config.load` do, and why is it required before triggering a test run? + _(It validates and registers a `provardx-properties.json` as the active configuration in the current session. The compile and testrun tools depend on this loaded state — without it they don't know which project, Provar home, or test cases to use)_ +2. What happens if `provardx-properties.json` still contains `${PLACEHOLDER}` values when you try to run tests? + _(The config load step will surface validation warnings for each unresolved placeholder. The run may still attempt to start but will likely fail when Provar Automation encounters the literal placeholder string instead of a real value)_ + +--- + +## Module 7: Running Tests + +**Learning objectives** + +- Trigger a local Provar Automation test run through the AI +- Poll a Quality Hub managed test run from the AI chat +- Locate and interpret test run artefacts (JUnit XML, HTML reports) + +### Lab 7.1 — Local Provar Automation test run + +> "Load the properties file at `/path/to/project/provardx-properties.json`, compile the Page Objects, then run the tests. Report the results when done." + +**The AI chains these tools:** + +1. `provar.automation.config.load` — registers the properties file +2. `provar.automation.compile` — compiles Java Page Objects +3. `provar.automation.testrun` — executes the run +4. `provar.testrun.report.locate` — finds the report artefacts + +**What to observe:** + +- The AI confirms the properties file is loaded successfully before attempting compilation +- Any compilation errors are surfaced before the test run starts +- After the run, the AI tells you where the JUnit XML and HTML report files are + +### Lab 7.2 — Quality Hub managed test run + +**Pre-requisite:** Authenticate your Quality Hub org with the Salesforce CLI: + +```sh +sf org login web -a MyQHOrg +sf provar quality-hub connect -o MyQHOrg +``` + +Then in your AI client: + +> "Connect to the Quality Hub org MyQHOrg, trigger a test run using the config at `/path/to/project/config/smoke-run.json`, and poll every 30 seconds until the run completes. Tell me the final pass/fail count." + +**What to observe:** + +- The AI extracts the run ID from the trigger response and uses it for polling +- Poll loop continues until status is `Completed`, `Failed`, or `Aborted` +- Final results summarised with pass count, fail count, and any error messages + +### Lab 7.3 — Locate artefacts and read results + +> "Find the JUnit XML results for the run that just completed and summarise any failures." + +The AI uses `provar.testrun.report.locate` to resolve the artefact paths, then reads the JUnit XML to extract failure details. + +### Knowledge check + +1. What does `provar.automation.compile` do, and when is it necessary? + _(It compiles Java Page Object and Page Control source files into class files. It is necessary after any Page Object is created or modified — Provar Automation executes the compiled `.class` files, not the `.java` source)_ +2. Why does a Quality Hub test run use a polling loop rather than waiting synchronously? + _(Quality Hub runs are executed on a remote grid and can take minutes to hours. The MCP tools invoke `sf` CLI subprocesses synchronously, so a long-running run would block the entire AI conversation. Polling with `provar.qualityhub.testrun.report` lets the AI check in periodically and report status without blocking)_ +3. Where does `provar.testrun.report.locate` look for report artefacts? + _(It searches the project's `Results/` directory for the most recent JUnit XML and HTML report files written by the last Provar Automation test run)_ + +--- + +## Module 8: Root Cause Analysis and Defect Creation + +**Learning objectives** + +- Use `provar.testrun.rca` to classify test failures +- Distinguish pre-existing issues from new regressions +- Create a Quality Hub defect from a failed test execution + +### Lab 8.1 — Classify failures after a run + +After a test run with failures: + +> "Analyse the test run that just completed. Classify each failure — which are likely pre-existing issues and which look like new regressions? What Page Objects were involved?" + +**What to observe:** + +- Failures categorised by type: environment issue, locator failure, assertion failure, timeout, etc. +- Pre-existing issues (failures that appear in historical runs) distinguished from new failures +- Page Objects referenced in failures called out for targeted review + +### Lab 8.2 — Create a defect in Quality Hub + +> "The test 'LoginTest' failed with an assertion error on the Account Name field. Create a defect in Quality Hub for it, tagged to the 'Regression' test project." + +The AI uses `provar.qualityhub.defect.create` to raise the defect without you leaving the chat session. + +### Knowledge check + +1. What information does `provar.testrun.rca` use to classify failures as pre-existing vs new? + _(It reads the JUnit XML results from the completed run, analyses failure messages and stack traces, and cross-references them against the test case history and Page Objects involved to identify patterns that suggest a pre-existing flake vs a newly introduced failure)_ +2. What is required in Quality Hub before you can create a defect from an MCP tool call? + _(The Quality Hub org must be connected via `provar.qualityhub.connect` (or `sf provar quality-hub connect`) in the current session, and the test project you're filing against must already exist in Quality Hub)_ + +--- + +## Module 9: Test Plan Management + +**Learning objectives** + +- Add a test case to an existing test plan using the AI +- Create a new test suite inside a plan +- Remove a test instance that is no longer needed + +### Lab 9.1 — Wire a test case into a plan + +After generating a new test case in Module 5: + +> "Add the test case at `/path/to/project/tests/smoke/CreateNewContact.testcase` to the test plan 'Smoke Tests', under the suite 'Contact Management'. Create the instance file at `/path/to/project/plans/SmokeTests/ContactManagement/CreateNewContact.testinstance`." + +The AI uses `provar.testplan.add-instance` to write the `.testinstance` file with the correct `testCasePath` attribute. + +### Lab 9.2 — Create a new suite in a plan + +> "Create a new test suite called 'Account Management' inside the 'Smoke Tests' plan at `/path/to/project/plans/SmokeTests/AccountManagement/`." + +### Lab 9.3 — Validate the plan after changes + +> "Now validate the 'Smoke Tests' plan and confirm coverage has improved." + +### Knowledge check + +1. What file type does `provar.testplan.add-instance` create, and what key attribute does it contain? + _(A `.testinstance` file. The key attribute is `testCasePath`, which holds the relative path to the `.testcase` file being wired into the plan)_ +2. After adding test instances to a plan, how does that affect the `coverage_percent` reported by `provar.project.inspect`? + _(The newly wired test cases move from `uncovered_test_case_paths` to `covered_test_case_paths`, increasing the `coverage_percent` value)_ + +--- + +## Module 10: Putting It All Together + +**Learning objectives** + +- Execute a full end-to-end workflow from project inspection to validated artefacts and test execution +- Combine multiple MCP tools in a single AI conversation + +### Lab 10.1 — The full workflow + +Work through this sequence in a single AI conversation: + +1. **Inspect** — Ask the AI to inspect your project and identify 2–3 coverage gaps +2. **Generate** — Have the AI create a Page Object and a test case for one of those gaps +3. **Validate** — Validate both artefacts and fix any issues the AI finds +4. **Plan** — Add the new test case to an existing test plan +5. **Run** — Load the properties file, compile, and run the tests +6. **Report** — Ask the AI to summarise the run and flag any failures + +### Lab 10.2 — Reflect on the AI loop + +After completing the workflow, consider: + +- How many CLI commands did you avoid typing? +- At which step did the AI's suggestions most closely match what you would have done manually? +- Where did you need to correct the AI or provide more context? + +--- + +## Course summary + +You have now covered the full Provar MCP feature set: + +| Area | Key tools | +| ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| Project awareness | `provar.project.inspect`, `provar.project.validate` | +| Quality validation | `provar.testcase.validate`, `provar.pageobject.validate`, `provar.testsuite.validate`, `provar.testplan.validate` | +| Test authoring | `provar.pageobject.generate`, `provar.testcase.generate` | +| Run configuration | `provar.properties.generate`, `provar.properties.set`, `provar.automation.config.load` | +| Test execution | `provar.automation.testrun`, `provar.qualityhub.testrun`, `provar.testrun.report.locate` | +| Failure analysis | `provar.testrun.rca`, `provar.qualityhub.defect.create` | +| Plan management | `provar.testplan.add-instance`, `provar.testplan.create-suite`, `provar.testplan.remove-instance` | + +## Frequently asked questions + +**Do I need a new license for Provar MCP?** +No. Your existing Provar Automation license covers MCP usage. The MCP server reads your IDE license automatically. + +**Can I use Provar MCP without an existing Provar project?** +The AI can generate new artefacts (Page Objects, test cases, properties files) from scratch, but project-level tools like `provar.project.inspect` and `provar.project.validate` require a project directory with at least a `.testproject` file. We recommend starting from an existing project. + +**Will the AI send my project files to Provar?** +No. The MCP server runs entirely on your local machine. File contents pass between the server and your AI client only (e.g. Claude Desktop, which runs locally). No data is sent to Provar's servers. + +**Is the AI making changes to my project automatically?** +Generation tools write files only when you provide an `output_path` and do not use `dry_run`. If you're unsure, ask the AI to show you the content first (dry run), then confirm before writing. + +**Provar MCP is labelled Beta — is it production-ready?** +Beta means the core feature set is complete and we are gathering feedback. It is suitable for use on real projects, but some edge cases may be rough. Please report issues at [github.com/ProvarTesting/provardx-cli/issues](https://github.com/ProvarTesting/provardx-cli/issues). GA is coming soon. diff --git a/messages/sf.provar.auth.clear.md b/messages/sf.provar.auth.clear.md new file mode 100644 index 00000000..fa1823f1 --- /dev/null +++ b/messages/sf.provar.auth.clear.md @@ -0,0 +1,13 @@ +# summary +Remove the stored Provar API key. + +# description +Deletes the API key stored at ~/.provar/credentials.json. After clearing, the +provar.testcase.validate MCP tool falls back to local validation (structural rules only, +no Quality Hub quality scoring). + +The PROVAR_API_KEY environment variable is not affected by this command. + +# examples +- Clear the stored API key: + <%= config.bin %> <%= command.id %> diff --git a/messages/sf.provar.auth.login.md b/messages/sf.provar.auth.login.md new file mode 100644 index 00000000..9d7ab138 --- /dev/null +++ b/messages/sf.provar.auth.login.md @@ -0,0 +1,31 @@ +# summary + +Log in to Provar Quality Hub and store your API key. + +# description + +Opens a browser to the Provar login page. After you authenticate, your API key +is stored at ~/.provar/credentials.json and used automatically by the Provar MCP +tools and CI/CD integrations. + +The Cognito session tokens are held in memory only for the duration of the key +exchange and are then discarded — only the pv*k* API key is written to disk. + +Run 'sf provar auth status' after login to confirm the key is configured correctly. + +Don't have an account? Request access at: +https://aqqlrlhga7.execute-api.us-east-1.amazonaws.com/dev/auth/request-access + +# flags.url.summary + +Override the Quality Hub API base URL (for testing against a non-production environment). + +# examples + +- Log in interactively (opens browser): + + <%= config.bin %> <%= command.id %> + +- Log in against a staging environment: + + <%= config.bin %> <%= command.id %> --url https://dev.api.example.com diff --git a/messages/sf.provar.auth.rotate.md b/messages/sf.provar.auth.rotate.md new file mode 100644 index 00000000..f00426f2 --- /dev/null +++ b/messages/sf.provar.auth.rotate.md @@ -0,0 +1,23 @@ +# summary + +Rotate your stored Provar Quality Hub API key. + +# description + +Exchanges your current pv*k* key for a new one in a single atomic operation. +The old key is invalidated the moment the new key is issued — there is no window +where both are valid. + +The new key is written to ~/.provar/credentials.json automatically. + +Use this command to rotate your key on a regular schedule (every ~90 days) without +going through the browser login flow again. + +If the current key is already expired or revoked, rotation is not possible — run +sf provar auth login instead to authenticate via browser and get a fresh key. + +# examples + +- Rotate the stored API key: + + <%= config.bin %> <%= command.id %> diff --git a/messages/sf.provar.auth.status.md b/messages/sf.provar.auth.status.md new file mode 100644 index 00000000..c4d3e54e --- /dev/null +++ b/messages/sf.provar.auth.status.md @@ -0,0 +1,13 @@ +# summary +Show the current Provar API key configuration status. + +# description +Reports where the active API key comes from (environment variable or stored file), +shows the key prefix and when it was set, and states whether validation will use the +Quality Hub API or local rules only. The full key is never printed. + +If no key is configured, guidance is shown for logging in or requesting access. + +# examples +- Check auth status: + <%= config.bin %> <%= command.id %> diff --git a/oclif.lock b/oclif.lock index 48c785ad..b1c44ecb 100644 --- a/oclif.lock +++ b/oclif.lock @@ -672,13 +672,13 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": - version "7.22.13" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz" + integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" + "@babel/highlight" "^7.24.6" + picocolors "^1.0.0" "@babel/compat-data@^7.19.1": version "7.19.1" @@ -706,13 +706,14 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== +"@babel/generator@^7.19.0", "@babel/generator@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz" + integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== dependencies: - "@babel/types" "^7.19.0" - "@jridgewell/gen-mapping" "^0.3.2" + "@babel/types" "^7.24.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" "@babel/helper-compilation-targets@^7.19.1": @@ -725,25 +726,25 @@ browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz" + integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== -"@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz" + integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== +"@babel/helper-hoist-variables@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz" + integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.24.6" "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -773,22 +774,22 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== +"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz" + integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.24.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz" + integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz" + integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== "@babel/helper-validator-option@^7.18.6": version "7.18.6" @@ -804,19 +805,20 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== +"@babel/highlight@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz" + integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-validator-identifier" "^7.24.6" chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/parser@^7.18.10", "@babel/parser@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz" - integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== +"@babel/parser@^7.19.1", "@babel/parser@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz" + integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== "@babel/runtime-corejs3@^7.12.5": version "7.19.1" @@ -833,38 +835,38 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.18.10", "@babel/template@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz" + integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/code-frame" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz" - integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.1" - "@babel/types" "^7.19.0" - debug "^4.1.0" + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz" + integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== +"@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.24.6": + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz" + integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-string-parser" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" to-fast-properties "^2.0.0" "@commitlint/cli@^17.1.2": @@ -1081,6 +1083,11 @@ resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@hono/node-server@^1.19.9": + version "1.19.11" + resolved "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz" + integrity sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g== + "@humanwhocodes/config-array@^0.11.13": version "0.11.13" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz" @@ -1109,6 +1116,14 @@ "@inquirer/type" "^1.1.6" chalk "^4.1.2" +"@inquirer/confirm@^3.1.9": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.2.0.tgz#6af1284670ea7c7d95e3f1253684cfbd7228ad6a" + integrity sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw== + dependencies: + "@inquirer/core" "^9.1.0" + "@inquirer/type" "^1.5.3" + "@inquirer/core@^6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@inquirer/core/-/core-6.0.0.tgz" @@ -1129,6 +1144,29 @@ strip-ansi "^6.0.1" wrap-ansi "^6.2.0" +"@inquirer/core@^9.1.0": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.15" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.15.tgz#dbb49ed80df11df74268023b496ac5d9acd22b3a" + integrity sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g== + "@inquirer/password@^1.1.16": version "1.1.16" resolved "https://registry.npmjs.org/@inquirer/password/-/password-1.1.16.tgz" @@ -1139,11 +1177,34 @@ ansi-escapes "^4.3.2" chalk "^4.1.2" +"@inquirer/password@^2.1.9": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-2.2.0.tgz#0b6f26336c259c8a9e5f5a3f2e1a761564f764ba" + integrity sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg== + dependencies: + "@inquirer/core" "^9.1.0" + "@inquirer/type" "^1.5.3" + ansi-escapes "^4.3.2" + "@inquirer/type@^1.1.6": version "1.2.0" resolved "https://registry.npmjs.org/@inquirer/type/-/type-1.2.0.tgz" integrity sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA== +"@inquirer/type@^1.5.3": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.5.5.tgz#303ea04ce7ad2e585b921b662b3be36ef7b4f09b" + integrity sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA== + dependencies: + mute-stream "^1.0.0" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" @@ -1185,26 +1246,26 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.1" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1217,17 +1278,17 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@jsforce/jsforce-node@^3.2.0": version "3.2.0" - resolved "https://registry.yarnpkg.com/@jsforce/jsforce-node/-/jsforce-node-3.2.0.tgz#4b104613fc9bb74e0e38d2c00936ea2b228ba73a" + resolved "https://registry.npmjs.org/@jsforce/jsforce-node/-/jsforce-node-3.2.0.tgz" integrity sha512-3GjWNgWs0HFajVhIhwvBPb0B45o500wTBNEBYxy8XjeeRra+qw8A9xUrfVU7TAGev8kXuKhjJwaTiSzThpEnew== dependencies: "@sindresorhus/is" "^4" @@ -1245,6 +1306,29 @@ strip-ansi "^6.0.0" xml2js "^0.6.2" +"@modelcontextprotocol/sdk@^1.8.0": + version "1.27.1" + resolved "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz" + integrity sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA== + dependencies: + "@hono/node-server" "^1.19.9" + ajv "^8.17.1" + ajv-formats "^3.0.1" + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.5" + eventsource "^3.0.2" + eventsource-parser "^3.0.0" + express "^5.2.1" + express-rate-limit "^8.2.1" + hono "^4.11.4" + jose "^6.1.3" + json-schema-typed "^8.0.2" + pkce-challenge "^5.0.0" + raw-body "^3.0.0" + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1519,6 +1603,7 @@ indent-string "^4.0.0" is-wsl "^2.2.0" js-yaml "^3.14.1" + minimatch "^9.0.4" natural-orderby "^2.0.3" object-treeify "^1.1.33" password-prompt "^1.1.3" @@ -1531,10 +1616,10 @@ wordwrap "^1.0.0" wrap-ansi "^7.0.0" -"@oclif/core@^3.26.2", "@oclif/core@^3.26.5": - version "3.26.5" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.26.5.tgz#6a1962971fcaa4e235c0d6a83d50681ccb2bd0e4" - integrity sha512-uRmAujGJjLhhgpLylbiuHuPt9Ec7u6aJ72utuSPNTRw47+W5vbQSGnLGPiil1Mt5YDL+zFOyTVH6Uv3NSP2SaQ== +"@oclif/core@^3.26.6", "@oclif/core@^3.27.0": + version "3.27.0" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-3.27.0.tgz#a22a4ff4e5811db7a182b1687302237a57802381" + integrity sha512-Fg93aNFvXzBq5L7ztVHFP2nYwWU1oTCq48G0TjF/qC1UN36KWa2H5Hsm72kERd5x/sjy2M2Tn4kDEorUlpXOlw== dependencies: "@types/cli-progress" "^3.11.5" ansi-escapes "^4.3.2" @@ -1544,7 +1629,7 @@ clean-stack "^3.0.1" cli-progress "^3.12.0" color "^4.2.3" - debug "^4.3.4" + debug "^4.3.5" ejs "^3.1.10" get-package-type "^0.1.0" globby "^11.1.0" @@ -1723,19 +1808,81 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@oozcitak/dom@1.15.10": + version "1.15.10" + resolved "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz" + integrity sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ== + dependencies: + "@oozcitak/infra" "1.0.8" + "@oozcitak/url" "1.0.4" + "@oozcitak/util" "8.3.8" + +"@oozcitak/infra@1.0.8": + version "1.0.8" + resolved "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz" + integrity sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg== + dependencies: + "@oozcitak/util" "8.3.8" + +"@oozcitak/url@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz" + integrity sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw== + dependencies: + "@oozcitak/infra" "1.0.8" + "@oozcitak/util" "8.3.8" + +"@oozcitak/util@8.3.8": + version "8.3.8" + resolved "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz" + integrity sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ== + +"@pinojs/redact@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@pinojs/redact/-/redact-0.4.0.tgz#c3de060dd12640dcc838516aa2a6803cc7b2e9d6" + integrity sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@provartesting/provardx-plugins-utils@0.0.2-beta": - version "0.0.2-beta" - resolved "https://registry.yarnpkg.com/@provartesting/provardx-plugins-utils/-/provardx-plugins-utils-0.0.2-beta.tgz#07cb9ca48391b21a91210b9fa74f0bad01c4eaba" - integrity sha512-UwBxqI0grO65jWz57Z5ffQZlYiu5T0T4wKCCQGxzHRpZJIG1mqIf/uCOSOd4BerkSgeoZJjRf7aTIa3F6H8qkg== +"@provartesting/provardx-plugins-automation@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@provartesting/provardx-plugins-automation/-/provardx-plugins-automation-1.2.2.tgz#db3c83a6449ea5d3dd0bb68fd72db77fd67ee1ee" + integrity sha512-HXCn85ZPXNDa4tUQYGUVpGjZNQPhbgBBiJ9P4NyTnpD0t2HZ+RbZlQUcmCw0P+HV9wxxHVJZNdT21xV6ICTbhA== + dependencies: + "@oclif/core" "^3.27.0" + "@provartesting/provardx-plugins-utils" "1.3.3" + "@salesforce/core" "^7.2.0" + "@salesforce/sf-plugins-core" "^9.1.1" + axios "^1.13.5" + xml-js "^1.6.11" + +"@provartesting/provardx-plugins-manager@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@provartesting/provardx-plugins-manager/-/provardx-plugins-manager-1.3.2.tgz#bad2bebf2eb2ffed7928747288593faecf47f026" + integrity sha512-ZBreiTbzB7Ur+CAyKxN9dh00KCj5XuPRi2wSRooujmuRPHrDfLPZc/pNV+XEUjGHvwWXxRwF3KByvrxLSs28lQ== dependencies: "@oclif/core" "^3.26.2" + "@provartesting/provardx-plugins-utils" "1.3.3" "@salesforce/core" "^7.2.0" + "@salesforce/kit" "^3.2.2" "@salesforce/sf-plugins-core" "^9.0.1" + ansis "^3.3.2" + cli-ux "^6.0.9" + uuid "^10.0.0" + xml2js "^0.6.2" + xmlbuilder2 "^3.1.1" + +"@provartesting/provardx-plugins-utils@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@provartesting/provardx-plugins-utils/-/provardx-plugins-utils-1.3.3.tgz#66b904c4edd70b10cabfb98e24d61ff2a18f18dc" + integrity sha512-+R4rWTy/aHJhykwev6iUCTOhW1hnkt1M/zOAk16SGdrOU9BWUBWTIU1jTLdLJeOTv8yGSoWrAfeTScSbwTL2mw== + dependencies: + "@oclif/core" "^3.27.0" + "@salesforce/core" "^7.2.0" + "@salesforce/sf-plugins-core" "^9.1.1" cli-ux "^6.0.9" jsonschema "^1.4.1" node-stream-zip "^1.15.0" @@ -1755,7 +1902,7 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.0" -"@salesforce/core@^6.4.7", "@salesforce/core@^6.5.1", "@salesforce/core@^6.5.2": +"@salesforce/core@^6.4.7": version "6.5.2" resolved "https://registry.npmjs.org/@salesforce/core/-/core-6.5.2.tgz" integrity sha512-/tviKhMQRMNZlbG/IldCXy6dLAOtCX9gysdiVeCoEsgWcXT72rj02fJg4PQMtc69GAu2vnRSbaRewfrC8Mrw8g== @@ -1779,16 +1926,40 @@ semver "^7.5.4" ts-retry-promise "^0.7.1" +"@salesforce/core@^7.0.0", "@salesforce/core@^7.3.9": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.5.0.tgz#cfa57281978c9d5df6f7419e5bc58ea914726cf5" + integrity sha512-mPg9Tj2Qqe/TY7q+CRNSeYYTV+dj/LflM7Fu/32EPLCEPGVIiSp/RaTFLTZwDcFX9BVYHOa2h6oliuO2Qnno+A== + dependencies: + "@jsforce/jsforce-node" "^3.2.0" + "@salesforce/kit" "^3.1.6" + "@salesforce/schemas" "^1.9.0" + "@salesforce/ts-types" "^2.0.10" + ajv "^8.15.0" + change-case "^4.1.2" + fast-levenshtein "^3.0.0" + faye "^1.4.0" + form-data "^4.0.0" + js2xmlparser "^4.0.1" + jsonwebtoken "9.0.2" + jszip "3.10.1" + pino "^9.2.0" + pino-abstract-transport "^1.2.0" + pino-pretty "^11.2.1" + proper-lockfile "^4.1.2" + semver "^7.6.2" + ts-retry-promise "^0.8.1" + "@salesforce/core@^7.2.0", "@salesforce/core@^7.3.3": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.3.4.tgz#0630a5b236254a7a690872d7179f7216dff02560" - integrity sha512-m6XY5Ju3rh2ljaqVydSPS1vekw+EyQ4uHiNV6mIdJeBile7WJgn5TbE7AJUU91ASPYy9X+/ZuiHyIbOaMc2YrA== + version "7.3.3" + resolved "https://registry.npmjs.org/@salesforce/core/-/core-7.3.3.tgz" + integrity sha512-THjYnOrfj0vW+qvlm70NDasH3RHD03cm884yi1+1axA4ugS4FFxXrPDPWAEU5ve5B4vnT7CJfuD/Q56l67ug8w== dependencies: "@jsforce/jsforce-node" "^3.2.0" "@salesforce/kit" "^3.1.1" "@salesforce/schemas" "^1.7.0" "@salesforce/ts-types" "^2.0.9" - ajv "^8.13.0" + ajv "^8.12.0" change-case "^4.1.2" faye "^1.4.0" form-data "^4.0.0" @@ -1839,53 +2010,51 @@ typescript "^4.9.5" wireit "^0.14.1" -"@salesforce/kit@^3.0.15": - version "3.0.15" - resolved "https://registry.npmjs.org/@salesforce/kit/-/kit-3.0.15.tgz" - integrity sha512-XkA8jsuLvVnyP460dAbU3pBFP2IkmmmsVxMQVifcKKbNWaIBbZBzAfj+vdaQfnvZyflLhsrFT3q2xkb0vHouPg== +"@salesforce/kit@^3.0.15", "@salesforce/kit@^3.1.0", "@salesforce/kit@^3.1.1", "@salesforce/kit@^3.2.2": + version "3.2.4" + resolved "https://registry.npmjs.org/@salesforce/kit/-/kit-3.2.4.tgz" + integrity sha512-9buqZ2puIGWqjUFWYNroSeNih4d1s9kdQAzZfutr/Re/JMl6xBct0ATO5LVb1ty5UhdBruJrVaiTg03PqVKU+Q== dependencies: - "@salesforce/ts-types" "^2.0.9" - tslib "^2.6.2" + "@salesforce/ts-types" "^2.0.12" -"@salesforce/kit@^3.1.0", "@salesforce/kit@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.1.1.tgz#d2147a50887214763cdf1c456d306b6da554d928" - integrity sha512-Cjkh+USp5PtdZmD30r1Y7d+USpIhQz9B48w76esBtYpgqzhyj806LHkVgEfmorLNq2Qe8EO5rtUYd+XZ3rnV9w== +"@salesforce/kit@^3.1.2", "@salesforce/kit@^3.1.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.2.6.tgz#6a6c13b463b51694a43d61094af67171086ed4f5" + integrity sha512-O8S4LWerHa9Zosqh+IoQjgLtpxMOfObRxaRnUdRV4MLtFUi+bQxQiyFvve6eEaBaMP1b1xVDQpvSvQ+PXEDGFQ== dependencies: - "@salesforce/ts-types" "^2.0.9" - tslib "^2.6.2" + "@salesforce/ts-types" "^2.0.12" "@salesforce/prettier-config@^0.0.3": version "0.0.3" resolved "https://registry.npmjs.org/@salesforce/prettier-config/-/prettier-config-0.0.3.tgz" integrity sha512-hYOhoPTCSYMDYn+U1rlEk16PoBeAJPkrdg4/UtAzupM1mRRJOwEPMG1d7U8DxJFKuXW3DMEYWr2MwAIBDaHmFg== -"@salesforce/schemas@^1.6.1": - version "1.6.1" - resolved "https://registry.npmjs.org/@salesforce/schemas/-/schemas-1.6.1.tgz" - integrity sha512-eVy947ZMxCJReKJdgfddUIsBIbPTa/i8RwQGwxq4/ss38H5sLOAeSTaun9V7HpJ1hkpDznWKfgzYvjsst9K6ig== - -"@salesforce/schemas@^1.7.0": +"@salesforce/schemas@^1.6.1", "@salesforce/schemas@^1.7.0": version "1.7.0" - resolved "https://registry.yarnpkg.com/@salesforce/schemas/-/schemas-1.7.0.tgz#b7e0af3ee414ae7160bce351c0184d77ccb98fe3" + resolved "https://registry.npmjs.org/@salesforce/schemas/-/schemas-1.7.0.tgz" integrity sha512-Z0PiCEV55khm0PG+DsnRYCjaDmacNe3HDmsoSm/CSyYvJJm+D5vvkHKN9/PKD/gaRe8XAU836yfamIYFblLINw== -"@salesforce/sf-plugins-core@^7.1.4": - version "7.1.8" - resolved "https://registry.npmjs.org/@salesforce/sf-plugins-core/-/sf-plugins-core-7.1.8.tgz" - integrity sha512-5QAcxCQ/YX1hywfKHRIe6RAVsHM27nMUOJlIW9+H2pdJeZbXg1TtMITjv7oQfxmGFlRG2UzgAm5ZfUlrl0IHtQ== - dependencies: - "@inquirer/confirm" "^2.0.17" - "@inquirer/password" "^1.1.16" - "@oclif/core" "^3.18.2" - "@salesforce/core" "^6.5.2" - "@salesforce/kit" "^3.0.15" +"@salesforce/schemas@^1.9.0": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@salesforce/schemas/-/schemas-1.10.3.tgz#52c867fdd60679cf216110aa49542b7ad391f5d1" + integrity sha512-FKfvtrYTcvTXE9advzS25/DEY9yJhEyLvStm++eQFtnAaX1pe4G3oGHgiQ0q55BM5+0AlCh0+0CVtQv1t4oJRA== + +"@salesforce/sf-plugins-core@^9.0.0", "@salesforce/sf-plugins-core@^9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@salesforce/sf-plugins-core/-/sf-plugins-core-9.1.1.tgz#8818fdb23e0f174d9e6dded0cf34a88be5e3cc44" + integrity sha512-5d4vGLqb1NZoHvDpuTu96TsFg/lexdnQNWC0h7GhOqxikJBpxk6P1DEbk9HrZWL18Gs1YXO9OCj2g8nKqbIC/Q== + dependencies: + "@inquirer/confirm" "^3.1.9" + "@inquirer/password" "^2.1.9" + "@oclif/core" "^3.26.6" + "@salesforce/core" "^7.3.9" + "@salesforce/kit" "^3.1.2" "@salesforce/ts-types" "^2.0.9" chalk "^5.3.0" "@salesforce/sf-plugins-core@^9.0.1": version "9.0.7" - resolved "https://registry.yarnpkg.com/@salesforce/sf-plugins-core/-/sf-plugins-core-9.0.7.tgz#77ffc67df994e0cec205827462811f521e3086ba" + resolved "https://registry.npmjs.org/@salesforce/sf-plugins-core/-/sf-plugins-core-9.0.7.tgz" integrity sha512-5F6/ax7welNZrizpl9QSQmGADqlCzFDB8t8I5P/n2LplMb3CwJRrZPcOZxJNnhlfXNlLrYtoShv2C+yrxgqYUA== dependencies: "@inquirer/confirm" "^2.0.17" @@ -1896,31 +2065,42 @@ "@salesforce/ts-types" "^2.0.9" chalk "^5.3.0" -"@salesforce/ts-types@^2.0.9": - version "2.0.9" - resolved "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.9.tgz" - integrity sha512-boUD9jw5vQpTCPCCmK/NFTWjSuuW+lsaxOynkyNXLW+zxOc4GDjhtKc4j0vWZJQvolpafbyS8ZLFHZJvs12gYA== - dependencies: - tslib "^2.6.2" +"@salesforce/ts-types@^2.0.10", "@salesforce/ts-types@^2.0.12", "@salesforce/ts-types@^2.0.9": + version "2.0.12" + resolved "https://registry.npmjs.org/@salesforce/ts-types/-/ts-types-2.0.12.tgz" + integrity sha512-BIJyduJC18Kc8z+arUm5AZ9VkPRyw1KKAm+Tk+9LT99eOzhNilyfKzhZ4t+tG2lIGgnJpmytZfVDZ0e2kFul8g== "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz" integrity sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ== -"@sindresorhus/is@^4", "@sindresorhus/is@^4.0.0": "@sindresorhus/is@^4", "@sindresorhus/is@^4.0.0": version "4.6.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": - version "1.8.3" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + version "1.8.6" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" +"@sinonjs/fake-timers@^15.1.1": + version "15.1.1" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz" + integrity sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz" @@ -1937,10 +2117,18 @@ lodash.get "^4.4.2" type-detect "^4.0.8" +"@sinonjs/samsam@^9.0.3": + version "9.0.3" + resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.3.tgz" + integrity sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ== + dependencies: + "@sinonjs/commons" "^3.0.1" + type-detect "^4.1.0" + "@sinonjs/text-encoding@^0.7.1": - version "0.7.2" - resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz" - integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== + version "0.7.3" + resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz" + integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== "@smithy/abort-controller@^2.1.1": version "2.1.1" @@ -2478,25 +2666,11 @@ dependencies: "@types/node" "*" -"@types/concat-stream@^1.6.0": - version "1.6.1" - resolved "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz" - integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA== - dependencies: - "@types/node" "*" - "@types/expect@^1.20.4": version "1.20.4" resolved "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/form-data@0.0.33": - version "0.0.33" - resolved "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz" - integrity sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw== - dependencies: - "@types/node" "*" - "@types/glob@~7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" @@ -2578,11 +2752,6 @@ resolved "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz" integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== -"@types/node@^10.0.3": - version "10.17.60" - resolved "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - "@types/node@^12.19.9": version "12.20.55" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" @@ -2602,15 +2771,17 @@ "@types/node@^18.15.3": version "18.19.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.31.tgz#b7d4a00f7cb826b60a543cebdbda5d189aaecdcd" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz" integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA== dependencies: undici-types "~5.26.4" -"@types/node@^8.0.0": - version "8.10.66" - resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz" - integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@types/node@^22.5.5": + version "22.19.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.17.tgz#09c71fb34ba2510f8ac865361b1fcb9552b8a581" + integrity sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q== + dependencies: + undici-types "~6.21.0" "@types/normalize-package-data@^2.4.0": version "2.4.4" @@ -2622,11 +2793,6 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/qs@^6.2.31": - version "6.9.14" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz" - integrity sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA== - "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz" @@ -2654,18 +2820,18 @@ dependencies: "@types/sinonjs__fake-timers" "*" +"@types/sinon@^21.0.0": + version "21.0.0" + resolved "https://registry.npmjs.org/@types/sinon/-/sinon-21.0.0.tgz" + integrity sha512-+oHKZ0lTI+WVLxx1IbJDNmReQaIsQJjN2e7UUrJHEeByG7bFeKJYsv1E75JxTQ9QKJDp21bAa/0W2Xo4srsDnw== + dependencies: + "@types/sinonjs__fake-timers" "*" + "@types/sinonjs__fake-timers@*": version "8.1.5" resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz" integrity sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ== -"@types/unzipper@^0.10.9": - version "0.10.9" - resolved "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.9.tgz" - integrity sha512-vHbmFZAw8emNAOVkHVbS3qBnbr0x/qHQZ+ei1HE7Oy6Tyrptl+jpqnOX+BF5owcu/HZLOV0nJK+K9sjs1Ox2JA== - dependencies: - "@types/node" "*" - "@types/vinyl@^2.0.4": version "2.0.6" resolved "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz" @@ -2790,6 +2956,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -2836,6 +3010,13 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -2846,27 +3027,15 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.11.0, ajv@^8.12.0: - version "8.12.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" - integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== +ajv@^8.0.0, ajv@^8.11.0, ajv@^8.12.0, ajv@^8.15.0, ajv@^8.17.1: + version "8.18.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: fast-deep-equal "^3.1.3" - fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.4.1" - uri-js "^4.4.1" ansi-colors@4.1.1: version "4.1.1" @@ -2919,6 +3088,11 @@ ansicolors@~0.3.2: resolved "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== +ansis@^3.3.2: + version "3.17.0" + resolved "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz" + integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" @@ -3082,7 +3256,7 @@ arrify@^2.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@*, asap@^2.0.0, asap@~2.0.6: +asap@*, asap@^2.0.0: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== @@ -3129,14 +3303,14 @@ available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.6: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz" integrity sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg== -axios@^1.6.7: - version "1.6.8" - resolved "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz" - integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== +axios@^1.13.5: + version "1.14.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.14.0.tgz#7c29f4cf2ea91ef05018d5aa5399bf23ed3120eb" + integrity sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ== dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" + follow-redirects "^1.15.11" + form-data "^4.0.5" + proxy-from-env "^2.1.0" balanced-match@^1.0.0: version "1.0.2" @@ -3189,6 +3363,21 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +body-parser@^2.2.1: + version "2.2.2" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.3" + http-errors "^2.0.0" + iconv-lite "^0.7.0" + on-finished "^2.4.1" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" + bowser@^2.11.0: version "2.11.0" resolved "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz" @@ -3274,6 +3463,11 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cacache@^15.0.3, cacache@^15.0.5, cacache@^15.2.0: version "15.3.0" resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" @@ -3368,7 +3562,15 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7: +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.2, call-bind@^1.0.5: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -3379,6 +3581,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -3433,11 +3643,6 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" -caseless@^0.12.0, caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - chai@^4.3.10: version "4.3.10" resolved "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz" @@ -3747,7 +3952,7 @@ colors@1.0.3: resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw== -combined-stream@^1.0.6, combined-stream@^1.0.8: +combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3797,16 +4002,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.6.0, concat-stream@^1.6.2: - version "1.6.2" - resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" @@ -3821,7 +4016,12 @@ constant-case@^3.0.4: tslib "^2.0.3" upper-case "^2.0.2" -content-type@^1.0.4: +content-disposition@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== + +content-type@^1.0.4, content-type@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -3857,6 +4057,16 @@ convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + core-js-compat@^3.34.0: version "3.35.1" resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz" @@ -3879,6 +4089,14 @@ core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.5: + version "2.8.6" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz" + integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig-typescript-loader@^4.0.0: version "4.4.0" resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz" @@ -3910,10 +4128,10 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -3933,7 +4151,7 @@ csv-parse@^4.8.2: csv-parse@^5.5.2: version "5.5.5" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.5.tgz#68a271a9092877b830541805e14c8a80e6a22517" + resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz" integrity sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ== csv-stringify@^5.3.4: @@ -3943,7 +4161,7 @@ csv-stringify@^5.3.4: csv-stringify@^6.4.4: version "6.4.6" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-6.4.6.tgz#9ccf87cb8b017c96673a9fa061768c8ba83e8b98" + resolved "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz" integrity sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag== dargs@^7.0.0: @@ -3970,6 +4188,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.5, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz" @@ -4070,6 +4295,11 @@ depd@^1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" @@ -4088,16 +4318,26 @@ diff@5.0.0: resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -diff@^4.0.1, diff@^4.0.2: +diff@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^4.0.2: + version "4.0.4" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== + diff@^5.0.0: version "5.1.0" resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== +diff@^8.0.3: + version "8.0.4" + resolved "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz" + integrity sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -4164,6 +4404,15 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" @@ -4176,20 +4425,18 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" -ejs@^3.1.10: +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10, ejs@^3.1.6, ejs@^3.1.8: version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" -ejs@^3.1.6, ejs@^3.1.8, ejs@^3.1.9: - version "3.1.9" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz" - integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== - dependencies: - jake "^10.8.5" - electron-to-chromium@^1.4.648: version "1.4.657" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.657.tgz" @@ -4205,6 +4452,11 @@ emoji-regex@^9.2.2: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" @@ -4296,18 +4548,23 @@ es-array-method-boxes-properly@^1.0.0: resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.0.0, es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz" @@ -4317,6 +4574,16 @@ es-set-tostringtag@^2.0.1: has-tostringtag "^1.0.0" hasown "^2.0.0" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz" @@ -4582,6 +4849,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" @@ -4597,6 +4869,18 @@ events@^3.3.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +eventsource-parser@^3.0.0, eventsource-parser@^3.0.1: + version "3.0.6" + resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + +eventsource@^3.0.2: + version "3.0.7" + resolved "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz" + integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + dependencies: + eventsource-parser "^3.0.1" + execa@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz" @@ -4627,6 +4911,47 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +express-rate-limit@^8.2.1: + version "8.3.1" + resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz" + integrity sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw== + dependencies: + ip-address "10.1.0" + +express@^5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + extend@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -4651,6 +4976,11 @@ fast-copy@^3.0.0: resolved "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz" integrity sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA== +fast-copy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" + integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -4694,6 +5024,11 @@ fast-safe-stringify@^2.1.1: resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fast-xml-parser@4.2.5: version "4.2.5" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz" @@ -4767,6 +5102,18 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + find-cache-dir@^3.2.0: version "3.3.2" resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz" @@ -4833,10 +5180,10 @@ flatted@^3.2.9: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.15.6: - version "1.15.6" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +follow-redirects@^1.15.11: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== for-each@^0.3.3: version "0.3.3" @@ -4861,15 +5208,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -form-data@^2.2.0: - version "2.5.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" @@ -4879,6 +5217,27 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + fromentries@^1.2.0: version "1.3.2" resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz" @@ -5024,26 +5383,34 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-port@^3.1.0: - version "3.2.0" - resolved "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz" - integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" get-stream@^5.0.0, get-stream@^5.1.0: version "5.2.0" @@ -5186,12 +5553,10 @@ globby@^11.0.1, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== got@^11: version "11.8.6" @@ -5257,14 +5622,14 @@ has-proto@^1.0.1: resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.2, has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.1: +has-tostringtag@^1.0.0, has-tostringtag@^1.0.1, has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" @@ -5282,10 +5647,10 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== +hasown@^2.0.0, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" @@ -5307,6 +5672,11 @@ help-me@^5.0.0: resolved "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz" integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== +hono@^4.11.4: + version "4.12.7" + resolved "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz" + integrity sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" @@ -5341,16 +5711,6 @@ htmlparser2@^9.0.0: domutils "^3.1.0" entities "^4.5.0" -http-basic@^8.1.1: - version "8.1.3" - resolved "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz" - integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw== - dependencies: - caseless "^0.12.0" - concat-stream "^1.6.2" - http-response-object "^3.0.1" - parse-cache-control "^1.0.1" - http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" @@ -5368,6 +5728,17 @@ http-call@^5.2.2: parse-json "^4.0.0" tunnel-agent "^0.6.0" +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + http-parser-js@>=0.5.1: version "0.5.8" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" @@ -5391,13 +5762,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-response-object@^3.0.1: - version "3.0.2" - resolved "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz" - integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA== - dependencies: - "@types/node" "^10.0.3" - http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz" @@ -5463,6 +5827,13 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -5523,7 +5894,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5587,11 +5958,21 @@ interpret@^1.0.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +ip-address@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + ip@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz" @@ -5732,6 +6113,11 @@ is-plain-object@^5.0.0: resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -5935,6 +6321,11 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +jose@^6.1.3: + version "6.2.1" + resolved "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz" + integrity sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw== + joycon@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz" @@ -5945,14 +6336,7 @@ joycon@^3.1.1: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@3.14.1, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -5960,6 +6344,13 @@ js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js2xmlparser@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz" @@ -6043,6 +6434,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema-typed@^8.0.2: + version "8.0.2" + resolved "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz" + integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" @@ -6400,6 +6796,11 @@ lowercase-keys@^2.0.0: resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" @@ -6412,11 +6813,6 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -"lru-cache@^9.1.1 || ^10.0.0": - version "10.1.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz" - integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== - lunr@^2.3.9: version "2.3.9" resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz" @@ -6519,6 +6915,16 @@ marked@^4.3.0: resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + "mem-fs-editor@^8.1.2 || ^9.0.0", mem-fs-editor@^9.0.0: version "9.7.0" resolved "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-9.7.0.tgz" @@ -6567,6 +6973,11 @@ meow@^8.0.0, meow@^8.1.2: type-fest "^0.18.0" yargs-parser "^20.2.3" +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -6590,6 +7001,11 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-types@^2.1.12: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" @@ -6597,6 +7013,13 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + mime@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz" @@ -6629,7 +7052,7 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1, minimatch@^9.0.3: +minimatch@9.0.3: version "9.0.3" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== @@ -6657,9 +7080,9 @@ minimatch@^7.2.0: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: +minimatch@^9.0.0, minimatch@^9.0.1, minimatch@^9.0.3, minimatch@^9.0.4: version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== dependencies: brace-expansion "^2.0.1" @@ -6852,12 +7275,12 @@ mri@^1.1.5: resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== -ms@2.1.2: +ms@2.1.2, ms@^2.0.0, ms@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6927,6 +7350,11 @@ negotiator@^0.6.2, negotiator@^0.6.3: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + nise@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz" @@ -7240,15 +7668,15 @@ nyc@^15.1.0: test-exclude "^6.0.0" yargs "^15.0.2" -object-assign@^4.1.1: +object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.1, object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-keys@^1.1.1: version "1.1.1" @@ -7329,6 +7757,13 @@ on-exit-leak-free@^2.1.0: resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz" integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" @@ -7547,11 +7982,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-cache-control@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz" - integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg== - parse-conflict-json@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz" @@ -7579,6 +8009,11 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + pascal-case@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz" @@ -7624,20 +8059,25 @@ path-parse@^1.0.7: integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-scurry@^1.10.1: - version "1.10.1" - resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + version "1.11.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: - lru-cache "^9.1.1 || ^10.0.0" + lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + version "1.9.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz" + integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g== dependencies: isarray "0.0.1" +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" @@ -7649,9 +8089,9 @@ pathval@^1.1.1: integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -7668,20 +8108,19 @@ pify@^4.0.1: resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.1.0, pino-abstract-transport@v1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz" - integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== +pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.1.0, pino-abstract-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz" + integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== dependencies: readable-stream "^4.0.0" split2 "^4.0.0" -pino-abstract-transport@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" - integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== +pino-abstract-transport@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60" + integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw== dependencies: - readable-stream "^4.0.0" split2 "^4.0.0" pino-pretty@^10.3.1: @@ -7704,11 +8143,36 @@ pino-pretty@^10.3.1: sonic-boom "^3.0.0" strip-json-comments "^3.1.1" +pino-pretty@^11.2.1: + version "11.3.0" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.3.0.tgz#390b3be044cf3d2e9192c7d19d44f6b690468f2e" + integrity sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.2" + fast-safe-stringify "^2.1.1" + help-me "^5.0.0" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^4.0.1" + strip-json-comments "^3.1.1" + pino-std-serializers@^6.0.0: version "6.2.2" resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz" integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== +pino-std-serializers@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz#a7b0cd65225f29e92540e7853bd73b07479893fc" + integrity sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw== + pino@^8.18.0, pino@^8.21.0: version "8.21.0" resolved "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz" @@ -7724,24 +8188,29 @@ pino@^8.18.0, pino@^8.21.0: real-require "^0.2.0" safe-stable-stringify "^2.3.1" sonic-boom "^3.7.0" - thread-stream "^2.0.0" + thread-stream "^2.6.0" -pino@^8.21.0: - version "8.21.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" - integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== +pino@^9.2.0: + version "9.14.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.14.0.tgz#673d9711c2d1e64d18670c1ec05ef7ba14562556" + integrity sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w== dependencies: + "@pinojs/redact" "^0.4.0" atomic-sleep "^1.0.0" - fast-redact "^3.1.1" on-exit-leak-free "^2.1.0" - pino-abstract-transport "^1.2.0" - pino-std-serializers "^6.0.0" - process-warning "^3.0.0" + pino-abstract-transport "^2.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" quick-format-unescaped "^4.0.3" real-require "^0.2.0" safe-stable-stringify "^2.3.1" - sonic-boom "^3.7.0" - thread-stream "^2.6.0" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + +pkce-challenge@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz" + integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" @@ -7819,6 +8288,11 @@ process-warning@^3.0.0: resolved "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz" integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== + process@^0.11.10: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" @@ -7847,13 +8321,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -promise@^8.0.0: - version "8.3.0" - resolved "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz" - integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== - dependencies: - asap "~2.0.6" - prop-types@^15.7.2: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" @@ -7872,10 +8339,18 @@ proper-lockfile@^4.1.2: retry "^0.12.0" signal-exit "^3.0.2" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== psl@^1.1.33: version "1.9.0" @@ -7895,12 +8370,12 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@^6.4.0: - version "6.12.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz" - integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg== +qs@^6.14.0, qs@^6.14.1: + version "6.15.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz" + integrity sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ== dependencies: - side-channel "^1.0.6" + side-channel "^1.1.0" querystringify@^2.1.1: version "2.2.0" @@ -7934,6 +8409,21 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.0, raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" @@ -7998,7 +8488,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.0.2, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -8202,6 +8692,17 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +router@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/router/-/router-2.2.0.tgz" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + run-async@^2.0.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" @@ -8306,11 +8807,33 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: semver@^7.6.0: version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" +semver@^7.6.2: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.npmjs.org/send/-/send-1.2.1.tgz" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + sentence-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz" @@ -8332,6 +8855,16 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + server-destroy@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz" @@ -8368,6 +8901,11 @@ setimmediate@^1.0.5: resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -8407,15 +8945,45 @@ shx@0.3.4: minimist "^1.2.3" shelljs "^0.8.5" -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.4, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" @@ -8455,6 +9023,17 @@ sinon@10.0.0: nise "^4.1.0" supports-color "^7.1.0" +sinon@^21.0.3: + version "21.0.3" + resolved "https://registry.npmjs.org/sinon/-/sinon-21.0.3.tgz" + integrity sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA== + dependencies: + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^15.1.1" + "@sinonjs/samsam" "^9.0.3" + diff "^8.0.3" + supports-color "^7.2.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" @@ -8515,6 +9094,13 @@ sonic-boom@^3.0.0, sonic-boom@^3.7.0: dependencies: atomic-sleep "^1.0.0" +sonic-boom@^4.0.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.1.tgz#28598250df4899c0ac572d7e2f0460690ba6a030" + integrity sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q== + dependencies: + atomic-sleep "^1.0.0" + sort-keys@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz" @@ -8624,6 +9210,11 @@ ssri@^9.0.0: dependencies: minipass "^3.1.1" +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -8678,20 +9269,20 @@ string.prototype.trimstart@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: +string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -8781,7 +9372,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -8801,22 +9392,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -sync-request@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz" - integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw== - dependencies: - http-response-object "^3.0.1" - sync-rpc "^1.2.1" - then-request "^6.0.0" - -sync-rpc@^1.2.1: - version "1.3.6" - resolved "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz" - integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== - dependencies: - get-port "^3.1.0" - tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" @@ -8865,23 +9440,6 @@ textextensions@^5.12.0, textextensions@^5.13.0: resolved "https://registry.npmjs.org/textextensions/-/textextensions-5.15.0.tgz" integrity sha512-MeqZRHLuaGamUXGuVn2ivtU3LA3mLCCIO5kUGoohTCoGmCBg/+8yPhWVX9WSl9telvVd8erftjFk9Fwb2dD6rw== -then-request@^6.0.0: - version "6.0.2" - resolved "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz" - integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA== - dependencies: - "@types/concat-stream" "^1.6.0" - "@types/form-data" "0.0.33" - "@types/node" "^8.0.0" - "@types/qs" "^6.2.31" - caseless "~0.12.0" - concat-stream "^1.6.0" - form-data "^2.2.0" - http-basic "^8.1.1" - http-response-object "^3.0.1" - promise "^8.0.0" - qs "^6.4.0" - thread-stream@^2.6.0: version "2.7.0" resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz" @@ -8889,10 +9447,10 @@ thread-stream@^2.6.0: dependencies: real-require "^0.2.0" -thread-stream@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" - integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== dependencies: real-require "^0.2.0" @@ -8927,6 +9485,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + tough-cookie@*: version "4.1.3" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz" @@ -8999,6 +9562,11 @@ ts-retry-promise@^0.8.0: resolved "https://registry.npmjs.org/ts-retry-promise/-/ts-retry-promise-0.8.0.tgz" integrity sha512-elI/GkojPANBikPaMWQnk4T/bOJ6tq/hqXyQRmhfC9PAD6MoHmXIXK7KilJrlpx47VAKCGcmBrTeK5dHk6YAYg== +ts-retry-promise@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/ts-retry-promise/-/ts-retry-promise-0.8.1.tgz#ba90eb07cb03677fcbf78fe38e94c9183927e154" + integrity sha512-+AHPUmAhr5bSRRK5CurE9kNH8gZlEHnCgusZ0zy2bjfatUBDX0h6vGQjiT0YrGwSDwRZmU+bapeX6mj55FOPvg== + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz" @@ -9014,7 +9582,7 @@ tslib@^1.11.1, tslib@^1.9.0: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.1, tslib@^2.5.0, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.1, tslib@^2.5.0: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -9047,6 +9615,11 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8: resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + type-fest@^0.18.0: version "0.18.1" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" @@ -9072,6 +9645,15 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + typed-array-buffer@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz" @@ -9118,11 +9700,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - typedoc-plugin-missing-exports@0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-0.23.0.tgz" @@ -9163,6 +9740,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" @@ -9225,6 +9807,11 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + untildify@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz" @@ -9252,10 +9839,9 @@ upper-case@^2.0.2: dependencies: tslib "^2.0.3" -uri-js@^4.2.2, uri-js@^4.4.1: -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -9273,6 +9859,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" @@ -9310,6 +9901,11 @@ validator@^13.6.0: resolved "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz" integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ== +vary@^1, vary@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + vinyl-file@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz" @@ -9542,12 +10138,22 @@ xml2js@^0.5.0: xml2js@^0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz" integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" +xmlbuilder2@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz" + integrity sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw== + dependencies: + "@oozcitak/dom" "1.15.10" + "@oozcitak/infra" "1.0.8" + "@oozcitak/util" "8.3.8" + js-yaml "3.14.1" + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" @@ -9578,7 +10184,7 @@ yaml@^1.10.0: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@20.2.4: +yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== @@ -9591,7 +10197,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2, yargs-parser@^20.2.3: +yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -9726,3 +10332,18 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + +zod-to-json-schema@^3.25.1: + version "3.25.1" + resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz" + integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== + +zod@^3.22.0, "zod@^3.25 || ^4.0": + version "3.25.76" + resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/package.json b/package.json index 331c04c2..5e92247f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@provartesting/provardx-cli", "description": "A plugin for the Salesforce CLI to orchestrate testing activities and report quality metrics to Provar Quality Hub", - "version": "1.5.0-beta.3", + "version": "1.5.0-beta.4", "license": "BSD-3-Clause", "plugins": [ "@provartesting/provardx-plugins-automation", @@ -35,7 +35,7 @@ "wireit": "^0.14.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18.0.0 <25.0.0" }, "files": [ "/lib", @@ -91,6 +91,9 @@ } } }, + "auth": { + "description": "Commands to manage Provar API key authentication." + }, "automation": { "description": "Commands to interact with Provar Automation.", "subtopics": { @@ -125,7 +128,7 @@ "postpack": "shx rm -f oclif.manifest.json", "prepack": "sf-prepack", "test": "wireit", - "test:nuts": "nyc mocha \"**/*generate.nut.ts\" \"**/*permission.nut.ts\" \"**/*load.nut.ts\" \"**/*validate.nut.ts\" \"**/*set.nut.ts\" \"**/*get.nut.ts\" --slow 4500 --timeout 600000 --reporter mochawesome", + "test:nuts": "nyc mocha \"**/*generate.nut.ts\" \"**/*permission.nut.ts\" \"**/*load.nut.ts\" \"**/*validate.nut.ts\" \"**/*set.nut.ts\" \"**/*get.nut.ts\" \"**/*key.nut.ts\" \"**/*status.nut.ts\" \"**/*clear.nut.ts\" --slow 4500 --timeout 600000 --reporter mochawesome", "test:only": "wireit", "test:dev": "nyc mocha \"test/**/*.test.ts\"", "test:watch": "mocha \"test/**/*.test.ts\" --watch --watch-files \"src/**/*.ts\" --watch-files \"test/**/*.ts\"", diff --git a/src/commands/provar/auth/clear.ts b/src/commands/provar/auth/clear.ts new file mode 100644 index 00000000..fa7e01aa --- /dev/null +++ b/src/commands/provar/auth/clear.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@provartesting/provardx-plugins-utils'; +import { clearCredentials, readStoredCredentials } from '../../../services/auth/credentials.js'; +import { qualityHubClient, getQualityHubBaseUrl } from '../../../services/qualityHub/client.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@provartesting/provardx-cli', 'sf.provar.auth.clear'); + +export default class SfProvarAuthClear extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public async run(): Promise { + const stored = readStoredCredentials(); + if (stored) { + const baseUrl = getQualityHubBaseUrl(); + try { + await qualityHubClient.revokeKey(stored.api_key, baseUrl); + } catch { + this.log(' Note: could not reach Quality Hub to revoke key server-side (offline?).'); + this.log(' The local credentials have been removed — the key may still be valid until it expires.'); + } + } + + clearCredentials(); + this.log('API key cleared.'); + this.log(' Next validation will use local rules only (structural checks, no quality scoring).'); + this.log(' To reconfigure: sf provar auth login'); + this.log(' For CI/CD: set the PROVAR_API_KEY environment variable.'); + } +} diff --git a/src/commands/provar/auth/login.ts b/src/commands/provar/auth/login.ts new file mode 100644 index 00000000..6a2ba769 --- /dev/null +++ b/src/commands/provar/auth/login.ts @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { Flags, SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@provartesting/provardx-plugins-utils'; +import { writeCredentials } from '../../../services/auth/credentials.js'; +import { loginFlowClient } from '../../../services/auth/loginFlow.js'; +import { + qualityHubClient, + getQualityHubBaseUrl, + QualityHubAuthError, + REQUEST_ACCESS_URL, +} from '../../../services/qualityHub/client.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@provartesting/provardx-cli', 'sf.provar.auth.login'); + +// Production values bundled from Phase 2 handoff (2026-04-11). +// Override via PROVAR_COGNITO_DOMAIN / PROVAR_COGNITO_CLIENT_ID for non-prod environments. +const DEFAULT_COGNITO_DOMAIN = 'us-east-1xpfwzwmop.auth.us-east-1.amazoncognito.com'; +const DEFAULT_CLIENT_ID = '29cs1a784r4cervmth8ugbkkri'; + +export default class SfProvarAuthLogin extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public static readonly flags = { + url: Flags.string({ + summary: messages.getMessage('flags.url.summary'), + required: false, + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(SfProvarAuthLogin); + + const cognitoDomain = process.env.PROVAR_COGNITO_DOMAIN ?? DEFAULT_COGNITO_DOMAIN; + const clientId = process.env.PROVAR_COGNITO_CLIENT_ID ?? DEFAULT_CLIENT_ID; + const baseUrl = flags.url ?? getQualityHubBaseUrl(); + + // ── Step 1: Generate PKCE pair, nonce, and state ─────────────────────── + const { verifier, challenge } = loginFlowClient.generatePkce(); + const nonce = loginFlowClient.generateNonce(); + const state = loginFlowClient.generateState(); + + // ── Step 2: Find an available registered callback port ────────────────── + const port = await loginFlowClient.findAvailablePort(); + const redirectUri = `http://localhost:${port}/callback`; + + // ── Step 3: Build the Cognito authorize URL ──────────────────────────── + const authorizeUrl = new URL(`https://${cognitoDomain}/oauth2/authorize`); + authorizeUrl.searchParams.set('response_type', 'code'); + authorizeUrl.searchParams.set('client_id', clientId); + authorizeUrl.searchParams.set('redirect_uri', redirectUri); + authorizeUrl.searchParams.set('code_challenge', challenge); + authorizeUrl.searchParams.set('code_challenge_method', 'S256'); + authorizeUrl.searchParams.set('scope', 'openid email aws.cognito.signin.user.admin'); + authorizeUrl.searchParams.set('state', state); + authorizeUrl.searchParams.set('nonce', nonce); + + // ── Step 4: Open browser and wait for callback ────────────────────────── + this.log('Opening browser for login...'); + this.log(` If the browser did not open, visit:\n ${authorizeUrl.toString()}`); + loginFlowClient.openBrowser(authorizeUrl.toString()); + + this.log('\nWaiting for authentication... (Ctrl-C to cancel)'); + const authCode = await loginFlowClient.listenForCallback(port, state); + + // ── Step 5: Exchange code for Cognito tokens ──────────────────────────── + const tokens = await loginFlowClient.exchangeCodeForTokens({ + code: authCode, + redirectUri, + clientId, + verifier, + tokenEndpoint: `https://${cognitoDomain}/oauth2/token`, + }); + + // ── Step 6: Exchange Cognito access token for pv_k_ key ───────────────── + // Cognito tokens are held in memory only — discarded after this call. + let keyData; + try { + keyData = await qualityHubClient.exchangeTokenForKey(tokens.access_token, baseUrl); + } catch (err) { + if (err instanceof QualityHubAuthError) { + this.error( + `No Provar MCP account found for this login.\nRequest access at: ${REQUEST_ACCESS_URL}`, + { exit: 1 } + ); + } + throw err; + } + + // ── Step 7: Persist the pv_k_ key ────────────────────────────────────── + writeCredentials(keyData.api_key, keyData.prefix, 'cognito', { + username: keyData.username, + tier: keyData.tier, + expires_at: keyData.expires_at, + }); + + this.log(`\nAuthenticated as ${keyData.username} (${keyData.tier} tier)`); + this.log(`API key stored (prefix: ${keyData.prefix}). Valid until ${keyData.expires_at}.`); + this.log(" Run 'sf provar auth status' to check at any time."); + } +} diff --git a/src/commands/provar/auth/rotate.ts b/src/commands/provar/auth/rotate.ts new file mode 100644 index 00000000..6945c485 --- /dev/null +++ b/src/commands/provar/auth/rotate.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@provartesting/provardx-plugins-utils'; +import { readStoredCredentials, writeCredentials } from '../../../services/auth/credentials.js'; +import { qualityHubClient, getQualityHubBaseUrl, QualityHubAuthError } from '../../../services/qualityHub/client.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@provartesting/provardx-cli', 'sf.provar.auth.rotate'); + +export default class SfProvarAuthRotate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public async run(): Promise { + const stored = readStoredCredentials(); + + if (!stored) { + this.error('No API key stored. Run `sf provar auth login` first.', { exit: 1 }); + } + + const baseUrl = getQualityHubBaseUrl(); + + try { + const keyData = await qualityHubClient.rotateKey(stored.api_key, baseUrl); + writeCredentials(keyData.api_key, keyData.prefix, 'cognito', { + username: keyData.username, + tier: keyData.tier, + expires_at: keyData.expires_at, + }); + this.log(`API key rotated (new prefix: ${keyData.prefix}). Valid until ${keyData.expires_at}.`); + this.log(" Run 'sf provar auth status' to verify."); + } catch (err) { + if (err instanceof QualityHubAuthError) { + this.error( + 'Current key is invalid or expired — rotation requires a valid key.\nRun `sf provar auth login` to authenticate via browser and get a fresh key.', + { exit: 1 } + ); + } + throw err; + } + } +} diff --git a/src/commands/provar/auth/status.ts b/src/commands/provar/auth/status.ts new file mode 100644 index 00000000..4a2340ab --- /dev/null +++ b/src/commands/provar/auth/status.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@provartesting/provardx-plugins-utils'; +import { readStoredCredentials } from '../../../services/auth/credentials.js'; +import { qualityHubClient, getQualityHubBaseUrl, REQUEST_ACCESS_URL } from '../../../services/qualityHub/client.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@provartesting/provardx-cli', 'sf.provar.auth.status'); + +export default class SfProvarAuthStatus extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + + public async run(): Promise { + const envKey = process.env.PROVAR_API_KEY?.trim(); + + if (envKey) { + if (!envKey.startsWith('pv_k_')) { + this.log('Warning: PROVAR_API_KEY is set but invalid (does not start with "pv_k_").'); + this.log(` Value: "${envKey.substring(0, 10)}..." — ignored for API calls.`); + this.log(' Fix: update PROVAR_API_KEY to a valid pv_k_ key from https://success.provartesting.com'); + this.log(''); + // Fall through to check stored credentials (matches resolveApiKey behaviour) + } else { + this.log('API key configured'); + this.log(' Source: environment variable (PROVAR_API_KEY)'); + this.log(` Prefix: ${envKey.substring(0, 12)}`); + this.log(''); + this.log(' Validation mode: Quality Hub API'); + return; + } + } + + const stored = readStoredCredentials(); + if (stored) { + // Best-effort live check — silent fallback to cached values if offline or unconfigured. + // Does not run for env var keys (CI environments may not have outbound access). + let liveValid: boolean | undefined; + try { + const live = await qualityHubClient.fetchKeyStatus(stored.api_key, getQualityHubBaseUrl()); + liveValid = live.valid; + if (live.username) stored.username = live.username; + if (live.tier) stored.tier = live.tier; + if (live.expires_at) stored.expires_at = live.expires_at; + } catch { + // Offline or API not yet configured — use locally cached values + } + + if (liveValid === false) { + this.log('API key expired or revoked.'); + this.log(' Source: ~/.provar/credentials.json'); + this.log(` Prefix: ${stored.prefix}`); + this.log(''); + this.log(' Run: sf provar auth login to refresh your key.'); + return; + } + + this.log('API key configured'); + this.log(' Source: ~/.provar/credentials.json'); + this.log(` Prefix: ${stored.prefix}`); + this.log(` Set at: ${stored.set_at}`); + if (stored.username) this.log(` Account: ${stored.username}`); + if (stored.tier) this.log(` Tier: ${stored.tier}`); + if (stored.expires_at) this.log(` Expires: ${stored.expires_at}`); + this.log(''); + this.log(' Validation mode: Quality Hub API'); + return; + } + + this.log('No API key configured.'); + this.log(''); + this.log('To enable Quality Hub validation (170 rules):'); + this.log(' Run: sf provar auth login'); + this.log(''); + this.log('For CI/CD: set the PROVAR_API_KEY environment variable.'); + this.log(''); + this.log(`No account? Request access at: ${REQUEST_ACCESS_URL}`); + this.log(''); + this.log('Validation mode: local only (structural rules, no quality scoring)'); + } +} diff --git a/src/mcp/tools/testCaseValidate.ts b/src/mcp/tools/testCaseValidate.ts index 20142752..89f02fbf 100644 --- a/src/mcp/tools/testCaseValidate.ts +++ b/src/mcp/tools/testCaseValidate.ts @@ -15,18 +15,42 @@ import type { ServerConfig } from '../server.js'; import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js'; import { makeError, makeRequestId, type ValidationIssue } from '../schemas/common.js'; import { log } from '../logging/logger.js'; +import { resolveApiKey } from '../../services/auth/credentials.js'; +import { + qualityHubClient, + getQualityHubBaseUrl, + QualityHubAuthError, + QualityHubRateLimitError, + REQUEST_ACCESS_URL, +} from '../../services/qualityHub/client.js'; import { runBestPractices } from './bestPracticesEngine.js'; +const ONBOARDING_MESSAGE = + 'Quality Hub validation unavailable — running local validation only (structural rules, no quality scoring).\n' + + 'To enable Quality Hub (170 rules): run sf provar auth login\n' + + 'For CI/CD: set the PROVAR_API_KEY environment variable.\n' + + `No account? Request access at: ${REQUEST_ACCESS_URL}`; + +const AUTH_WARNING = + 'Quality Hub API key is invalid or expired. Running local validation only.\n' + + `Run sf provar auth login to get a new key, or request access at: ${REQUEST_ACCESS_URL}`; + +const RATE_LIMIT_WARNING = 'Quality Hub API rate limit reached. Running local validation only. Try again shortly.'; + +const UNREACHABLE_WARNING = + 'Quality Hub API unreachable. Running local validation only (structural rules, no quality scoring).\n' + + 'For CI/CD: set PROVAR_QUALITY_HUB_URL and PROVAR_API_KEY environment variables.'; + export function registerTestCaseValidate(server: McpServer, config: ServerConfig): void { server.tool( 'provar.testcase.validate', - 'Validate a Provar XML test case for structural correctness and quality. Checks XML declaration, root element, required attributes (guid UUID v4, testItemId integer), presence, and applies best-practice rules (same ruleset and scoring as the Quality Hub batch validation API). Returns validity_score (schema compliance) and quality_score (best practices, 0–100).', + 'Validate a Provar XML test case for structural correctness and quality. Checks XML declaration, root element, required attributes (guid UUID v4, testItemId integer), presence, and applies best-practice rules. When a Provar API key is configured (via sf provar auth login or PROVAR_API_KEY env var), calls the Quality Hub API for full 170-rule scoring. Falls back to local validation if no key is set or the API is unavailable. Returns validity_score (schema compliance), quality_score (best practices, 0–100), and validation_source indicating which ruleset was applied.', { content: z.string().optional().describe('XML content to validate directly (alias: xml)'), xml: z.string().optional().describe('XML content to validate — API-compatible alias for content'), file_path: z.string().optional().describe('Path to .xml test case file'), }, - ({ content, xml, file_path }) => { + async ({ content, xml, file_path }) => { const requestId = makeRequestId(); log('info', 'provar.testcase.validate', { requestId, has_content: !!(content ?? xml), file_path }); @@ -49,8 +73,61 @@ export function registerTestCaseValidate(server: McpServer, config: ServerConfig return { isError: true, content: [{ type: 'text' as const, text: JSON.stringify(err) }] }; } - const validation = validateTestCase(source); - const result = { requestId, ...validation }; + const apiKey = resolveApiKey(); + + if (apiKey) { + const baseUrl = getQualityHubBaseUrl(); + try { + const apiResult = await qualityHubClient.validateTestCaseViaApi(source, apiKey, baseUrl); + const localMeta = validateTestCase(source); + const result = { + requestId, + ...apiResult, + step_count: localMeta.step_count, + error_count: apiResult.issues.filter((i) => i.severity === 'ERROR').length, + warning_count: apiResult.issues.filter((i) => i.severity === 'WARNING').length, + test_case_id: localMeta.test_case_id, + test_case_name: localMeta.test_case_name, + validation_source: 'quality_hub' as const, + }; + log('info', 'provar.testcase.validate: quality_hub', { requestId }); + return { + content: [{ type: 'text' as const, text: JSON.stringify(result) }], + structuredContent: result, + }; + } catch (apiErr: unknown) { + // API failed — determine the warning and fall through to local validation + let warning: string; + if (apiErr instanceof QualityHubAuthError) { + warning = AUTH_WARNING; + log('warn', 'provar.testcase.validate: auth error, falling back', { requestId }); + } else if (apiErr instanceof QualityHubRateLimitError) { + warning = RATE_LIMIT_WARNING; + log('warn', 'provar.testcase.validate: rate limited, falling back', { requestId }); + } else { + warning = UNREACHABLE_WARNING; + log('warn', 'provar.testcase.validate: api unreachable, falling back', { requestId }); + } + const localResult = { + requestId, + ...validateTestCase(source), + validation_source: 'local_fallback' as const, + validation_warning: warning, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(localResult) }], + structuredContent: localResult, + }; + } + } + + // No API key configured — run local validation with onboarding message + const result = { + requestId, + ...validateTestCase(source), + validation_source: 'local' as const, + validation_warning: ONBOARDING_MESSAGE, + }; return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], structuredContent: result, @@ -87,6 +164,10 @@ export interface TestCaseValidationResult { /** Violations from the Best Practices Engine (same rules as the Quality Hub API). */ best_practices_violations?: Array; best_practices_rules_evaluated?: number; + /** Which ruleset produced this result. Always present. */ + validation_source: 'quality_hub' | 'local' | 'local_fallback'; + /** Set when falling back to local — explains why and what to do. */ + validation_warning?: string; } /** Pure function — exported for unit testing */ @@ -96,7 +177,8 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas // TC_001: XML declaration if (!xmlContent.trimStart().startsWith('.', applies_to: 'document', suggestion: 'Add XML declaration as the first line.', @@ -115,7 +197,8 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas } catch (e: unknown) { const parseError = e as Error; issues.push({ - rule_id: 'TC_002', severity: 'ERROR', + rule_id: 'TC_002', + severity: 'ERROR', message: `XML parse error: ${parseError.message}`, applies_to: 'document', suggestion: 'Fix XML syntax errors.', @@ -126,7 +209,8 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas // TC_003: Root element if (!('testCase' in parsed)) { issues.push({ - rule_id: 'TC_003', severity: 'ERROR', + rule_id: 'TC_003', + severity: 'ERROR', message: 'Root element must be .', applies_to: 'document', suggestion: 'Ensure root element is .', @@ -145,7 +229,8 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas if (!tcId) { issues.push({ - rule_id: 'TC_010', severity: 'ERROR', + rule_id: 'TC_010', + severity: 'ERROR', message: 'testCase missing required id attribute.', applies_to: 'testCase', suggestion: 'Add id attribute to testCase element.', @@ -153,14 +238,16 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas } if (!tcGuid) { issues.push({ - rule_id: 'TC_011', severity: 'ERROR', + rule_id: 'TC_011', + severity: 'ERROR', message: 'testCase missing required guid attribute.', applies_to: 'testCase', suggestion: 'Add guid attribute (UUID v4) to testCase element.', }); } else if (!UUID_V4_RE.test(tcGuid)) { issues.push({ - rule_id: 'TC_012', severity: 'ERROR', + rule_id: 'TC_012', + severity: 'ERROR', message: `testCase guid "${tcGuid}" is not a valid UUID v4.`, applies_to: 'testCase', suggestion: 'Generate a proper UUID v4 for the guid attribute.', @@ -174,7 +261,8 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas // TC_020: element if (!('steps' in tc)) { issues.push({ - rule_id: 'TC_020', severity: 'ERROR', + rule_id: 'TC_020', + severity: 'ERROR', message: 'testCase missing element.', applies_to: 'testCase', suggestion: 'Wrap all step elements in a element.', @@ -188,7 +276,7 @@ export function validateTestCase(xmlContent: string, testName?: string): TestCas rawSteps !== null && typeof rawSteps === 'object' ? (rawSteps as Record) : {}; const rawApiCalls = steps['apiCall']; const apiCalls: Array> = rawApiCalls - ? (Array.isArray(rawApiCalls) ? rawApiCalls : [rawApiCalls]) as Array> + ? ((Array.isArray(rawApiCalls) ? rawApiCalls : [rawApiCalls]) as Array>) : []; for (const call of apiCalls) { @@ -207,14 +295,16 @@ function validateApiCall(call: Record, issues: ValidationIssue[ if (!callGuid) { issues.push({ - rule_id: 'TC_030', severity: 'ERROR', + rule_id: 'TC_030', + severity: 'ERROR', message: `apiCall${label} missing guid attribute.`, applies_to: 'apiCall', suggestion: 'Add a UUID v4 guid to each apiCall.', }); } else if (!UUID_V4_RE.test(callGuid)) { issues.push({ - rule_id: 'TC_031', severity: 'ERROR', + rule_id: 'TC_031', + severity: 'ERROR', message: `apiCall${label} guid "${callGuid}" is not a valid UUID v4.`, applies_to: 'apiCall', suggestion: 'Use proper UUID v4 format.', @@ -222,7 +312,8 @@ function validateApiCall(call: Record, issues: ValidationIssue[ } if (!apiId) { issues.push({ - rule_id: 'TC_032', severity: 'ERROR', + rule_id: 'TC_032', + severity: 'ERROR', message: 'apiCall missing apiId attribute.', applies_to: 'apiCall', suggestion: 'Add apiId attribute (e.g., UiConnect, ApexSoqlQuery).', @@ -230,7 +321,8 @@ function validateApiCall(call: Record, issues: ValidationIssue[ } if (!name) { issues.push({ - rule_id: 'TC_033', severity: 'WARNING', + rule_id: 'TC_033', + severity: 'WARNING', message: `apiCall${label} missing name attribute.`, applies_to: 'apiCall', suggestion: 'Add a descriptive name attribute.', @@ -238,14 +330,16 @@ function validateApiCall(call: Record, issues: ValidationIssue[ } if (!testItemId) { issues.push({ - rule_id: 'TC_034', severity: 'ERROR', + rule_id: 'TC_034', + severity: 'ERROR', message: `apiCall${label} missing testItemId attribute.`, applies_to: 'apiCall', suggestion: 'Add sequential testItemId (1, 2, 3...).', }); } else if (!/^\d+$/.test(testItemId)) { issues.push({ - rule_id: 'TC_035', severity: 'ERROR', + rule_id: 'TC_035', + severity: 'ERROR', message: `apiCall${label} testItemId "${testItemId}" must be a whole number.`, applies_to: 'apiCall', suggestion: 'Use sequential integers for testItemId.', @@ -282,5 +376,8 @@ function finalize( issues, best_practices_violations: bp.violations, best_practices_rules_evaluated: bp.rules_evaluated, + // validation_source is set by the caller (MCP tool handler or direct callers). + // Default to 'local' here so the pure validateTestCase() function is self-contained. + validation_source: 'local' as const, }; } diff --git a/src/services/auth/credentials.ts b/src/services/auth/credentials.ts new file mode 100644 index 00000000..32cb06fb --- /dev/null +++ b/src/services/auth/credentials.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +export interface StoredCredentials { + api_key: string; + prefix: string; + set_at: string; + source: 'manual' | 'cognito' | 'salesforce'; + // Phase 2 fields — optional so Phase 1 files remain valid after upgrade + username?: string; + tier?: string; + expires_at?: string; +} + +const KEY_PREFIX = 'pv_k_'; + +export function getCredentialsPath(): string { + return path.join(os.homedir(), '.provar', 'credentials.json'); +} + +export function readStoredCredentials(): StoredCredentials | null { + try { + const p = getCredentialsPath(); + if (!fs.existsSync(p)) return null; + const raw = fs.readFileSync(p, 'utf-8'); + return JSON.parse(raw) as StoredCredentials; + } catch { + return null; + } +} + +export function writeCredentials( + key: string, + prefix: string, + source: StoredCredentials['source'], + extra?: { username?: string; tier?: string; expires_at?: string } +): void { + if (!key.startsWith(KEY_PREFIX)) { + throw new Error(`Invalid API key format. Keys must start with "${KEY_PREFIX}".`); + } + const p = getCredentialsPath(); + fs.mkdirSync(path.dirname(p), { recursive: true }); + const data: StoredCredentials = { + api_key: key, + prefix, + set_at: new Date().toISOString(), + source, + ...(extra?.username ? { username: extra.username } : {}), + ...(extra?.tier ? { tier: extra.tier } : {}), + ...(extra?.expires_at ? { expires_at: extra.expires_at } : {}), + }; + // mode: 0o600 sets permissions atomically on file creation (POSIX). + // chmodSync handles re-runs on existing files. Both are no-ops on Windows. + fs.writeFileSync(p, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 }); + try { + fs.chmodSync(p, 0o600); + } catch { + /* Windows: no file permission model */ + } +} + +export function clearCredentials(): void { + const p = getCredentialsPath(); + try { + fs.rmSync(p); + } catch (err: unknown) { + const e = err as NodeJS.ErrnoException; + if (e.code !== 'ENOENT') throw err; + // file did not exist — nothing to clear, not an error + } +} + +export function resolveApiKey(): string | null { + const envKey = process.env.PROVAR_API_KEY?.trim(); + if (envKey?.startsWith(KEY_PREFIX)) return envKey; + const stored = readStoredCredentials(); + return stored?.api_key ?? null; +} diff --git a/src/services/auth/loginFlow.ts b/src/services/auth/loginFlow.ts new file mode 100644 index 00000000..e33462d8 --- /dev/null +++ b/src/services/auth/loginFlow.ts @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import crypto from 'node:crypto'; +import http from 'node:http'; +import https from 'node:https'; +import { execFile } from 'node:child_process'; +import { URL } from 'node:url'; + +// All three ports must be pre-registered in the Cognito App Client. +// Cognito requires redirect_uri to exactly match a registered callback URL — no wildcards. +export const CALLBACK_PORTS = [1717, 7890, 8080]; + +// ── PKCE ───────────────────────────────────────────────────────────────────── + +/** + * Generate a PKCE code_verifier / code_challenge pair (S256 method, as required by Cognito). + */ +export function generatePkce(): { verifier: string; challenge: string } { + const verifier = crypto.randomBytes(32).toString('base64url'); + const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); + return { verifier, challenge }; +} + +/** + * Generate a random nonce for OIDC replay-attack prevention. + * Required by the OpenID Connect spec when requesting an id_token. + */ +export function generateNonce(): string { + return crypto.randomBytes(16).toString('base64url'); +} + +/** + * Generate a random state value for CSRF protection. + * Required by Cognito Managed Login even though it is optional per the OAuth 2.0 spec. + */ +export function generateState(): string { + return crypto.randomBytes(16).toString('base64url'); +} + +// ── Port selection ──────────────────────────────────────────────────────────── + +/** + * Try each registered callback port in order; return the first that is free. + */ +export async function findAvailablePort(): Promise { + for (const port of CALLBACK_PORTS) { + // Sequential by design — we need the first free registered port, not all of them. + // eslint-disable-next-line no-await-in-loop + if (await isPortFree(port)) return port; + } + throw new Error( + 'Could not bind to any registered callback port (1717, 7890, 8080). ' + + 'Check that no other process is using these ports and try again.' + ); +} + +function isPortFree(port: number): Promise { + return new Promise((resolve) => { + const probe = http.createServer(); + probe.once('error', () => resolve(false)); + probe.listen(port, '127.0.0.1', () => { + probe.close(() => resolve(true)); + }); + }); +} + +// ── Browser open ────────────────────────────────────────────────────────────── + +/** + * Open a URL in the system browser. The URL is passed as an argument — not + * interpolated into a shell string — to avoid command injection. + */ +export function openBrowser(url: string): void { + switch (process.platform) { + case 'darwin': + execFile('open', [url]); + break; + case 'win32': + // Pass the URL via $args[0] so it is never interpolated into the -Command + // string — avoids quote-breaking and injection risk from special characters. + execFile('powershell.exe', ['-NoProfile', '-Command', 'Start-Process $args[0]', '-args', url]); + break; + default: + execFile('xdg-open', [url]); + } +} + +// ── Localhost callback server ───────────────────────────────────────────────── + +/** + * Spin up a temporary localhost HTTP server that accepts exactly one callback + * from Cognito's Hosted UI, extracts the auth code, and shuts down. + */ +export function listenForCallback(port: number, expectedState?: string): Promise { + return new Promise((resolve, reject) => { + const server = http.createServer((req, res) => { + const parsed = new URL(req.url ?? '/', `http://localhost:${port}`); + const code = parsed.searchParams.get('code'); + const error = parsed.searchParams.get('error'); + const description = parsed.searchParams.get('error_description'); + const callbackState = parsed.searchParams.get('state'); + + if (expectedState && callbackState !== expectedState) { + res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end( + '' + + '

Authentication failed

' + + '

Invalid state parameter — possible CSRF attack. Please try again.

' + + '' + ); + server.close(); + reject(new Error('OAuth callback state mismatch — possible CSRF. Try again.')); + return; + } + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end( + '' + + '

Authentication complete

' + + '

You can close this tab and return to the terminal.

' + + '' + ); + server.close(); + + if (code) { + resolve(code); + } else { + reject(new Error(description ?? error ?? 'No authorisation code received from Cognito')); + } + }); + server.listen(port, '127.0.0.1'); + server.on('error', (err: Error) => reject(err)); + }); +} + +// ── Cognito token exchange ──────────────────────────────────────────────────── + +export interface CognitoTokens { + access_token: string; + id_token: string; + token_type: string; + expires_in: number; +} + +/** + * Exchange a PKCE auth code for Cognito tokens via the standard token endpoint. + * Uses the Authorization Code + PKCE grant — no client secret required. + */ +export async function exchangeCodeForTokens(opts: { + code: string; + redirectUri: string; + clientId: string; + verifier: string; + tokenEndpoint: string; +}): Promise { + const body = new URLSearchParams({ + grant_type: 'authorization_code', + code: opts.code, + redirect_uri: opts.redirectUri, + client_id: opts.clientId, + code_verifier: opts.verifier, + }).toString(); + + const { status, responseBody } = await httpsPost(opts.tokenEndpoint, body, { + 'Content-Type': 'application/x-www-form-urlencoded', + }); + + if (status !== 200) { + throw new Error(`Cognito token exchange failed (${status}): ${responseBody}`); + } + + return JSON.parse(responseBody) as CognitoTokens; +} + +// ── Internal HTTPS helper ───────────────────────────────────────────────────── + +const REQUEST_TIMEOUT_MS = 30_000; + +function httpsPost( + url: string, + body: string, + headers: Record +): Promise<{ status: number; responseBody: string }> { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const req = https.request( + { + hostname: parsed.hostname, + port: parsed.port || undefined, + path: parsed.pathname + parsed.search, + method: 'POST', + headers: { + ...headers, + 'Content-Length': Buffer.byteLength(body).toString(), + }, + }, + (res) => { + let data = ''; + res.on('data', (chunk: Buffer) => { + data += chunk.toString('utf-8'); + }); + res.on('end', () => resolve({ status: res.statusCode ?? 0, responseBody: data })); + } + ); + req.setTimeout(REQUEST_TIMEOUT_MS, () => { + req.destroy(new Error(`Cognito token exchange timed out after ${REQUEST_TIMEOUT_MS / 1000}s`)); + }); + req.on('error', reject); + req.write(body); + req.end(); + }); +} + +// ── Indirection object (sinon-stubbable) ────────────────────────────────────── + +/** + * The login command calls loginFlowClient.X() so tests can replace properties with stubs. + */ +export const loginFlowClient = { + generatePkce, + generateNonce, + generateState, + findAvailablePort, + openBrowser, + listenForCallback: listenForCallback as (port: number, expectedState?: string) => Promise, + exchangeCodeForTokens, +}; diff --git a/src/services/qualityHub/client.ts b/src/services/qualityHub/client.ts new file mode 100644 index 00000000..365c7409 --- /dev/null +++ b/src/services/qualityHub/client.ts @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import https from 'node:https'; +import { URL as NodeURL } from 'node:url'; + +/** + * Quality Hub validation result — our internal normalised shape. + * Mapped from the raw API response by normaliseApiResponse(). + * Also returned by the local validator so both paths share one shape. + */ +export interface QualityHubValidationResult { + is_valid: boolean; + validity_score: number; + quality_score: number; + issues: Array<{ + rule_id: string; + severity: 'ERROR' | 'WARNING'; + message: string; + applies_to?: string; + suggestion?: string; + }>; +} + +// ── Raw API response types (confirmed with AWS team, 2026-04-10) ────────────── + +interface QualityHubApiViolation { + severity: 'critical' | 'major' | 'minor' | 'info'; + rule_id: string; + name: string; + description: string; + category: string; + message: string; + test_item_id?: string; + weight: number; + recommendation: string; + applies_to: string[]; +} + +interface QualityHubApiResponse { + valid: boolean; + errors: QualityHubApiViolation[]; + warnings: QualityHubApiViolation[]; + metadata: Record; + quality_metrics: { + quality_score: number; + max_score: number; + total_violations: number; + best_practices_grade: number; + }; + validation_mode: string; + validated_at: string; +} + +/** + * Map the raw API response to our internal validation result shape. + * Exported for unit testing; called by validateTestCaseViaApi once the stub is replaced. + * + * Mapping rules (from AWS memo 2026-04-10): + * raw.valid → is_valid + * raw.errors[].severity "critical" → issues[].severity "ERROR" + * raw.warnings[].severity * → issues[].severity "WARNING" + * raw.quality_metrics.quality_score → quality_score + * validity_score: 100 when valid, else max(0, 100 - errors.length * 20) + */ +export function normaliseApiResponse(raw: QualityHubApiResponse): QualityHubValidationResult { + const issues = [ + ...raw.errors.map((v) => ({ + rule_id: v.rule_id, + severity: 'ERROR' as const, + message: v.message, + applies_to: v.applies_to[0] as string | undefined, + suggestion: v.recommendation, + })), + ...raw.warnings.map((v) => ({ + rule_id: v.rule_id, + severity: 'WARNING' as const, + message: v.message, + applies_to: v.applies_to[0] as string | undefined, + suggestion: v.recommendation, + })), + ]; + + return { + is_valid: raw.valid, + validity_score: raw.valid ? 100 : Math.max(0, 100 - raw.errors.length * 20), + quality_score: raw.quality_metrics.quality_score, + issues, + }; +} + +/** + * Typed errors returned when the API call fails in a known way. + * The MCP tool maps these to appropriate fallback behaviour. + */ +export class QualityHubAuthError extends Error { + public readonly code = 'AUTH_ERROR'; +} + +export class QualityHubRateLimitError extends Error { + public readonly code = 'RATE_LIMITED'; +} + +/** + * POST /validate — submit XML to the Quality Hub validation API. + * + * Request: + * POST /validate + * x-provar-key: pv_k_... (user auth — no x-api-key infra gate on this endpoint) + * Content-Type: application/json + * { "test_case_xml": "" } + * + * On failure the MCP tool catches and falls back to local validation + * (validation_source: "local_fallback"). No user-visible crash. + */ +export async function validateTestCaseViaApi( + xml: string, + apiKey: string, + baseUrl: string +): Promise { + const body = JSON.stringify({ test_case_xml: xml }); + const { status, responseBody } = await httpsRequest( + `${baseUrl}/validate`, + 'POST', + { 'Content-Type': 'application/json', 'x-provar-key': apiKey }, + body + ); + + if (status === 401) { + throw new QualityHubAuthError( + 'API key is invalid, expired, or revoked. Run `sf provar auth login` to get a new key.' + ); + } + + if (status === 429) { + throw new QualityHubRateLimitError('Quality Hub validation rate limit exceeded. Try again later.'); + } + + if (!isOk(status)) { + throw new Error(`Quality Hub validate failed (${status}): ${responseBody}`); + } + + return normaliseApiResponse(JSON.parse(responseBody) as QualityHubApiResponse); +} + +/** + * Returns the Quality Hub base URL to use for API calls. + * Defaults to the dev environment URL; override via PROVAR_QUALITY_HUB_URL for production. + * Update DEFAULT_QUALITY_HUB_URL when the production URL is confirmed. + */ +const DEFAULT_QUALITY_HUB_URL = 'https://aqqlrlhga7.execute-api.us-east-1.amazonaws.com/dev'; + +/** + * Self-service access request page for users who do not yet have a Provar MCP account. + * Public HTML — no API key or Cognito token required. + * Update when staging/prod stages are deployed. + */ +export const REQUEST_ACCESS_URL = `${DEFAULT_QUALITY_HUB_URL}/auth/request-access`; + +export function getQualityHubBaseUrl(): string { + return process.env.PROVAR_QUALITY_HUB_URL ?? DEFAULT_QUALITY_HUB_URL; +} + +// ── Auth endpoint types ─────────────────────────────────────────────────────── + +export interface AuthExchangeResponse { + api_key: string; + prefix: string; + tier: string; + username: string; + expires_at: string; +} + +export interface KeyStatusResponse { + valid: boolean; + tier?: string; + username?: string; + expires_at?: string; +} + +// ── Auth endpoint functions ─────────────────────────────────────────────────── + +/** + * POST /auth/exchange — exchange a Cognito access token for a pv_k_ key. + * Called immediately after PKCE callback; Cognito tokens are discarded after this call. + */ +export async function exchangeTokenForKey(cognitoAccessToken: string, baseUrl: string): Promise { + const body = JSON.stringify({ access_token: cognitoAccessToken }); + const { status, responseBody } = await httpsRequest( + `${baseUrl}/auth/exchange`, + 'POST', + { 'Content-Type': 'application/json' }, + body + ); + if (status === 401) + throw new QualityHubAuthError( + `Account not found or no active subscription.\nRequest access at: ${REQUEST_ACCESS_URL}` + ); + if (!isOk(status)) throw new Error(`Auth exchange failed (${status}): ${responseBody}`); + return JSON.parse(responseBody) as AuthExchangeResponse; +} + +/** + * GET /auth/status — verify a stored pv_k_ key is still valid server-side. + * Best-effort: callers should catch and fall back to locally cached values on failure. + */ +export async function fetchKeyStatus(apiKey: string, baseUrl: string): Promise { + const { status, responseBody } = await httpsRequest(`${baseUrl}/auth/status`, 'GET', { + 'x-provar-key': apiKey, + }); + if (!isOk(status)) throw new Error(`Auth status check failed (${status})`); + return JSON.parse(responseBody) as KeyStatusResponse; +} + +/** + * POST /auth/revoke — invalidate a pv_k_ key on the server. + * Best-effort: callers should catch, log a note, then delete the local file regardless. + */ +export async function revokeKey(apiKey: string, baseUrl: string): Promise { + const { status, responseBody } = await httpsRequest(`${baseUrl}/auth/revoke`, 'POST', { + 'x-provar-key': apiKey, + 'Content-Length': '0', + }); + if (!isOk(status)) throw new Error(`Key revocation failed (${status}): ${responseBody}`); +} + +/** + * POST /auth/rotate — atomically replace the current pv_k_ key with a new one. + * The old key is invalidated immediately. Returns the same shape as /auth/exchange. + * On 401: key is invalid/expired — caller should direct user to sf provar auth login. + */ +export async function rotateKey(apiKey: string, baseUrl: string): Promise { + const { status, responseBody } = await httpsRequest(`${baseUrl}/auth/rotate`, 'POST', { + 'x-provar-key': apiKey, + 'Content-Length': '0', + }); + if (status === 401) + throw new QualityHubAuthError('API key is invalid or expired. Run `sf provar auth login` to get a new key.'); + if (!isOk(status)) throw new Error(`Key rotation failed (${status}): ${responseBody}`); + return JSON.parse(responseBody) as AuthExchangeResponse; +} + +// ── Internal HTTPS helper ───────────────────────────────────────────────────── + +function isOk(status: number): boolean { + return status >= 200 && status < 300; +} + +const REQUEST_TIMEOUT_MS = 30_000; + +function httpsRequest( + url: string, + method: string, + headers: Record, + body?: string +): Promise<{ status: number; responseBody: string }> { + return new Promise((resolve, reject) => { + const parsed = new NodeURL(url); + const opts = { + hostname: parsed.hostname, + port: parsed.port || undefined, + path: parsed.pathname + parsed.search, + method, + headers: { + ...headers, + ...(body ? { 'Content-Length': Buffer.byteLength(body).toString() } : {}), + }, + }; + const req = https.request(opts, (res) => { + let data = ''; + res.on('data', (chunk: Buffer) => { + data += chunk.toString('utf-8'); + }); + res.on('end', () => resolve({ status: res.statusCode ?? 0, responseBody: data })); + }); + req.setTimeout(REQUEST_TIMEOUT_MS, () => { + req.destroy(new Error(`Quality Hub API request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`)); + }); + req.on('error', reject); + if (body) req.write(body); + req.end(); + }); +} + +// ── Indirection object used by MCP tools and testable via sinon ─────────────── + +/** + * MCP tools and auth commands call qualityHubClient.X() so tests can replace + * properties with stubs without ESM re-export issues. + */ +export const qualityHubClient = { + validateTestCaseViaApi, + exchangeTokenForKey, + fetchKeyStatus, + revokeKey, + rotateKey, +}; diff --git a/test/commands/provar/auth/clear.nut.ts b/test/commands/provar/auth/clear.nut.ts new file mode 100644 index 00000000..419a5385 --- /dev/null +++ b/test/commands/provar/auth/clear.nut.ts @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { execCmd } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; +import { SfProvarCommandResult } from '@provartesting/provardx-plugins-utils'; + +const CREDS_PATH = path.join(os.homedir(), '.provar', 'credentials.json'); + +function seedCredentials(): void { + fs.mkdirSync(path.dirname(CREDS_PATH), { recursive: true }); + fs.writeFileSync( + CREDS_PATH, + JSON.stringify({ api_key: 'pv_k_cleartest123456', prefix: 'pv_k_clearte', set_at: new Date().toISOString(), source: 'manual' }), + 'utf-8' + ); +} + +describe('sf provar auth clear NUTs', () => { + let credentialsBackup: string | null = null; + + before(() => { + if (fs.existsSync(CREDS_PATH)) { + credentialsBackup = fs.readFileSync(CREDS_PATH, 'utf-8'); + fs.rmSync(CREDS_PATH); + } + }); + + after(() => { + if (credentialsBackup !== null) { + fs.mkdirSync(path.dirname(CREDS_PATH), { recursive: true }); + fs.writeFileSync(CREDS_PATH, credentialsBackup, 'utf-8'); + } else if (fs.existsSync(CREDS_PATH)) { + fs.rmSync(CREDS_PATH); + } + }); + + it('does not throw and prints confirmation when no credentials file exists', () => { + const output = execCmd('provar auth clear').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('API key cleared'); + }); + + it('removes the credentials file and reports success', () => { + seedCredentials(); + expect(fs.existsSync(CREDS_PATH)).to.equal(true); + + const output = execCmd('provar auth clear').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('API key cleared'); + expect(fs.existsSync(CREDS_PATH)).to.equal(false); + }); + + it('is idempotent — clearing twice does not throw', () => { + seedCredentials(); + execCmd('provar auth clear'); + const output = execCmd('provar auth clear').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('API key cleared'); + }); + + it('status shows no key after clear', () => { + seedCredentials(); + execCmd('provar auth clear'); + const output = execCmd('provar auth status').shellOutput; + expect(output.stdout).to.include('No API key configured'); + }); +}); diff --git a/test/commands/provar/auth/status.nut.ts b/test/commands/provar/auth/status.nut.ts new file mode 100644 index 00000000..eadeb285 --- /dev/null +++ b/test/commands/provar/auth/status.nut.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { execCmd } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; +import { SfProvarCommandResult } from '@provartesting/provardx-plugins-utils'; + +const CREDS_PATH = path.join(os.homedir(), '.provar', 'credentials.json'); + +describe('sf provar auth status NUTs', () => { + let credentialsBackup: string | null = null; + let envBackup: string | undefined; + + before(() => { + envBackup = process.env.PROVAR_API_KEY; + delete process.env.PROVAR_API_KEY; + if (fs.existsSync(CREDS_PATH)) { + credentialsBackup = fs.readFileSync(CREDS_PATH, 'utf-8'); + fs.rmSync(CREDS_PATH); + } + }); + + after(() => { + if (envBackup !== undefined) { + process.env.PROVAR_API_KEY = envBackup; + } else { + delete process.env.PROVAR_API_KEY; + } + if (credentialsBackup !== null) { + fs.mkdirSync(path.dirname(CREDS_PATH), { recursive: true }); + fs.writeFileSync(CREDS_PATH, credentialsBackup, 'utf-8'); + } else if (fs.existsSync(CREDS_PATH)) { + fs.rmSync(CREDS_PATH); + } + }); + + afterEach(() => { + delete process.env.PROVAR_API_KEY; + if (fs.existsSync(CREDS_PATH)) { + fs.rmSync(CREDS_PATH); + } + }); + + it('reports no key configured when neither env var nor file is set', () => { + const output = execCmd('provar auth status').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('No API key configured'); + expect(output.stdout).to.include('local only'); + }); + + it('reports key source as credentials file when credentials file exists', () => { + fs.mkdirSync(path.dirname(CREDS_PATH), { recursive: true }); + fs.writeFileSync( + CREDS_PATH, + JSON.stringify({ api_key: 'pv_k_statustest12345', prefix: 'pv_k_statust', set_at: new Date().toISOString(), source: 'manual' }), + 'utf-8' + ); + const output = execCmd('provar auth status').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('API key configured'); + expect(output.stdout).to.include('credentials.json'); + expect(output.stdout).to.include('Prefix:'); + expect(output.stdout).to.include('Quality Hub API'); + }); + + it('reports key source as environment variable when PROVAR_API_KEY is set', () => { + process.env.PROVAR_API_KEY = 'pv_k_envstatustest12'; + const output = execCmd('provar auth status').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('API key configured'); + expect(output.stdout).to.include('PROVAR_API_KEY'); + expect(output.stdout).to.include('Quality Hub API'); + }); + + it('reports misconfiguration when PROVAR_API_KEY lacks pv_k_ prefix', () => { + process.env.PROVAR_API_KEY = 'sk-wrong-prefix-value'; + const output = execCmd('provar auth status').shellOutput; + expect(output.stderr).to.equal(''); + expect(output.stdout).to.include('invalid'); + expect(output.stdout).to.include('pv_k_'); + }); +}); diff --git a/test/unit/commands/provar/auth/clear.test.ts b/test/unit/commands/provar/auth/clear.test.ts new file mode 100644 index 00000000..c29a413a --- /dev/null +++ b/test/unit/commands/provar/auth/clear.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; +import { + writeCredentials, + clearCredentials, + getCredentialsPath, +} from '../../../../../src/services/auth/credentials.js'; + +let origHomedir: () => string; +let tempDir: string; + +function useTemp(): void { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'provar-clear-test-')); + origHomedir = os.homedir; + (os as unknown as { homedir: () => string }).homedir = (): string => tempDir; +} + +function restoreHome(): void { + (os as unknown as { homedir: () => string }).homedir = origHomedir; + fs.rmSync(tempDir, { recursive: true, force: true }); +} + +describe('auth clear logic', () => { + beforeEach(useTemp); + afterEach(restoreHome); + + it('deletes the credentials file when it exists', () => { + writeCredentials('pv_k_abc123456789xyz', 'pv_k_abc123', 'manual'); + assert.ok(fs.existsSync(getCredentialsPath()), 'File should exist before clear'); + clearCredentials(); + assert.ok(!fs.existsSync(getCredentialsPath()), 'File should be deleted after clear'); + }); + + it('does not throw when no credentials file exists', () => { + assert.doesNotThrow(() => clearCredentials()); + }); +}); diff --git a/test/unit/commands/provar/auth/login.test.ts b/test/unit/commands/provar/auth/login.test.ts new file mode 100644 index 00000000..37992807 --- /dev/null +++ b/test/unit/commands/provar/auth/login.test.ts @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import http from 'node:http'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; +import sinon from 'sinon'; +import { + generatePkce, + loginFlowClient, + type CognitoTokens, + CALLBACK_PORTS, +} from '../../../../../src/services/auth/loginFlow.js'; +import { qualityHubClient, type AuthExchangeResponse } from '../../../../../src/services/qualityHub/client.js'; +import { + writeCredentials, + readStoredCredentials, + getCredentialsPath, +} from '../../../../../src/services/auth/credentials.js'; + +// ── Fixtures ────────────────────────────────────────────────────────────────── + +const MOCK_TOKENS: CognitoTokens = { + access_token: 'cognito-access-token-test', + id_token: 'cognito-id-token-test', + token_type: 'Bearer', + expires_in: 3600, +}; + +const MOCK_KEY: AuthExchangeResponse = { + api_key: 'pv_k_logintest1234567890', + prefix: 'pv_k_logintest', + tier: 'enterprise', + username: 'test@provar.com', + expires_at: '2026-07-11T00:00:00.000Z', +}; + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +let origHomedir: () => string; +let tempDir: string; + +function useTemp(): void { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'provar-login-test-')); + origHomedir = os.homedir; + (os as unknown as { homedir: () => string }).homedir = (): string => tempDir; +} + +function restoreHome(): void { + (os as unknown as { homedir: () => string }).homedir = origHomedir; + fs.rmSync(tempDir, { recursive: true, force: true }); +} + +// ── generatePkce ────────────────────────────────────────────────────────────── + +describe('generatePkce', () => { + it('returns distinct verifier and challenge strings', () => { + const { verifier, challenge } = generatePkce(); + assert.ok(verifier.length >= 43, 'verifier should be ≥43 chars (base64url of 32 bytes)'); + assert.notEqual(verifier, challenge, 'verifier and challenge must differ'); + assert.ok(/^[A-Za-z0-9_-]+$/.test(verifier), 'verifier is base64url'); + assert.ok(/^[A-Za-z0-9_-]+$/.test(challenge), 'challenge is base64url'); + }); + + it('generates a unique pair on each call', () => { + const a = generatePkce(); + const b = generatePkce(); + assert.notEqual(a.verifier, b.verifier); + assert.notEqual(a.challenge, b.challenge); + }); + + it('challenge is the S256 (SHA-256 base64url) of verifier', async () => { + const crypto = await import('node:crypto'); + const { verifier, challenge } = generatePkce(); + const expected = crypto.createHash('sha256').update(verifier).digest('base64url'); + assert.equal(challenge, expected); + }); +}); + +// ── listenForCallback ───────────────────────────────────────────────────────── + +describe('listenForCallback', () => { + it('resolves with the auth code when Cognito callback arrives', async () => { + const port = CALLBACK_PORTS[0]; + const callbackPromise = loginFlowClient.listenForCallback(port); + + // Simulate Cognito redirect + await new Promise((res) => setTimeout(res, 20)); + const req = http.request({ hostname: '127.0.0.1', port, path: '/callback?code=test-code-123', method: 'GET' }); + req.end(); + + const code = await callbackPromise; + assert.equal(code, 'test-code-123'); + }); + + it('rejects when Cognito returns an error parameter', async () => { + const port = CALLBACK_PORTS[1]; + const callbackPromise = loginFlowClient.listenForCallback(port); + + await new Promise((res) => setTimeout(res, 20)); + const req = http.request({ + hostname: '127.0.0.1', + port, + path: '/callback?error=access_denied&error_description=User+cancelled', + method: 'GET', + }); + req.end(); + + await assert.rejects(callbackPromise, /User cancelled/); + }); +}); + +// ── Login flow integration (service-layer, no OCLIF invocation) ─────────────── +// The login command is a thin orchestrator. We test that the credential write +// happens correctly after a successful exchange, and that nothing is written on +// failure. NUT tests cover end-to-end command invocation. + +describe('login flow: credential writing', () => { + beforeEach(useTemp); + afterEach(restoreHome); + + it('writeCredentials with source "cognito" stores the pv_k_ key', () => { + writeCredentials(MOCK_KEY.api_key, MOCK_KEY.prefix, 'cognito'); + const stored = readStoredCredentials(); + assert.ok(stored, 'credentials should be written'); + assert.equal(stored.api_key, MOCK_KEY.api_key); + assert.equal(stored.prefix, MOCK_KEY.prefix); + assert.equal(stored.source, 'cognito'); + }); + + it('Cognito tokens do NOT appear in the credentials file', () => { + writeCredentials(MOCK_KEY.api_key, MOCK_KEY.prefix, 'cognito'); + const raw = fs.readFileSync(getCredentialsPath(), 'utf-8'); + assert.ok(!raw.includes(MOCK_TOKENS.access_token), 'access_token must not be on disk'); + assert.ok(!raw.includes(MOCK_TOKENS.id_token), 'id_token must not be on disk'); + }); +}); + +// ── qualityHubClient.exchangeTokenForKey (via sinon stub) ───────────────────── + +describe('qualityHubClient.exchangeTokenForKey stub', () => { + let stub: sinon.SinonStub; + + beforeEach(useTemp); + afterEach(() => { + stub.restore(); + restoreHome(); + }); + + it('resolves with AuthExchangeResponse and credentials are written', async () => { + stub = sinon.stub(qualityHubClient, 'exchangeTokenForKey').resolves(MOCK_KEY); + + const result = await qualityHubClient.exchangeTokenForKey('any-access-token', 'https://example.com'); + writeCredentials(result.api_key, result.prefix, 'cognito'); + + assert.ok(stub.calledOnce); + assert.equal(stub.firstCall.args[0], 'any-access-token'); + + const stored = readStoredCredentials(); + assert.ok(stored); + assert.equal(stored.api_key, MOCK_KEY.api_key); + assert.equal(stored.source, 'cognito'); + }); + + it('propagates auth error — credentials must not be written on failure', async () => { + stub = sinon + .stub(qualityHubClient, 'exchangeTokenForKey') + .rejects(new Error('Account not found or no active subscription')); + + await assert.rejects( + () => qualityHubClient.exchangeTokenForKey('bad-token', 'https://example.com'), + /subscription/ + ); + assert.equal(readStoredCredentials(), null, 'no credentials should be written on error'); + }); +}); diff --git a/test/unit/commands/provar/auth/rotate.test.ts b/test/unit/commands/provar/auth/rotate.test.ts new file mode 100644 index 00000000..32843035 --- /dev/null +++ b/test/unit/commands/provar/auth/rotate.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; +import sinon from 'sinon'; +import { + qualityHubClient, + type AuthExchangeResponse, + QualityHubAuthError, +} from '../../../../../src/services/qualityHub/client.js'; +import { + writeCredentials, + readStoredCredentials, + getCredentialsPath, +} from '../../../../../src/services/auth/credentials.js'; + +// ── Fixtures ────────────────────────────────────────────────────────────────── + +const STORED_KEY = 'pv_k_currentkey123456'; +const STORED_PREFIX = 'pv_k_currentk'; + +const ROTATED_KEY: AuthExchangeResponse = { + api_key: 'pv_k_newrotatedkey12345', + prefix: 'pv_k_newrotat', + tier: 'standard', + username: 'test@provar.com', + expires_at: '2026-07-13T10:00:00+00:00', +}; + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe('sf provar auth rotate — rotateKey client function', () => { + let credentialsBackup: string | null = null; + const credsPath = getCredentialsPath(); + + beforeEach(() => { + if (fs.existsSync(credsPath)) { + credentialsBackup = fs.readFileSync(credsPath, 'utf-8'); + } + writeCredentials(STORED_KEY, STORED_PREFIX, 'cognito'); + }); + + afterEach(() => { + sinon.restore(); + if (credentialsBackup !== null) { + fs.mkdirSync(path.dirname(credsPath), { recursive: true }); + fs.writeFileSync(credsPath, credentialsBackup, 'utf-8'); + credentialsBackup = null; + } else if (fs.existsSync(credsPath)) { + fs.rmSync(credsPath); + } + }); + + it('rotateKey resolves with new AuthExchangeResponse on success', async () => { + sinon.stub(qualityHubClient, 'rotateKey').resolves(ROTATED_KEY); + const result = await qualityHubClient.rotateKey(STORED_KEY, 'https://example.com'); + assert.equal(result.api_key, ROTATED_KEY.api_key); + assert.equal(result.prefix, ROTATED_KEY.prefix); + assert.equal(result.expires_at, ROTATED_KEY.expires_at); + }); + + it('writing the rotated key replaces the stored credentials', async () => { + sinon.stub(qualityHubClient, 'rotateKey').resolves(ROTATED_KEY); + const result = await qualityHubClient.rotateKey(STORED_KEY, 'https://example.com'); + writeCredentials(result.api_key, result.prefix, 'cognito'); + const stored = readStoredCredentials(); + assert.equal(stored?.api_key, ROTATED_KEY.api_key); + assert.notEqual(stored?.api_key, STORED_KEY); + }); + + it('rotateKey rejects with QualityHubAuthError on 401', async () => { + sinon.stub(qualityHubClient, 'rotateKey').rejects(new QualityHubAuthError('API key is invalid or expired.')); + await assert.rejects(() => qualityHubClient.rotateKey(STORED_KEY, 'https://example.com'), QualityHubAuthError); + }); + + it('rotateKey rejects with generic Error on 500', async () => { + sinon.stub(qualityHubClient, 'rotateKey').rejects(new Error('Key rotation failed (500): Internal server error')); + await assert.rejects(() => qualityHubClient.rotateKey(STORED_KEY, 'https://example.com'), Error); + }); + + it('readStoredCredentials returns null when no credentials file exists', () => { + fs.rmSync(credsPath, { force: true }); + assert.equal(readStoredCredentials(), null); + }); +}); diff --git a/test/unit/commands/provar/auth/status.test.ts b/test/unit/commands/provar/auth/status.test.ts new file mode 100644 index 00000000..b3791286 --- /dev/null +++ b/test/unit/commands/provar/auth/status.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; +import { + readStoredCredentials, + writeCredentials, + resolveApiKey, +} from '../../../../../src/services/auth/credentials.js'; + +// The status command reads credentials and reports source. We test the +// source-detection logic directly — the same logic the command uses. + +let origHomedir: () => string; +let tempDir: string; +let savedEnv: string | undefined; + +function useTemp(): void { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'provar-status-test-')); + origHomedir = os.homedir; + (os as unknown as { homedir: () => string }).homedir = (): string => tempDir; +} + +function restoreHome(): void { + (os as unknown as { homedir: () => string }).homedir = origHomedir; + fs.rmSync(tempDir, { recursive: true, force: true }); +} + +describe('auth status logic', () => { + beforeEach(() => { + savedEnv = process.env.PROVAR_API_KEY; + delete process.env.PROVAR_API_KEY; + useTemp(); + }); + + afterEach(() => { + if (savedEnv === undefined) { + delete process.env.PROVAR_API_KEY; + } else { + process.env.PROVAR_API_KEY = savedEnv; + } + restoreHome(); + }); + + it('resolveApiKey returns null when nothing is configured', () => { + assert.equal(resolveApiKey(), null); + assert.equal(readStoredCredentials(), null); + }); + + it('resolveApiKey returns env var and readStoredCredentials returns null', () => { + process.env.PROVAR_API_KEY = 'pv_k_fromenv123456'; + assert.equal(resolveApiKey(), 'pv_k_fromenv123456'); + assert.equal(readStoredCredentials(), null); + }); + + it('resolveApiKey returns stored key and readStoredCredentials returns the object', () => { + writeCredentials('pv_k_fromfile12345', 'pv_k_fromfil', 'manual'); + assert.equal(resolveApiKey(), 'pv_k_fromfile12345'); + assert.ok(readStoredCredentials(), 'stored credentials should be readable'); + }); + + it('status source detection: env var present → source is env (not file)', () => { + writeCredentials('pv_k_fromfile12345', 'pv_k_fromfil', 'manual'); + process.env.PROVAR_API_KEY = 'pv_k_fromenv123456'; + const envKey = process.env.PROVAR_API_KEY?.trim(); + // status command checks env first; if present, source = env var + assert.ok(envKey, 'env key should be truthy'); + assert.equal(resolveApiKey(), 'pv_k_fromenv123456'); + }); + + it('resolveApiKey ignores env var without pv_k_ prefix, falls through to stored file', () => { + writeCredentials('pv_k_fromfile12345', 'pv_k_fromfil', 'manual'); + process.env.PROVAR_API_KEY = 'sk-wrong-prefix-key'; + assert.equal(resolveApiKey(), 'pv_k_fromfile12345'); + }); +}); diff --git a/test/unit/mcp/testCaseValidate.test.ts b/test/unit/mcp/testCaseValidate.test.ts index 7862c120..7096077a 100644 --- a/test/unit/mcp/testCaseValidate.test.ts +++ b/test/unit/mcp/testCaseValidate.test.ts @@ -1,6 +1,17 @@ +/* eslint-disable camelcase */ import { strict as assert } from 'node:assert'; -import { describe, it } from 'mocha'; -import { validateTestCase } from '../../../src/mcp/tools/testCaseValidate.js'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; +import sinon from 'sinon'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { validateTestCase, registerTestCaseValidate } from '../../../src/mcp/tools/testCaseValidate.js'; +import { + qualityHubClient, + QualityHubAuthError, + QualityHubRateLimitError, +} from '../../../src/services/qualityHub/client.js'; // Valid UUID v4 values (format: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx) const GUID_TC = '550e8400-e29b-41d4-a716-446655440000'; @@ -29,21 +40,28 @@ describe('validateTestCase', () => { describe('document-level rules', () => { it('TC_001: flags missing XML declaration', () => { - const r = validateTestCase( - `` + const r = validateTestCase(``); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_001'), + 'Expected TC_001' ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_001'), 'Expected TC_001'); assert.equal(r.is_valid, false); }); it('TC_002: flags malformed XML', () => { const r = validateTestCase(' i.rule_id === 'TC_002'), 'Expected TC_002'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_002'), + 'Expected TC_002' + ); }); it('TC_003: flags wrong root element', () => { const r = validateTestCase(''); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_003'), 'Expected TC_003'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_003'), + 'Expected TC_003' + ); }); }); @@ -52,21 +70,30 @@ describe('validateTestCase', () => { const r = validateTestCase( `` ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_010'), 'Expected TC_010'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_010'), + 'Expected TC_010' + ); }); it('TC_011: flags missing guid', () => { const r = validateTestCase( '' ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_011'), 'Expected TC_011'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_011'), + 'Expected TC_011' + ); }); it('TC_012: flags non-UUID-v4 guid', () => { const r = validateTestCase( '' ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_012'), 'Expected TC_012'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_012'), + 'Expected TC_012' + ); }); }); @@ -80,7 +107,10 @@ describe('validateTestCase', () => {
` ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_031'), 'Expected TC_031'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_031'), + 'Expected TC_031' + ); }); it('TC_034 + TC_035: flags non-integer testItemId', () => { @@ -92,7 +122,10 @@ describe('validateTestCase', () => {
` ); - assert.ok(r.issues.some((i) => i.rule_id === 'TC_035'), 'Expected TC_035'); + assert.ok( + r.issues.some((i) => i.rule_id === 'TC_035'), + 'Expected TC_035' + ); }); }); @@ -103,6 +136,13 @@ describe('validateTestCase', () => { }); }); + describe('validation_source field', () => { + it('returns validation_source: "local" from the pure function', () => { + const r = validateTestCase(VALID_TC); + assert.equal(r.validation_source, 'local'); + }); + }); + describe('self-closing element handling', () => { // fast-xml-parser yields '' for a self-closing element with no attributes. // These must not throw "Cannot use 'in' operator to search for '...' in ''" @@ -125,3 +165,105 @@ describe('validateTestCase', () => { }); }); }); + +// ── Handler-level tests (registerTestCaseValidate) ──────────────────────────── + +describe('registerTestCaseValidate handler', () => { + // Minimal stub server that captures the registered handler for direct invocation. + // Cast to McpServer via unknown — safe because registerTestCaseValidate only calls server.tool(). + class CapturingServer { + public capturedHandler: ((args: Record) => Promise) | null = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public tool(...args: any[]): void { + this.capturedHandler = args[args.length - 1] as (args: Record) => Promise; + } + } + + let capServer: CapturingServer; + let savedApiKey: string | undefined; + let origHomedir: () => string; + let tempDir: string; + let apiStub: sinon.SinonStub | null = null; + + beforeEach(() => { + // Redirect home so readStoredCredentials() finds no file + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'provar-handler-test-')); + origHomedir = os.homedir; + (os as unknown as { homedir: () => string }).homedir = (): string => tempDir; + + savedApiKey = process.env.PROVAR_API_KEY; + delete process.env.PROVAR_API_KEY; + + capServer = new CapturingServer(); + registerTestCaseValidate(capServer as unknown as McpServer, { allowedPaths: [] }); + }); + + afterEach(() => { + (os as unknown as { homedir: () => string }).homedir = origHomedir; + fs.rmSync(tempDir, { recursive: true, force: true }); + if (savedApiKey !== undefined) { + process.env.PROVAR_API_KEY = savedApiKey; + } else { + delete process.env.PROVAR_API_KEY; + } + apiStub?.restore(); + apiStub = null; + }); + + it('no key → validation_source "local" with onboarding warning', async () => { + const res = (await capServer.capturedHandler!({ content: VALID_TC })) as { content: Array<{ text: string }> }; + const result = JSON.parse(res.content[0].text) as Record; + assert.equal(result['validation_source'], 'local'); + const warning = String(result['validation_warning']); + assert.ok(warning, 'Expected validation_warning to be set'); + assert.ok(warning.includes('Quality Hub'), 'Warning must mention Quality Hub'); + }); + + it('key + API success → validation_source "quality_hub" with local metadata', async () => { + process.env.PROVAR_API_KEY = 'pv_k_testkey12345'; + apiStub = sinon.stub(qualityHubClient, 'validateTestCaseViaApi').resolves({ + is_valid: true, + validity_score: 100, + quality_score: 90, + issues: [], + }); + const res = (await capServer.capturedHandler!({ content: VALID_TC })) as { content: Array<{ text: string }> }; + const result = JSON.parse(res.content[0].text) as Record; + assert.equal(result['validation_source'], 'quality_hub'); + assert.equal(result['is_valid'], true); + assert.equal(result['quality_score'], 90); + // Metadata extracted from XML locally and merged into the API response + assert.equal(result['test_case_id'], 'test-001'); + assert.equal(result['test_case_name'], 'My Test'); + assert.equal(result['step_count'], 2); + }); + + it('key + network error → validation_source "local_fallback" with unreachable warning', async () => { + process.env.PROVAR_API_KEY = 'pv_k_testkey12345'; + apiStub = sinon.stub(qualityHubClient, 'validateTestCaseViaApi').rejects(new Error('connect ECONNREFUSED')); + const res = (await capServer.capturedHandler!({ content: VALID_TC })) as { content: Array<{ text: string }> }; + const result = JSON.parse(res.content[0].text) as Record; + assert.equal(result['validation_source'], 'local_fallback'); + assert.ok(String(result['validation_warning']).toLowerCase().includes('unreachable')); + }); + + it('key + QualityHubAuthError → validation_source "local_fallback" with auth warning', async () => { + process.env.PROVAR_API_KEY = 'pv_k_testkey12345'; + apiStub = sinon.stub(qualityHubClient, 'validateTestCaseViaApi').rejects(new QualityHubAuthError('Unauthorized')); + const res = (await capServer.capturedHandler!({ content: VALID_TC })) as { content: Array<{ text: string }> }; + const result = JSON.parse(res.content[0].text) as Record; + assert.equal(result['validation_source'], 'local_fallback'); + assert.ok(String(result['validation_warning']).includes('invalid or expired')); + }); + + it('key + QualityHubRateLimitError → validation_source "local_fallback" with rate limit warning', async () => { + process.env.PROVAR_API_KEY = 'pv_k_testkey12345'; + apiStub = sinon + .stub(qualityHubClient, 'validateTestCaseViaApi') + .rejects(new QualityHubRateLimitError('Too Many Requests')); + const res = (await capServer.capturedHandler!({ content: VALID_TC })) as { content: Array<{ text: string }> }; + const result = JSON.parse(res.content[0].text) as Record; + assert.equal(result['validation_source'], 'local_fallback'); + assert.ok(String(result['validation_warning']).toLowerCase().includes('rate limit')); + }); +}); diff --git a/test/unit/services/auth/credentials.test.ts b/test/unit/services/auth/credentials.test.ts new file mode 100644 index 00000000..d8f88fec --- /dev/null +++ b/test/unit/services/auth/credentials.test.ts @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { strict as assert } from 'node:assert'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, it, beforeEach, afterEach } from 'mocha'; + +// We override the credentials path by pointing PROVAR_CREDENTIALS_PATH at a temp dir. +// The module reads getCredentialsPath() at call time, so we patch os.homedir via an env-var +// approach: override the home dir with a temp directory for tests. +import { + getCredentialsPath, + readStoredCredentials, + writeCredentials, + clearCredentials, + resolveApiKey, + type StoredCredentials, +} from '../../../../src/services/auth/credentials.js'; + +// ── helpers ──────────────────────────────────────────────────────────────────── + +let origHomedir: () => string; +let tempDir: string; + +function useTemp(): void { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'provar-cred-test-')); + origHomedir = os.homedir; + // Monkey-patch homedir for the duration of the test block + (os as unknown as { homedir: () => string }).homedir = (): string => tempDir; +} + +function restoreHome(): void { + (os as unknown as { homedir: () => string }).homedir = origHomedir; + fs.rmSync(tempDir, { recursive: true, force: true }); +} + +// ── getCredentialsPath ───────────────────────────────────────────────────────── + +describe('getCredentialsPath', () => { + it('returns a path ending in .provar/credentials.json', () => { + const p = getCredentialsPath(); + assert.ok(p.endsWith(path.join('.provar', 'credentials.json')), `Got: ${p}`); + }); +}); + +// ── readStoredCredentials ────────────────────────────────────────────────────── + +describe('readStoredCredentials', () => { + beforeEach(useTemp); + afterEach(restoreHome); + + it('returns null when file does not exist', () => { + assert.equal(readStoredCredentials(), null); + }); + + it('returns null on JSON parse failure', () => { + const p = getCredentialsPath(); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, 'not-valid-json'); + assert.equal(readStoredCredentials(), null); + }); + + it('returns parsed object on valid file', () => { + const data: StoredCredentials = { + api_key: 'pv_k_abc123', + prefix: 'pv_k_abc123', + set_at: '2026-01-01T00:00:00.000Z', + source: 'manual', + }; + const p = getCredentialsPath(); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, JSON.stringify(data)); + const result = readStoredCredentials(); + assert.deepEqual(result, data); + }); +}); + +// ── writeCredentials ─────────────────────────────────────────────────────────── + +describe('writeCredentials', () => { + beforeEach(useTemp); + afterEach(restoreHome); + + it('writes a file with the correct shape', () => { + writeCredentials('pv_k_testkey123', 'pv_k_testke', 'manual'); + const stored = readStoredCredentials(); + assert.ok(stored, 'Expected stored credentials to be present'); + assert.equal(stored.api_key, 'pv_k_testkey123'); + assert.equal(stored.prefix, 'pv_k_testke'); + assert.equal(stored.source, 'manual'); + assert.ok(stored.set_at, 'Expected set_at to be present'); + }); + + it('rejects a key that does not start with pv_k_', () => { + assert.throws(() => writeCredentials('invalid-key', 'invalid', 'manual'), /Invalid API key format/); + }); + + it('creates the parent directory if it does not exist', () => { + writeCredentials('pv_k_testkey123', 'pv_k_testke', 'manual'); + assert.ok(fs.existsSync(getCredentialsPath())); + }); +}); + +// ── clearCredentials ─────────────────────────────────────────────────────────── + +describe('clearCredentials', () => { + beforeEach(useTemp); + afterEach(restoreHome); + + it('deletes the credentials file when it exists', () => { + writeCredentials('pv_k_testkey123', 'pv_k_testke', 'manual'); + assert.ok(fs.existsSync(getCredentialsPath())); + clearCredentials(); + assert.ok(!fs.existsSync(getCredentialsPath())); + }); + + it('does not throw when the file does not exist (ENOENT)', () => { + assert.doesNotThrow(() => clearCredentials()); + }); +}); + +// ── resolveApiKey ────────────────────────────────────────────────────────────── + +describe('resolveApiKey', () => { + let savedEnv: string | undefined; + + beforeEach(() => { + savedEnv = process.env.PROVAR_API_KEY; + delete process.env.PROVAR_API_KEY; + useTemp(); + }); + + afterEach(() => { + if (savedEnv === undefined) { + delete process.env.PROVAR_API_KEY; + } else { + process.env.PROVAR_API_KEY = savedEnv; + } + restoreHome(); + }); + + it('returns the env var when set', () => { + process.env.PROVAR_API_KEY = 'pv_k_fromenv'; + assert.equal(resolveApiKey(), 'pv_k_fromenv'); + }); + + it('env var takes priority over a stored file', () => { + writeCredentials('pv_k_fromfile', 'pv_k_fromfil', 'manual'); + process.env.PROVAR_API_KEY = 'pv_k_fromenv'; + assert.equal(resolveApiKey(), 'pv_k_fromenv'); + }); + + it('treats PROVAR_API_KEY="" as unset and falls through to stored file', () => { + process.env.PROVAR_API_KEY = ''; + writeCredentials('pv_k_fromfile', 'pv_k_fromfil', 'manual'); + assert.equal(resolveApiKey(), 'pv_k_fromfile'); + }); + + it('treats PROVAR_API_KEY with only whitespace as unset', () => { + process.env.PROVAR_API_KEY = ' '; + writeCredentials('pv_k_fromfile', 'pv_k_fromfil', 'manual'); + assert.equal(resolveApiKey(), 'pv_k_fromfile'); + }); + + it('treats PROVAR_API_KEY without pv_k_ prefix as invalid and falls through to stored file', () => { + process.env.PROVAR_API_KEY = 'sk-invalid-prefix'; + writeCredentials('pv_k_fromfile', 'pv_k_fromfil', 'manual'); + assert.equal(resolveApiKey(), 'pv_k_fromfile'); + }); + + it('returns stored key when no env var is set', () => { + writeCredentials('pv_k_fromfile', 'pv_k_fromfil', 'manual'); + assert.equal(resolveApiKey(), 'pv_k_fromfile'); + }); + + it('returns null when neither env var nor file is set', () => { + assert.equal(resolveApiKey(), null); + }); + + it('returns null when file is corrupt JSON', () => { + const p = getCredentialsPath(); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, 'not-json'); + assert.equal(resolveApiKey(), null); + }); +}); diff --git a/test/unit/services/qualityHub/client.test.ts b/test/unit/services/qualityHub/client.test.ts new file mode 100644 index 00000000..3daa8f7e --- /dev/null +++ b/test/unit/services/qualityHub/client.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 Provar Limited. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/* eslint-disable camelcase */ +import { strict as assert } from 'node:assert'; +import { describe, it } from 'mocha'; +import { normaliseApiResponse } from '../../../../src/services/qualityHub/client.js'; + +// ── Fixtures ────────────────────────────────────────────────────────────────── + +const BASE_RESPONSE = { + valid: true, + errors: [] as Array, + warnings: [] as Array, + metadata: {}, + quality_metrics: { quality_score: 92, max_score: 100, total_violations: 0, best_practices_grade: 92 }, + validation_mode: 'both', + validated_at: '2026-04-10T21:00:00Z', +}; + +const ERROR_VIOLATION = { + severity: 'critical' as const, + rule_id: 'TC_001', + name: 'XML Declaration', + description: 'Missing XML declaration', + category: 'Structure', + message: 'File must start with ', + weight: 5, + recommendation: 'Add XML declaration as the first line.', + applies_to: ['testcase'], +}; + +const WARNING_VIOLATION = { + severity: 'major' as const, + rule_id: 'BP-STEP-001', + name: 'Step Description Required', + description: 'Step missing description attribute', + category: 'StepQuality', + message: 'Step 1 is missing a description', + weight: 3, + recommendation: 'Add a description attribute to the step.', + applies_to: ['testcase', 'step'], +}; + +// ── normaliseApiResponse ────────────────────────────────────────────────────── + +describe('normaliseApiResponse', () => { + it('maps valid:true → is_valid:true and validity_score:100', () => { + const r = normaliseApiResponse(BASE_RESPONSE); + assert.equal(r.is_valid, true); + assert.equal(r.validity_score, 100); + }); + + it('maps valid:false → is_valid:false and validity_score < 100', () => { + const r = normaliseApiResponse({ ...BASE_RESPONSE, valid: false, errors: [ERROR_VIOLATION] }); + assert.equal(r.is_valid, false); + assert.ok(r.validity_score < 100); + }); + + it('validity_score is never negative regardless of error count', () => { + const manyErrors = Array.from({ length: 10 }, () => ERROR_VIOLATION); + const r = normaliseApiResponse({ ...BASE_RESPONSE, valid: false, errors: manyErrors }); + assert.ok(r.validity_score >= 0); + }); + + it('maps quality_metrics.quality_score → quality_score', () => { + const r = normaliseApiResponse(BASE_RESPONSE); + assert.equal(r.quality_score, 92); + }); + + it('maps errors[] → issues with severity ERROR', () => { + const r = normaliseApiResponse({ ...BASE_RESPONSE, valid: false, errors: [ERROR_VIOLATION] }); + const issue = r.issues.find((i) => i.rule_id === 'TC_001'); + assert.ok(issue, 'Expected TC_001 in issues'); + assert.equal(issue.severity, 'ERROR'); + assert.equal(issue.message, ERROR_VIOLATION.message); + assert.equal(issue.suggestion, ERROR_VIOLATION.recommendation); + assert.equal(issue.applies_to, 'testcase'); + }); + + it('maps warnings[] → issues with severity WARNING', () => { + const r = normaliseApiResponse({ ...BASE_RESPONSE, warnings: [WARNING_VIOLATION] }); + const issue = r.issues.find((i) => i.rule_id === 'BP-STEP-001'); + assert.ok(issue, 'Expected BP-STEP-001 in issues'); + assert.equal(issue.severity, 'WARNING'); + assert.equal(issue.message, WARNING_VIOLATION.message); + assert.equal(issue.suggestion, WARNING_VIOLATION.recommendation); + // applies_to: first element of the array + assert.equal(issue.applies_to, 'testcase'); + }); + + it('combines errors and warnings into a single issues array in order (errors first)', () => { + const r = normaliseApiResponse({ + ...BASE_RESPONSE, + valid: false, + errors: [ERROR_VIOLATION], + warnings: [WARNING_VIOLATION], + }); + assert.equal(r.issues.length, 2); + assert.equal(r.issues[0].severity, 'ERROR'); + assert.equal(r.issues[1].severity, 'WARNING'); + }); + + it('returns empty issues array when both arrays are empty', () => { + const r = normaliseApiResponse(BASE_RESPONSE); + assert.equal(r.issues.length, 0); + }); + + it('handles violation with empty applies_to array gracefully', () => { + const violation = { ...ERROR_VIOLATION, applies_to: [] }; + assert.doesNotThrow(() => normaliseApiResponse({ ...BASE_RESPONSE, valid: false, errors: [violation] })); + const r = normaliseApiResponse({ ...BASE_RESPONSE, valid: false, errors: [violation] }); + assert.equal(r.issues[0].applies_to, undefined); + }); +}); +