From 7b6c07e65af293b0b1b8245ba28a74ba43ae5a21 Mon Sep 17 00:00:00 2001 From: Matthew Thomas <25988124+Ma11hewThomas@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:40:38 +0000 Subject: [PATCH 1/2] Add standard CodeceptJS conformance tests --- .github/workflows/main.yaml | 77 +- CONTRIBUTING.md | 56 + package-lock.json | 2 + package.json | 11 +- tests/conformance-tooling.test.ts | 184 +++ tests/reporters/vitest.mjs | 54 + tests/standard/checker/checker.test.mjs | 384 ++++++ tests/standard/checker/run.mjs | 50 + tests/standard/conformance/README.md | 60 + .../conformance/codeceptjs-checks.mjs | 61 + tests/standard/conformance/codeceptjs.json | 749 +++++++++++ .../conformance/expected-failures.mjs | 63 + tests/standard/conformance/expected.mjs | 136 ++ .../integration/conformance.test.mjs | 1 + .../integration/fixtures/config.cjs | 37 + .../integration/fixtures/helper.cjs | 5 + .../integration/fixtures/observer.cjs | 45 + .../integration/fixtures/one_test.cjs | 23 + .../integration/fixtures/two_test.cjs | 2 + .../integration/fixtures/worker-plugin.cjs | 6 + .../conformance/integration/prepare.mjs | 361 ++++++ tests/standard/conformance/reporters/ctrf.mjs | 106 ++ tests/standard/conformance/reporters/node.mjs | 38 + tests/standard/conformance/run-all.mjs | 12 + tests/standard/conformance/run.mjs | 70 ++ .../conformance/shared/catalogue.json | 1096 +++++++++++++++++ tests/standard/conformance/shared/checks.mjs | 462 +++++++ .../conformance/shared/expected-failures.mjs | 58 + tests/standard/conformance/suite.mjs | 35 + .../conformance/unit/conformance.test.mjs | 1 + tests/standard/conformance/unit/fixtures.mjs | 223 ++++ tests/standard/run-all.mjs | 13 + tests/standard/shared-files.json | 13 + vitest.config.ts | 3 +- 34 files changed, 4491 insertions(+), 6 deletions(-) create mode 100644 tests/conformance-tooling.test.ts create mode 100644 tests/reporters/vitest.mjs create mode 100644 tests/standard/checker/checker.test.mjs create mode 100644 tests/standard/checker/run.mjs create mode 100644 tests/standard/conformance/README.md create mode 100644 tests/standard/conformance/codeceptjs-checks.mjs create mode 100644 tests/standard/conformance/codeceptjs.json create mode 100644 tests/standard/conformance/expected-failures.mjs create mode 100644 tests/standard/conformance/expected.mjs create mode 100644 tests/standard/conformance/integration/conformance.test.mjs create mode 100644 tests/standard/conformance/integration/fixtures/config.cjs create mode 100644 tests/standard/conformance/integration/fixtures/helper.cjs create mode 100644 tests/standard/conformance/integration/fixtures/observer.cjs create mode 100644 tests/standard/conformance/integration/fixtures/one_test.cjs create mode 100644 tests/standard/conformance/integration/fixtures/two_test.cjs create mode 100644 tests/standard/conformance/integration/fixtures/worker-plugin.cjs create mode 100644 tests/standard/conformance/integration/prepare.mjs create mode 100644 tests/standard/conformance/reporters/ctrf.mjs create mode 100644 tests/standard/conformance/reporters/node.mjs create mode 100644 tests/standard/conformance/run-all.mjs create mode 100644 tests/standard/conformance/run.mjs create mode 100644 tests/standard/conformance/shared/catalogue.json create mode 100644 tests/standard/conformance/shared/checks.mjs create mode 100644 tests/standard/conformance/shared/expected-failures.mjs create mode 100644 tests/standard/conformance/suite.mjs create mode 100644 tests/standard/conformance/unit/conformance.test.mjs create mode 100644 tests/standard/conformance/unit/fixtures.mjs create mode 100644 tests/standard/run-all.mjs create mode 100644 tests/standard/shared-files.json diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index acbd249..e1ac1ac 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -51,6 +51,7 @@ jobs: permissions: contents: read actions: read + pull-requests: write steps: - name: Checkout @@ -62,7 +63,13 @@ jobs: node-version: ${{ matrix.node }} cache: npm + - name: Prepare conformance diagnostics + run: | + echo "CTRF_STANDARD_ARTIFACTS=$RUNNER_TEMP/conformance" >> "$GITHUB_ENV" + mkdir -p "$RUNNER_TEMP/conformance" + - name: Install + id: install run: npm ci - name: Type check (TypeScript 7) @@ -72,16 +79,80 @@ jobs: run: npm run test:coverage - name: Framework integration + if: ${{ !cancelled() && steps.install.outcome == 'success' }} run: npm run test:integration - name: Packed consumer checks + if: ${{ !cancelled() && steps.install.outcome == 'success' }} run: npm run test:package + - name: Run shared checker controls + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + run: npm run test:standard:checker + + - name: Run unit conformance + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + run: npm run test:standard:conformance:unit + + - name: Run installed-package conformance with real CodeceptJS + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + run: npm run test:standard:conformance:integration + + - name: Publish conformance test reports + uses: ctrf-io/github-test-reporter@v1 + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + with: + report-path: '${{ runner.temp }}/conformance/reports/*.json' + title: 'Reporter conformance — contract families (Node ${{ matrix.node }})' + summary-report: true + suite-folded-report: true + group-by: suite + pull-request: ${{ matrix.node == '24' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + overwrite-comment: true + comment-tag: reporter-conformance + collapse-large-reports: true + upload-artifact: true + artifact-name: ctrf-conformance-node-${{ matrix.node }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish shared checker controls + uses: ctrf-io/github-test-reporter@v1 + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + with: + report-path: '${{ runner.temp }}/conformance/checker-reports/*.json' + title: 'Shared checker controls (Node ${{ matrix.node }})' + summary-report: true + suite-folded-report: true + group-by: suite + pull-request: ${{ matrix.node == '24' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + overwrite-comment: true + comment-tag: reporter-checker + upload-artifact: true + artifact-name: ctrf-checker-node-${{ matrix.node }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload conformance results and diagnostics + uses: actions/upload-artifact@v4 + if: always() + with: + name: conformance-node-${{ matrix.node }} + path: ${{ runner.temp }}/conformance/ + include-hidden-files: true + if-no-files-found: warn + - name: Publish Test Report uses: ctrf-io/github-test-reporter@v1 with: - report-path: './ctrf/*.json' - summary-delta-report: true + report-path: './ctrf/reporter-tests.json' + title: 'Reporter tests (Node ${{ matrix.node }})' + summary-report: true + test-report: true + collapse-large-reports: true + pull-request: ${{ matrix.node == '24' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + overwrite-comment: true + comment-tag: reporter-tests github-report: true insights-report: true flaky-rate-report: true @@ -90,7 +161,7 @@ jobs: artifact-name: ctrf-test-report-node-${{ matrix.node }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: always() && hashFiles('ctrf/*.json') != '' + if: always() && hashFiles('ctrf/reporter-tests.json') != '' - name: Build run: npm run build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da36704..c7d3ba5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,3 +85,59 @@ The tsup declaration build uses the TypeScript 6 compiler API. Keep its This version removes the old repository-local CTRF type declarations. Use canonical `ctrf` types and strict 0.2.0 validation in new report tests. + +## Standard conformance tests + +Run the relevant checks before submitting a pull request and report their results. +CI publishes the existing reporter tests through GitHub Test Reporter. The +reference Node job maintains a `reporter-tests` PR comment, separately from the +conformance comment; both comments are overwritten on subsequent builds. + +### Conformance tests + +For reporter output changes, update the affected conformance fixtures and run: + +```bash +npm run test:standard:conformance:unit +npm run test:standard:conformance:integration +``` + +`npm run test:standard:conformance` runs both levels even when unit fails. +The [suite guide](tests/standard/conformance/README.md) explains setup and file roles. +Unit tests use controlled native inputs; integration tests install the packed +reporter and run real CodeceptJS through its public plugin interface with independent native observers. +They need npm registry access to install the pinned dependencies and remain +separate from `npm test`. + +Keep common family IDs stable, update `codeceptjs.json` when capabilities change, +and provide positive evidence for supported properties. Unsupported optional +features are skipped with reasons; unrecorded broken behavior must fail. +Expected-failure entries in `tests/standard/conformance/expected-failures.mjs` +must identify a fix follow-up and match specific levels/samples. Use a local +draft identifier until issue publication, then link the GitHub issue. Keep checks running; +unexpected passes and changed failures must fail. Remove markers with the fix. +Check schema and written-specification implications, including valid omission and +conditional rules. Recommendations remain advisory. Do not change expectations +to match incorrect reporter output. + +CI runs both conformance levels as separate steps alongside the existing integration checks in the test/build +job on Node 22, 24 and 26. A unit failure does not prevent integration from running. +The job publishes CTRF results for both levels through GitHub Test Reporter, with +the built-in summary and folded suite table, grouped by level and check category. Node 24 also maintains one PR +comment tagged `reporter-conformance`, overwriting it on later runs. It describes +the reference runtime; check the full matrix for compatibility results. Fork PRs +keep build summaries/artifacts without posting a comment. Expected failures retain +explicit test-name labels and use Other with defect metadata rather than passed. +Unit/integration names and identities stay distinct; the conformance comment +contains contract families only. Shared checker controls use the same independent +fixtures and test IDs across reporters, run with `npm run test:standard:checker`, +and publish separately under `reporter-checker`. Reporter-specific fixture, +matcher and reporting controls belong in the reporter's own tests and +`reporter-tests` results. `npm run test:standard` runs all standard suites. +CTRF artifacts, conformance summaries and integration reports/logs are retained, +including on failure. The commands use the console and CTRF reporters selected by +native test configuration and `tests/standard/conformance/run.mjs`; no additional +runner JSON output is required. Ordinary CTRF is `ctrf/reporter-tests.json`; exclude +fixture-generated producer reports from publication. +In your PR, include commands and results and explain skips, expected failures and +warnings. Track defect details and fixes in GitHub issues rather than these docs. diff --git a/package-lock.json b/package-lock.json index 6ca6076..b523ea9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@types/node": "26.6.5", "@typescript/native": "npm:typescript@7.0.2", "@vitest/coverage-v8": "5.0.3", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", "codeceptjs": "4.2.0", "tsup": "8.5.1", "typescript": "npm:@typescript/typescript6@6.0.2", diff --git a/package.json b/package.json index c5af69f..7b00adb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,12 @@ "lint:check": "biome lint .", "format:check": "biome format .", "format": "biome format --write .", - "all": "npm run build:check && npm run lint:check && npm run format:check && npm run test:coverage && npm run test:integration && npm run test:package" + "all": "npm run build:check && npm run lint:check && npm run format:check && npm run test:coverage && npm run test:integration && npm run test:package", + "test:standard:checker": "node tests/standard/checker/run.mjs", + "test:standard:conformance:unit": "npm run build && node tests/standard/conformance/run.mjs unit", + "test:standard:conformance:integration": "npm run build && node tests/standard/conformance/run.mjs integration", + "test:standard:conformance": "node tests/standard/conformance/run-all.mjs", + "test:standard": "node tests/standard/run-all.mjs" }, "files": [ "dist/", @@ -39,7 +44,9 @@ "codeceptjs": "4.2.0", "tsup": "8.5.1", "typescript": "npm:@typescript/typescript6@6.0.2", - "vitest": "5.0.3" + "vitest": "5.0.3", + "ajv": "8.20.0", + "ajv-formats": "3.0.1" }, "type": "module", "module": "./dist/index.js", diff --git a/tests/conformance-tooling.test.ts b/tests/conformance-tooling.test.ts new file mode 100644 index 0000000..76d72ec --- /dev/null +++ b/tests/conformance-tooling.test.ts @@ -0,0 +1,184 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { test } from "vitest"; +import { + catalogue, + disposition, +} from "./standard/conformance/shared/checks.mjs"; +import { checkCodeceptEntry } from "./standard/conformance/codeceptjs-checks.mjs"; +import { + runConformance, + expectedFailures, +} from "./standard/conformance/expected-failures.mjs"; +import { unitContext } from "./standard/conformance/unit/fixtures.mjs"; +import { recordsFromRun } from "./reporters/vitest.mjs"; +const context = unitContext(); +const clone = () => { + const copy = structuredClone(context); + for (const sample of copy.samples) sample.bytes = Buffer.from(sample.bytes); + return copy; +}; +test("Codecept tooling / reviewed shared snapshot remains exact", () => { + const base = new URL("./standard/", import.meta.url); + const m = JSON.parse( + readFileSync(new URL("shared-files.json", base), "utf8"), + ); + for (const [file, hash] of Object.entries(m.files)) + assert.equal( + createHash("sha256") + .update(readFileSync(new URL(file, base))) + .digest("hex"), + hash, + file, + ); +}); +test("Codecept tooling / capabilities cover exactly the shared inventory", () => { + const m = JSON.parse( + readFileSync( + new URL("./standard/conformance/codeceptjs.json", import.meta.url), + "utf8", + ), + ); + for (const e of catalogue.entries) disposition(e, m); + assert.deepEqual( + Object.keys(m.properties).sort(), + catalogue.entries + .filter((e) => e.kind === "property") + .map((e) => e.path) + .sort(), + ); + assert.deepEqual( + Object.keys(m.rules).sort(), + catalogue.entries + .filter((e) => e.kind === "report") + .map((e) => e.rule) + .sort(), + ); +}); +for (const id of Object.keys(expectedFailures)) { + const entry = catalogue.entries.find((e) => e.id === id); + test(`Codecept emission matcher / ${id} matches exact native rejection at both levels`, () => { + for (const level of ["unit", "integration"]) + assert.equal( + runConformance(entry, context, level).issue, + "native-step-output-rejected", + ); + }); + test(`Codecept emission matcher / ${id} rejects a successful emission`, () => { + const c = clone(); + c.failedEmissions = []; + c.samples.push({ ...c.samples[0], name: "actor-step" }); + assert.throws(() => runConformance(entry, c, "unit"), /Unexpected pass/); + }); + test(`Codecept emission matcher / ${id} rejects changed validation errors`, () => { + const c = clone(); + c.failedEmissions[0].emissionFailure.message += "; another failure"; + assert.throws(() => runConformance(entry, c, "unit")); + }); + test(`Codecept emission matcher / ${id} requires native failure evidence`, () => { + const c = clone(); + c.failedEmissions = []; + assert.throws(() => runConformance(entry, c, "unit"), /fixture missing/); + }); + test(`Codecept emission matcher / ${id} rejects a missing file caused by infrastructure`, () => { + const c = clone(); + c.failedEmissions[0].emissionFailure.errorType = "ENOENT"; + assert.throws(() => runConformance(entry, c, "unit")); + }); +} +test("Codecept oracle / catches corrupted structured runtime metadata", () => { + const c = clone(); + c.samples[0].report.results.tests[0].extra.nested.one = false; + assert.throws(() => + checkCodeceptEntry( + catalogue.entries.find((e) => e.path === "results.tests[].extra"), + c, + ), + ); +}); +test("Codecept oracle / catches inclusion of the final attempt in history", () => { + const c = clone(); + const retry = c.samples[0].report.results.tests.find((t) => t.flaky); + retry.retryAttempts[0].duration = retry.duration; + assert.throws(() => + checkCodeceptEntry( + catalogue.entries.find( + (e) => e.rule === "history-excludes-final-attempt", + ), + c, + ), + ); +}); +test("Codecept oracle / catches a schema-valid native status mapping change", () => { + const c = clone(); + c.samples[0].report.results.tests[0].status = "failed"; + assert.throws(() => + checkCodeceptEntry( + catalogue.entries.find((e) => e.path === "results.tests[].status"), + c, + ), + ); +}); +test("Codecept emission evidence / never fabricates a report or bytes", () => { + assert.equal(context.failedEmissions.length, 1); + const evidence = context.failedEmissions[0]; + assert.equal(Object.hasOwn(evidence, "report"), false); + assert.equal(Object.hasOwn(evidence, "bytes"), false); + assert.equal(evidence.emissionFailure.absentOutput, true); +}); +test("Reporter results / completed records preserve pass, failure, pending and skip", () => { + const modes = [ + { mode: "run", state: "passed" }, + { mode: "run", state: "failed" }, + { mode: "todo", state: "pending" }, + { mode: "skip", state: "skipped" }, + ]; + const modules = [ + { + moduleId: "fixture.test.ts", + children: { + allTests: () => + modes.map((m, i) => ({ + name: `case-${i}`, + options: { mode: m.mode }, + result: () => ({ state: m.state }), + diagnostic: () => ({ duration: 4 }), + })), + }, + errors: () => [], + }, + ]; + assert.deepEqual( + recordsFromRun(modules, []).map((r) => r.status), + ["passed", "failed", "pending", "skipped"], + ); +}); +test("Reporter results / module and unhandled failures stay visible", () => { + const modules = [ + { + moduleId: "broken.test.ts", + children: { allTests: () => [] }, + errors: () => [{ message: "load failed" }], + }, + ]; + assert.deepEqual( + recordsFromRun(modules, [{ message: "unhandled" }]).map((r) => r.status), + ["failed", "failed"], + ); +}); + +test("Codecept oracle / omitted zero retry count still checks absence of history", () => { + const c = clone(); + const test = c.samples[0].report.results.tests[0]; + test.retries = 1; + test.retryAttempts = [{ attempt: 1, status: "failed", duration: 1 }]; + assert.throws( + () => + checkCodeceptEntry( + catalogue.entries.find((e) => e.rule === "zero-retries-omit-history"), + c, + ), + /native unretried/, + ); +}); diff --git a/tests/reporters/vitest.mjs b/tests/reporters/vitest.mjs new file mode 100644 index 0000000..6b658b0 --- /dev/null +++ b/tests/reporters/vitest.mjs @@ -0,0 +1,54 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { createReport } from "../standard/conformance/reporters/ctrf.mjs"; +export function recordsFromRun(modules, unhandledErrors) { + const records = []; + for (const module of modules) { + for (const test of module.children.allTests()) { + const result = test.result(); + records.push({ + name: `${path.basename(module.moduleId)} / ${test.fullName ?? test.name}`, + status: test.options.mode === "todo" ? "pending" : result.state, + duration: test.diagnostic()?.duration ?? 0, + filePath: module.moduleId, + message: result.errors?.map((e) => e.message).join("\n") ?? result.note, + trace: result.errors + ?.map((e) => e.stack) + .filter(Boolean) + .join("\n"), + }); + } + for (const error of module.errors()) + records.push({ + name: `Module error: ${module.moduleId}`, + status: "failed", + message: error.message, + trace: error.stack, + }); + } + for (const error of unhandledErrors) + records.push({ + name: "Unhandled runner error", + status: "failed", + message: error.message, + trace: error.stack, + }); + return records; +} +export default class ReporterTestsCtrf { + onTestRunStart() { + this.start = Date.now(); + } + onTestRunEnd(modules, unhandledErrors) { + const report = createReport( + recordsFromRun(modules, unhandledErrors), + "reporter", + "vitest", + this.start ?? Date.now(), + ); + for (const test of report.results.tests) + test.suite = ["Reporter tests", path.basename(test.filePath ?? "setup")]; + mkdirSync("ctrf", { recursive: true }); + writeFileSync("ctrf/reporter-tests.json", JSON.stringify(report, null, 2)); + } +} diff --git a/tests/standard/checker/checker.test.mjs b/tests/standard/checker/checker.test.mjs new file mode 100644 index 0000000..746daa3 --- /dev/null +++ b/tests/standard/checker/checker.test.mjs @@ -0,0 +1,384 @@ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import assert, { AssertionError } from "node:assert/strict"; +import { test } from "node:test"; +import { getSchema } from "ctrf"; +import { + catalogue, + checkEntry, + reportChecks, + disposition, +} from "../conformance/shared/checks.mjs"; +import { runExpectedFailure } from "../conformance/shared/expected-failures.mjs"; +const property = (path) => + catalogue.entries.find((entry) => entry.path === path); +const rule = (name) => catalogue.entries.find((entry) => entry.rule === name); +function control() { + const report = { + reportFormat: "CTRF", + specVersion: "0.2.0", + results: { + tool: { name: "independent-control" }, + summary: { + tests: 1, + passed: 1, + failed: 0, + pending: 0, + skipped: 0, + other: 0, + start: 100, + stop: 120, + }, + tests: [ + { + name: "case", + status: "passed", + duration: 12, + retries: 1, + flaky: true, + attemptId: "final", + retryAttempts: [{ attempt: 1, attemptId: "first", status: "failed" }], + }, + ], + }, + }; + return { + samples: [ + { + name: "valid", + report, + bytes: Buffer.from(JSON.stringify(report)), + expected: { + results: { + summary: { tests: 1 }, + tests: [ + { + duration: 12, + sourceAttempts: [ + { status: "failed" }, + { status: "passed", duration: 12 }, + ], + }, + ], + }, + }, + }, + ], + warnings: [], + }; +} +test("checker/schema/catalogue-covers-expanded-properties-and-relationships", () => { + const schema = getSchema(catalogue.specVersion), + paths = []; + function walk(input, prefix = "") { + const node = input.$ref + ? input.$ref + .slice(2) + .split("/") + .reduce((value, key) => value[key], schema) + : input; + for (const [key, child] of Object.entries(node.properties ?? {})) { + const path = prefix ? `${prefix}.${key}` : key; + paths.push(path); + walk(child, path); + } + if (node.items) walk(node.items, `${prefix}[]`); + } + walk(schema); + assert.deepEqual( + catalogue.entries.filter((e) => e.kind === "property").map((e) => e.path), + paths, + ); + assert.deepEqual( + catalogue.entries + .filter((e) => e.kind === "report") + .map((e) => e.rule) + .sort(), + Object.keys(reportChecks).sort(), + ); + assert.equal( + new Set(catalogue.entries.map((e) => e.id)).size, + catalogue.entries.length, + ); +}); +test("checker/schema/accepts-independent-valid-document", () => + checkEntry(rule("single-json-object"), control())); +for (const [name, mutate] of [ + [ + "unknown-property", + (s) => { + s.report.unknown = true; + }, + ], + [ + "wrong-type", + (s) => { + s.report.results.tests[0].duration = "12"; + }, + ], + [ + "missing-required-field", + (s) => { + delete s.report.results.tests[0].status; + }, + ], +]) + test(`checker/schema/rejects-${name}`, () => { + const c = control(); + mutate(c.samples[0]); + c.samples[0].bytes = Buffer.from(JSON.stringify(c.samples[0].report)); + assert.throws(() => checkEntry(rule("single-json-object"), c)); + }); +test("checker/schema/requires-positive-evidence", () => + assert.throws( + () => checkEntry(property("results.tests[].screenshot"), control()), + /No positive evidence/, + )); +test("checker/schema/required-child-is-checked-when-parent-exists", () => { + const c = control(); + delete c.samples[0].report.results.tests[0].status; + assert.throws( + () => checkEntry(property("results.tests[].status"), c), + /missing/, + ); +}); +test("checker/schema/capabilities-require-entry-and-reason", () => { + assert.throws(() => + disposition(property("reportFormat"), { properties: {} }), + ); + assert.throws(() => + disposition(property("reportFormat"), { + properties: { reportFormat: { supported: true } }, + }), + ); +}); +test("checker/schema/unsupported-declaration-cannot-hide-emitted-invalid-field", () => { + const c = control(); + c.samples[0].report.results.tests[0].extra = "invalid"; + c.samples[0].bytes = Buffer.from(JSON.stringify(c.samples[0].report)); + assert.throws(() => checkEntry(rule("single-json-object"), c)); +}); +test("checker/specification/accepts-independent-retry-history", () => { + for (const name of [ + "history-requires-retry-count", + "positive-retries-require-history", + "retry-count-matches-history-length", + "history-orders-contiguous-attempts", + "history-excludes-final-attempt", + "flaky-matches-attempt-outcomes", + ]) + checkEntry(rule(name), control()); +}); +for (const [name, mutate] of [ + [ + "utf8-without-bom", + (s) => { + s.bytes = Buffer.from([0xef, 0xbb, 0xbf, 0x7b, 0x7d]); + }, + ], + [ + "summary-matches-final-results", + (s) => { + s.report.results.summary.passed++; + }, + ], + [ + "history-orders-contiguous-attempts", + (s) => { + s.report.results.tests[0].retryAttempts[0].attempt = 2; + }, + ], + [ + "retry-count-matches-history-length", + (s) => { + s.report.results.tests[0].retries = 2; + }, + ], + [ + "history-excludes-final-attempt", + (s) => { + s.report.results.tests[0].retryAttempts.push({ + attempt: 2, + status: "passed", + }); + }, + ], + [ + "flaky-matches-attempt-outcomes", + (s) => { + s.report.results.tests[0].flaky = false; + }, + ], + [ + "attempt-identity-scoped-to-execution", + (s) => { + s.report.results.tests[0].attemptId = "first"; + }, + ], + [ + "respects-reserved-extension-ownership", + (s) => { + s.report.results.tests[0].extra = { "ctrf.not-owned": true }; + }, + ], +]) + test(`checker/specification/rejects-${name}-mutation`, () => { + const c = control(); + mutate(c.samples[0]); + assert.throws(() => reportChecks[name](c)); + }); +test("checker/specification/rejects-source-duration-unit-mismatch", () => { + const c = control(); + c.samples[0].report.results.tests[0].duration *= 1000; + assert.throws(() => checkEntry(property("results.tests[].duration"), c)); +}); +test("checker/specification/missing-contextual-evidence-fails", () => { + for (const name of [ + "case-identity-stable-across-runs", + "coordinates-run-and-shard-identity", + ]) + assert.throws(() => checkEntry(rule(name), control()), /Missing/); +}); +test("checker/specification/timing-recommendation-warns", () => { + const c = control(); + c.samples[0].report.results.summary.stop = 50; + checkEntry(rule("orders-start-and-stop"), c); + assert.equal(c.warnings.length, 1); +}); +test("checker/specification/total-count-recommendation-warns-but-bounds-fail", () => { + const c = control(); + c.samples[0].report.results.summary.tests = 2; + checkEntry(property("results.summary.tests"), c); + checkEntry(rule("summary-matches-final-results"), c); + assert.equal(c.warnings.length, 1); + c.samples[0].report.results.summary.tests = -1; + assert.throws( + () => checkEntry(property("results.summary.tests"), c), + /nonnegative/, + ); +}); +const entry = property("results.environment.buildNumber"); +const issue = { + issue: "control-defect", + reason: "Synthetic checker control", + followUp: "https://example.test/control", + samples: { unit: ["first", "second"] }, +}; +function defectContext() { + return { + samples: ["first", "second"].map((name) => ({ + name, + report: { results: { environment: { buildNumber: 1.5 } } }, + })), + }; +} +function check(_entry, context) { + for (const sample of context.samples) + assert.equal( + sample.report.results.environment.buildNumber, + 0, + "control mismatch", + ); +} +function matches(_entry, _sample, _issue, error) { + return ( + error instanceof AssertionError && + error.actual === 1.5 && + error.expected === 0 && + error.operator === "strictEqual" && + error.message.startsWith("control mismatch\n") + ); +} +const run = (context, checker = check, level = "unit") => + runExpectedFailure(entry, context, level, issue, checker, matches); +test("checker/expected-failure/accepts-only-recorded-mismatch", () => { + assert.equal(run(defectContext()).issue, "control-defect"); +}); +for (const [name, mutate] of [ + [ + "unexpected-pass", + (c) => { + for (const s of c.samples) s.report.results.environment.buildNumber = 0; + }, + ], + [ + "partial-fix", + (c) => { + c.samples[1].report.results.environment.buildNumber = 0; + }, + ], + [ + "different-value", + (c) => { + c.samples[0].report.results.environment.buildNumber = "wrong"; + }, + ], + [ + "missing-sample", + (c) => { + c.samples.pop(); + }, + ], + [ + "duplicate-sample", + (c) => { + c.samples.push(c.samples[0]); + }, + ], + [ + "unrecorded-broken-sample", + (c) => { + c.samples.push({ + name: "third", + report: { results: { environment: { buildNumber: 1.5 } } }, + }); + }, + ], +]) + test(`checker/expected-failure/rejects-${name}`, () => { + const c = defectContext(); + mutate(c); + assert.throws(() => run(c)); + }); +test("checker/expected-failure/rejects-infrastructure-error", () => + assert.throws( + () => + run(defectContext(), () => { + throw Error("infrastructure"); + }), + /infrastructure/, + )); +test("checker/expected-failure/does-not-apply-at-unrecorded-level", () => + assert.throws( + () => run(defectContext(), check, "integration"), + /control mismatch/, + )); +test("checker/expected-failure/allows-optional-absence-outside-defect-samples", () => { + const c = defectContext(); + c.samples.push({ name: "absent", report: { results: {} } }); + assert.equal(run(c).issue, "control-defect"); +}); +test("checker/expected-failure/rejects-unrelated-error-in-later-sample", () => + assert.throws( + () => + run(defectContext(), (item, c) => { + if (c.samples[0].name === "second") throw Error("different error"); + check(item, c); + }), + /different error/, + )); + +test("checker/schema/shared-snapshot-matches-declared-files", () => { + const snapshot = JSON.parse( + readFileSync(new URL("../shared-files.json", import.meta.url)), + ); + assert.ok(snapshot.revision); + for (const [file, sha256] of Object.entries(snapshot.files)) + assert.equal( + createHash("sha256") + .update(readFileSync(new URL(`../${file}`, import.meta.url))) + .digest("hex"), + sha256, + `Shared file drift: ${file}; review and synchronize the standard revision across reporters`, + ); +}); diff --git a/tests/standard/checker/run.mjs b/tests/standard/checker/run.mjs new file mode 100644 index 0000000..eef0eab --- /dev/null +++ b/tests/standard/checker/run.mjs @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { existsSync, rmSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { reportPath, writeReport } from "../conformance/reporters/ctrf.mjs"; +const root = fileURLToPath(new URL("../../../", import.meta.url)); +const level = "checker"; +const start = Date.now(); +rmSync(reportPath(level), { force: true }); +try { + const result = spawnSync( + process.execPath, + [ + "--test", + "--test-reporter=spec", + "--test-reporter-destination=stdout", + "--test-reporter=./tests/standard/conformance/reporters/node.mjs", + "--test-reporter-destination=stdout", + "tests/standard/checker/checker.test.mjs", + ], + { + cwd: root, + env: { ...process.env, CTRF_CONFORMANCE_LEVEL: level }, + stdio: "inherit", + timeout: 300_000, + }, + ); + assert.ifError(result.error); + assert.equal(result.signal, null); + assert.ok( + existsSync(reportPath(level)), + "Native runner did not emit its CTRF result", + ); + process.exitCode = result.status; +} catch (error) { + writeReport( + [ + { + name: `${level} setup`, + status: "failed", + message: error.message, + trace: error.stack, + }, + ], + level, + "node:test", + start, + ); + throw error; +} diff --git a/tests/standard/conformance/README.md b/tests/standard/conformance/README.md new file mode 100644 index 0000000..74f4df5 --- /dev/null +++ b/tests/standard/conformance/README.md @@ -0,0 +1,60 @@ +# Standard conformance + +Conformance uses shared property and report/relationship checks at both unit and +integration levels, against the declared CTRF specification version. These are +coverage families, not an exhaustive set of cases. Existing reporter tests are separate. + +```sh +npm run test:standard:conformance:unit +npm run test:standard:conformance:integration +# Run both, even if unit fails: +npm run test:standard:conformance +``` + +Unit tests capture output from controlled native inputs; separate controls +test the checker. Integration installs the packed reporter in a temporary consumer +and runs real CodeceptJS through its public plugin interface. It needs npm registry access to +install the pinned dependencies. Missing setup fails the run. + +Temporary consumers are cleaned automatically. Set `CTRF_STANDARD_ARTIFACTS` to an +absolute directory outside the checkout to retain integration reports and logs. + +Both suites generate schema-validated CTRF under `$CTRF_STANDARD_ARTIFACTS/reports` +(or `ctrf/reports` locally). CI publishes these through GitHub Test Reporter in the +integration job summary and retains CTRF artifacts. The reference Node job also +maintains one PR comment using the built-in summary and folded suite table; +no custom template is needed. Fork PRs retain build summaries and artifacts with +read-only tokens. Shared checker controls run and report separately. Expected failures use **Other**, +explicit labels and defect metadata; +they are not counted as passed. Unit and integration identities remain distinct. + +- `shared/catalogue.json`: common family IDs for this pilot; property paths are + checked against the schema. It is a local snapshot pending shared distribution. +- `shared/checks.mjs`: schema and specification assertions. +- `shared/expected-failures.mjs`: common strict expected-failure engine. +- `codeceptjs.json`: capabilities and reasons for unsupported entries. +- `expected.mjs` and native fixtures: independent CodeceptJS expectations. + +Unsupported optional features remain skipped with reasons. Supported fields need +positive evidence; all emitted fields undergo full-document validation. Mandatory +violations fail, recommendations warn, and missing fixture evidence cannot count +as a pass. Expected-failure entries in `expected-failures.mjs` identify tracked defects +and match specific samples. They still execute and +are reported separately as expected failures; different errors or unexpected +passes fail CI. Remove the matching markers when fixing a defect. Expected +failures are not conformance passes. +The conformance comment contains only contract families, grouped by unit/integration +and schema properties/specification relationships. Expected failures have separate +tracked-defect groups. The top summary distinguishes skipped, failed and Other; +the built-in folded table combines skipped/pending/Other in its Other column. +Expected-failure groups contain only tracked defects, never unsupported skips. + +`npm run test:standard:checker` runs the independent shared checker controls in +`tests/standard/checker`. These have the same IDs and implementation for every +reporter adopting this revision and publish to a separate `reporter-checker` +comment. Their CTRF is under `$CTRF_STANDARD_ARTIFACTS/checker-reports` (or +`ctrf/checker-reports` locally), outside the conformance report glob. +`npm run test:standard` runs shared controls and both conformance levels even if +one fails. Reporter-specific fixture, matcher and reporting-adapter controls live +in the reporter's own tests and appear in the `reporter-tests` comment. +Test tooling is excluded from the published package. diff --git a/tests/standard/conformance/codeceptjs-checks.mjs b/tests/standard/conformance/codeceptjs-checks.mjs new file mode 100644 index 0000000..949e09e --- /dev/null +++ b/tests/standard/conformance/codeceptjs-checks.mjs @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { checkEntry } from "./shared/checks.mjs"; +export function checkCodeceptEntry(entry, context) { + for (const sample of context.samples) + if (sample.emissionFailure) { + const failure = sample.emissionFailure; + assert.fail( + `${sample.name}: producer rejected native step output: ${failure.errorType}: ${failure.message}`, + ); + } + checkEntry(entry, context); + if (entry.rule === "zero-retries-omit-history") { + // Codecept omits the optional retries field for an unretried scenario. + // Use independently observed attempts to exercise this branch rather + // than relying on a literal retries: 0 that this producer never emits. + let witnessed = 0; + for (const sample of context.samples) + for (const [index, source] of sample.expected.results.tests.entries()) { + if (source.sourceAttempts.length !== 1) continue; + witnessed++; + assert.equal( + sample.report.results.tests[index].retryAttempts, + undefined, + `${sample.name}: native unretried execution must omit history`, + ); + } + assert.ok(witnessed > 0, "Missing native unretried execution evidence"); + } + + if ( + [ + "results.tests[].extra", + "results.environment.extra", + "results.tests[].steps", + "results.tests[].retryAttempts[].extra", + ].includes(entry.path) + ) + for (const sample of context.samples) { + let actual, expected; + if (entry.path === "results.environment.extra") { + actual = sample.report.results.environment?.extra; + expected = sample.expected.results.environment?.extra; + } else if (entry.path.includes("retryAttempts")) { + actual = sample.report.results.tests.flatMap((t) => + (t.retryAttempts ?? []).map((a) => a.extra), + ); + expected = sample.expected.results.tests.flatMap((t) => + (t.retryAttempts ?? []).map((a) => a.extra), + ); + } else { + const key = entry.path.split(".").at(-1); + actual = sample.report.results.tests.map((t) => t[key]); + expected = sample.expected.results.tests.map((t) => t[key]); + } + assert.deepEqual( + actual, + expected, + `${sample.name}: native structured source mapping ${entry.path}`, + ); + } +} diff --git a/tests/standard/conformance/codeceptjs.json b/tests/standard/conformance/codeceptjs.json new file mode 100644 index 0000000..26d19fc --- /dev/null +++ b/tests/standard/conformance/codeceptjs.json @@ -0,0 +1,749 @@ +{ + "reporter": "codeceptjs", + "specVersion": "0.2.0", + "sourceRevision": "jest-ctrf-json-reporter@a067e6d94f172db0636c4bd1d9c3706cafae2b48", + "properties": { + "reportFormat": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "specVersion": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "reportId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "runId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "timestamp": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "generatedBy": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tool": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tool.name": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tool.version": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tool.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.summary": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.tests": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.passed": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.failed": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.skipped": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.pending": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.other": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.flaky": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.summary.suites": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.summary.start": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.stop": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.summary.duration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.summary.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].id": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].testId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].executionId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].attemptId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].name": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].status": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].duration": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].start": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].stop": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].suite": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].message": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].trace": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].snippet": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].ai": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].line": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].rawStatus": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].tags": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].labels": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].type": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].filePath": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retries": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].attempt": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].attemptId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].status": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].duration": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].message": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].trace": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].line": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].snippet": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].stdout": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].stderr": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].start": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].stop": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].retryAttempts[].attachments": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].attachments[].attachmentId": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].attachments[].name": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].attachments[].contentType": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].attachments[].path": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].attachments[].extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].retryAttempts[].extra": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].flaky": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.tests[].stdout": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].stderr": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].threadId": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].browser": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].device": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].screenshot": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments[].attachmentId": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments[].name": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments[].contentType": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments[].path": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].attachments[].extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].parameters": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].steps": { + "supported": true, + "reason": "Supported producer field; native actor-step emission is currently rejected before serialization and is tracked as an exact expected failure" + }, + "results.tests[].steps[].name": { + "supported": true, + "reason": "Supported producer field; native actor-step emission is currently rejected before serialization and is tracked as an exact expected failure" + }, + "results.tests[].steps[].status": { + "supported": true, + "reason": "Supported producer field; native actor-step emission is currently rejected before serialization and is tracked as an exact expected failure" + }, + "results.tests[].steps[].extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.passRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.passRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.passRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.passRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.failRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.failRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.failRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.failRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.flakyRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.flakyRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.flakyRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.flakyRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.averageTestDuration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.averageTestDuration.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.averageTestDuration.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.averageTestDuration.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.p95TestDuration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.p95TestDuration.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.p95TestDuration.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.p95TestDuration.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.executedInRuns": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].insights.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.tests[].extra": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.reportName": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.environment.appName": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.environment.appVersion": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.buildId": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.environment.buildName": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.buildNumber": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.buildUrl": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.repositoryName": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.repositoryUrl": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.commit": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.branchName": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.osPlatform": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.osRelease": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.osVersion": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.testEnvironment": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.shardId": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.environment.healthy": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "results.environment.extra": { + "supported": true, + "reason": "Emitted by CodeceptJS reporter from native events, configuration or runtime metadata" + }, + "results.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.passRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.passRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.passRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.passRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.failRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.failRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.failRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.failRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.flakyRate": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.flakyRate.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.flakyRate.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.flakyRate.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageRunDuration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageRunDuration.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageRunDuration.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageRunDuration.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.p95RunDuration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.p95RunDuration.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.p95RunDuration.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.p95RunDuration.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageTestDuration": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageTestDuration.current": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageTestDuration.baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.averageTestDuration.change": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.runsAnalyzed": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "insights.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.reportId": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.timestamp": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.source": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.buildNumber": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.buildName": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.buildUrl": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.commit": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + }, + "baseline.extra": { + "supported": false, + "reason": "CodeceptJS reporter does not emit this optional property" + } + }, + "rules": { + "utf8-without-bom": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "single-json-object": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "declares-supported-contract": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "summary-matches-final-results": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "summary-matches-flaky-results": { + "supported": false, + "reason": "CodeceptJS emits no optional summary.flaky or attachments" + }, + "orders-start-and-stop": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "history-requires-retry-count": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "positive-retries-require-history": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "zero-retries-omit-history": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "retry-count-matches-history-length": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "history-orders-contiguous-attempts": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "history-excludes-final-attempt": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "flaky-matches-attempt-outcomes": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "case-identity-stable-across-runs": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "execution-identity-distinguishes-executions": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "attempt-identity-scoped-to-execution": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "attachment-identity-scoped-to-parent": { + "supported": false, + "reason": "CodeceptJS emits no optional summary.flaky or attachments" + }, + "document-identity-follows-emission": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "coordinates-run-and-shard-identity": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + }, + "respects-reserved-extension-ownership": { + "supported": true, + "reason": "Applicable to CodeceptJS output and controlled native attempts" + } + } +} diff --git a/tests/standard/conformance/expected-failures.mjs b/tests/standard/conformance/expected-failures.mjs new file mode 100644 index 0000000..a004124 --- /dev/null +++ b/tests/standard/conformance/expected-failures.mjs @@ -0,0 +1,63 @@ +import { AssertionError } from "node:assert/strict"; +import { checkCodeceptEntry } from "./codeceptjs-checks.mjs"; +import { runExpectedFailure } from "./shared/expected-failures.mjs"; +const issue = { + issue: "native-step-output-rejected", + reason: + "Native CodeceptJS actor steps have no name field; producer validation prevents report emission", + followUp: + "https://github.com/ctrf-io/codeceptjs-ctrf-json-reporter/issues/26", + samples: { unit: ["actor-step"], integration: ["actor-step"] }, +}; +export const expectedFailures = Object.fromEntries( + [ + "conformance/report/single-json-object", + "conformance/property/results.tests[].steps", + "conformance/property/results.tests[].steps[].name", + "conformance/property/results.tests[].steps[].status", + ].map((id) => [id, issue]), +); +export function expectedFailure(entry, level) { + return expectedFailures[entry.id]?.samples[level] + ? expectedFailures[entry.id] + : undefined; +} +export function matchesKnownFailure(_entry, sample, record, error) { + const failure = sample.emissionFailure; + const message = + "CTRF validation failed:\n/results/tests/0/steps/0: must have required property 'name'"; + return ( + record.issue === "native-step-output-rejected" && + sample.name === "actor-step" && + error instanceof AssertionError && + failure?.absentOutput === true && + failure.errorType === "ValidationError" && + failure.message === message && + failure.nativeSteps.length === 1 && + failure.nativeSteps[0].name === undefined && + failure.nativeSteps[0].title === "check" && + failure.nativeSteps[0].helperMethod === "check" && + failure.nativeSteps[0].status === "success" && + error.message === + `${sample.name}: producer rejected native step output: ValidationError: ${message}` + ); +} +export function runConformance(entry, context, level) { + const record = expectedFailure(entry, level); + // Only the affected families receive failed-emission evidence. Every other + // family checks real captured reports. Shared code and IDs stay unchanged. + const evidence = record + ? { + ...context, + samples: [...context.samples, ...(context.failedEmissions ?? [])], + } + : context; + return runExpectedFailure( + entry, + evidence, + level, + record, + checkCodeceptEntry, + matchesKnownFailure, + ); +} diff --git a/tests/standard/conformance/expected.mjs b/tests/standard/conformance/expected.mjs new file mode 100644 index 0000000..d9e36dc --- /dev/null +++ b/tests/standard/conformance/expected.mjs @@ -0,0 +1,136 @@ +export const metadata = { + runId: "controlled-run", + shardId: "one", + appName: "fixture", + appVersion: "1.2.3", + osPlatform: "linux", + osRelease: "controlled", + osVersion: "6", + buildName: "build", + buildNumber: "42", + buildUrl: "https://example.com/build/42", + repositoryName: "fixture", + repositoryUrl: "https://example.com/repository", + branchName: "main", + commit: "abc123", + testEnvironment: "test", +}; +export const passExtra = { + owner: "last", + nested: { one: true, two: true }, + list: ["one", "two"], +}; +export function nativeExtra(title, attempt) { + if (title.startsWith("passing")) return structuredClone(passExtra); + if (title.includes("retry")) return { attempt: attempt + 1 }; + return undefined; +} +const elapsed = (n) => + typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : 0; +export function expectedReport(native, options) { + const groups = new Map(); + for (const item of native.attempts) { + const key = JSON.stringify([item.file, item.suite, item.title]); + if (!groups.has(key)) groups.set(key, []); + groups.get(key).push(item); + } + const tests = [...groups.values()].map((attempts) => { + const t = attempts.at(-1); + const result = { + name: t.title, + status: t.status, + duration: elapsed( + t.duration ?? t.steps?.reduce((sum, s) => sum + elapsed(s.duration), 0), + ), + sourceAttempts: attempts.map((a) => ({ + status: a.status, + duration: elapsed( + a.duration ?? + a.steps?.reduce((sum, s) => sum + elapsed(s.duration), 0), + ), + })), + }; + if (!options.minimal) { + if ( + t.startedAt !== undefined && + Number.isFinite(t.startedAt) && + t.startedAt >= 0 + ) { + result.start = t.startedAt; + result.stop = t.startedAt + result.duration; + } + if (t.suite?.length) result.suite = t.suite; + if (t.file) result.filePath = t.file; + if (t.tags?.length) result.tags = t.tags; + result.rawStatus = t.status; + result.type = options.testType ?? "e2e"; + if (t.status === "failed" && t.error) { + result.message = t.error.message; + result.trace = t.error.stack; + } + if (t.steps?.length) + result.steps = t.steps.map((s) => ({ + name: s.name, + status: ["passed", "failed", "skipped", "pending", "other"].includes( + s.status, + ) + ? s.status + : "other", + })); + const extra = nativeExtra(t.title, t.attempt); + if (extra) result.extra = extra; + } + if (options.customIdentity) result.testId = `custom:${t.title}`; + if (attempts.length > 1) { + result.retries = attempts.length - 1; + result.flaky = + t.status === "passed" && + attempts.slice(0, -1).some((a) => a.status === "failed"); + result.retryAttempts = attempts.slice(0, -1).map((a, i) => { + const h = { + attempt: i + 1, + status: a.status, + duration: result.sourceAttempts[i].duration, + }; + if (!options.minimal) { + if (a.startedAt !== undefined) { + h.start = a.startedAt; + h.stop = a.startedAt + h.duration; + } + if (a.status === "failed" && a.error) { + h.message = a.error.message; + h.trace = a.error.stack; + } + const extra = nativeExtra(a.title, a.attempt); + if (extra) h.extra = extra; + } + return h; + }); + } + return result; + }); + const environment = Object.fromEntries( + Object.entries(options).filter( + ([key]) => key in metadata && !["runId", "appName"].includes(key), + ), + ); + if (environment.buildNumber !== undefined) + environment.buildNumber = Number(environment.buildNumber); + if (options.appName !== undefined) + environment.extra = { appName: options.appName }; + const summary = { tests: tests.length }; + for (const status of ["passed", "failed", "pending", "skipped", "other"]) + summary[status] = tests.filter((t) => t.status === status).length; + return { + reportFormat: "CTRF", + specVersion: "0.2.0", + generatedBy: "codeceptjs-ctrf-json-reporter", + ...(options.runId ? { runId: options.runId } : {}), + results: { + tool: { name: "codeceptjs" }, + summary, + tests, + ...(Object.keys(environment).length ? { environment } : {}), + }, + }; +} diff --git a/tests/standard/conformance/integration/conformance.test.mjs b/tests/standard/conformance/integration/conformance.test.mjs new file mode 100644 index 0000000..6321d3e --- /dev/null +++ b/tests/standard/conformance/integration/conformance.test.mjs @@ -0,0 +1 @@ +import "../suite.mjs"; diff --git a/tests/standard/conformance/integration/fixtures/config.cjs b/tests/standard/conformance/integration/fixtures/config.cjs new file mode 100644 index 0000000..74d4f04 --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/config.cjs @@ -0,0 +1,37 @@ +const path = require("node:path"); +const fixture = JSON.parse(process.env.CTRF_FIXTURE_CONFIG); +const options = { ...fixture.options, outputDir: fixture.output }; +if (options.customIdentity) + options.testIdResolver = ({ name }) => `custom:${name}`; +exports.config = { + tests: path.join( + __dirname, + fixture.shard === 0 + ? "one_test.cjs" + : fixture.shard === 1 + ? "two_test.cjs" + : "*_test.cjs", + ), + output: fixture.output, + helpers: { Fixture: { require: path.join(__dirname, "helper.cjs") } }, + plugins: { + nativeObserver: { + enabled: true, + require: path.join(__dirname, "observer.cjs"), + outputDir: fixture.output, + }, + ctrf: { + enabled: true, + require: fixture.workers + ? path.join(__dirname, "worker-plugin.cjs") + : fixture.esm + ? require + .resolve("codeceptjs-ctrf-json-reporter") + .replace("index.cjs", "index.js") + : "codeceptjs-ctrf-json-reporter", + ...options, + outputFile: "report.json", + }, + }, + name: "ctrf-fixture", +}; diff --git a/tests/standard/conformance/integration/fixtures/helper.cjs b/tests/standard/conformance/integration/fixtures/helper.cjs new file mode 100644 index 0000000..4b7c86c --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/helper.cjs @@ -0,0 +1,5 @@ +module.exports = class Fixture extends require("codeceptjs").helper { + check(value) { + if (!value) throw new Error("controlled step failure"); + } +}; diff --git a/tests/standard/conformance/integration/fixtures/observer.cjs b/tests/standard/conformance/integration/fixtures/observer.cjs new file mode 100644 index 0000000..0a3d84b --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/observer.cjs @@ -0,0 +1,45 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { threadId } = require("node:worker_threads"); +const imported = require("codeceptjs/lib/event"); +const event = imported.default ?? imported; +module.exports = (options) => { + const native = { attempts: [], threadId, started: 0 }; + event.dispatcher.on(event.test.started, () => { + native.started++; + }); + for (const status of ["passed", "failed", "skipped"]) + event.dispatcher.on(event.test[status], (test, error) => { + const suite = []; + for (let p = test.parent; p && !p.root; p = p.parent) + if (p.title) suite.unshift(p.title); + const e = error ?? test.err; + native.attempts.push({ + title: test.title, + uid: test.uid, + file: test.file, + suite, + tags: test.tags, + startedAt: test.startedAt, + duration: test.duration, + steps: test.steps?.map((s) => ({ + name: s.name ?? s.title, + rawName: s.name, + title: s.title, + helperMethod: s.helperMethod, + status: s.status, + duration: s.duration, + })), + attempt: Math.max(test.currentRetry?.() ?? 0, test.retryNum ?? 0), + status, + error: e ? { message: e.message, stack: e.stack } : undefined, + }); + }); + event.dispatcher.on(event.all.result, () => { + fs.mkdirSync(options.outputDir, { recursive: true }); + fs.writeFileSync( + path.join(options.outputDir, `native-${threadId}.json`), + JSON.stringify(native), + ); + }); +}; diff --git a/tests/standard/conformance/integration/fixtures/one_test.cjs b/tests/standard/conformance/integration/fixtures/one_test.cjs new file mode 100644 index 0000000..a1cdb2b --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/one_test.cjs @@ -0,0 +1,23 @@ +const { extra } = require("codeceptjs-ctrf-json-reporter/runtime"); +Feature("Native one"); +Scenario("passing ✓ @smoke", ({ I }) => { + extra({ owner: "first", nested: { one: true }, list: ["one"] }); + extra({ owner: "last", nested: { two: true }, list: ["two"] }); + if (process.env.CTRF_ACTOR_STEP === "true") I.check(true); +}); +Scenario.skip("skipped", () => {}); +let recovery = 0; +Scenario("retry recovery", () => { + recovery++; + extra({ attempt: recovery }); + if (recovery < 3 && process.env.CTRF_FIXTURE_PASSED !== "true") + throw new Error(`recovery attempt ${recovery}`); +}).retry(2); +let exhausted = 0; +Scenario("retry exhausted", () => { + exhausted++; + extra({ attempt: exhausted }); + if (process.env.CTRF_FIXTURE_PASSED !== "true") + throw new Error(`exhausted attempt ${exhausted}`); +}).retry(1); +Scenario("same", () => {}); diff --git a/tests/standard/conformance/integration/fixtures/two_test.cjs b/tests/standard/conformance/integration/fixtures/two_test.cjs new file mode 100644 index 0000000..73ac4aa --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/two_test.cjs @@ -0,0 +1,2 @@ +Feature("Native two"); +Scenario("same", () => {}); diff --git a/tests/standard/conformance/integration/fixtures/worker-plugin.cjs b/tests/standard/conformance/integration/fixtures/worker-plugin.cjs new file mode 100644 index 0000000..7ab74cb --- /dev/null +++ b/tests/standard/conformance/integration/fixtures/worker-plugin.cjs @@ -0,0 +1,6 @@ +const { threadId } = require("node:worker_threads"); +module.exports = (options) => + require("codeceptjs-ctrf-json-reporter")({ + ...options, + outputFile: `report-${threadId}.json`, + }); diff --git a/tests/standard/conformance/integration/prepare.mjs b/tests/standard/conformance/integration/prepare.mjs new file mode 100644 index 0000000..63ee7ff --- /dev/null +++ b/tests/standard/conformance/integration/prepare.mjs @@ -0,0 +1,361 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { + cpSync, + mkdirSync, + mkdtempSync, + readFileSync, + writeFileSync, + readdirSync, + rmSync, + existsSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { metadata, expectedReport } from "../expected.mjs"; +import { catalogue, checkEntry } from "../shared/checks.mjs"; +export function integrationContext(root, diagnostics) { + const consumer = mkdtempSync(path.join(tmpdir(), "ctrf-codecept-consumer-")); + const npm = process.platform === "win32" ? "npm.cmd" : "npm"; + const env = { ...process.env, FORCE_COLOR: "0" }; + delete env.NODE_PATH; + delete env.NODE_OPTIONS; + function execute(cmd, args, name, expected = 0) { + const result = spawnSync(cmd, args, { + cwd: consumer, + env, + encoding: "utf8", + timeout: 240000, + maxBuffer: 16 * 1024 * 1024, + }); + if (diagnostics) { + mkdirSync(diagnostics, { recursive: true }); + writeFileSync( + path.join(diagnostics, `${name}.log`), + `${result.stdout ?? ""}\n${result.stderr ?? ""}\n${result.error ?? ""}`, + ); + } + assert.ifError(result.error); + assert.equal(result.signal, null, name); + if (expected !== undefined) + assert.equal( + result.status, + expected, + `${name}: ${result.stdout} ${result.stderr}`, + ); + return result; + } + try { + const pkg = JSON.parse( + readFileSync(path.join(root, "package.json"), "utf8"), + ); + const packed = JSON.parse( + execute(npm, ["pack", root, "--json", "--ignore-scripts"], "pack").stdout, + )[0]; + assert.ok( + packed.files.every( + (f) => !f.path.startsWith("tests/") && !f.path.startsWith("src/"), + ), + ); + writeFileSync( + path.join(consumer, "package.json"), + JSON.stringify({ private: true, overrides: pkg.overrides }), + ); + execute( + npm, + [ + "install", + "--ignore-scripts", + "--no-audit", + "--no-fund", + "--save-exact", + `./${packed.filename}`, + `codeceptjs@${pkg.devDependencies.codeceptjs}`, + ], + "install", + ); + cpSync( + new URL("./fixtures/", import.meta.url), + path.join(consumer, "fixtures"), + { recursive: true }, + ); + const samples = []; + const failedEmissions = []; + const scenarios = [ + { name: "actor-step", actor: true, passed: true, exit: undefined }, + { + name: "actor-minimal", + actor: true, + options: { minimal: true }, + passed: true, + exit: 0, + }, + { name: "full", exit: 1 }, + { name: "repeated", exit: 1 }, + { name: "esm", esm: true, exit: 1 }, + { name: "minimal", options: { minimal: true }, exit: 1 }, + { name: "defaults", defaults: true, passed: true, exit: 0 }, + { + name: "zero-build-number", + options: { buildNumber: 0 }, + passed: true, + exit: 0, + }, + { name: "empty-run-id", options: { runId: "" }, passed: true, exit: 0 }, + { + name: "custom-identity", + shard: 1, + options: { customIdentity: true }, + passed: true, + exit: 0, + }, + { + name: "shard-one", + shard: 0, + options: { shardId: "one" }, + passed: true, + exit: 0, + }, + { + name: "shard-two", + shard: 1, + options: { shardId: "two" }, + passed: true, + exit: 0, + }, + { name: "workers", workers: true, passed: true, exit: 0 }, + ]; + for (const scenario of scenarios) { + const output = path.join(consumer, scenario.name); + mkdirSync(output); + const options = { + ...(scenario.defaults ? {} : metadata), + ...scenario.options, + }; + env.CTRF_ACTOR_STEP = String(scenario.actor ?? false); + env.CTRF_FIXTURE_PASSED = String(scenario.passed ?? false); + env.CTRF_FIXTURE_CONFIG = JSON.stringify({ + ...scenario, + output, + options, + }); + const args = [ + "node_modules/codeceptjs/bin/codecept.js", + ...(scenario.workers ? ["run-workers", "2", "--by", "suite"] : ["run"]), + "--config", + "fixtures/config.cjs", + ]; + const runWindow = [Date.now()]; + const outcome = execute( + process.execPath, + args, + scenario.name, + scenario.exit, + ); + runWindow.push(Date.now()); + const nativeFiles = readdirSync(output).filter((f) => + /^native-\d+\.json$/u.test(f), + ); + assert.ok(nativeFiles.length, "Native observer did not finish"); + const workerThreads = []; + const nativeCases = []; + for (const nativeFile of nativeFiles) { + const native = JSON.parse( + readFileSync(path.join(output, nativeFile), "utf8"), + ); + const reportFile = scenario.workers + ? `report-${native.threadId}.json` + : "report.json"; + const fileExists = existsSync(path.join(output, reportFile)); + const bytes = fileExists + ? readFileSync(path.join(output, reportFile)) + : undefined; + if (native.threadId > 0 && native.started > 0) + workerThreads.push(native.threadId); + const grouped = new Map(); + for (const a of native.attempts) { + const key = JSON.stringify([a.suite, a.title]); + if (!grouped.has(key)) grouped.set(key, []); + grouped.get(key).push(a); + } + nativeCases.push( + ...[...grouped.values()].map((attempts) => attempts.at(-1)), + ); + for (const attempts of grouped.values()) { + const last = attempts.at(-1); + assert.equal( + last.status, + last.title === "skipped" + ? "skipped" + : last.title === "retry exhausted" && !scenario.passed + ? "failed" + : "passed", + `${scenario.name}: independent final outcome ${last.title}`, + ); + if (!scenario.passed && last.title.includes("retry")) { + assert.equal( + attempts.length, + last.title === "retry recovery" ? 3 : 2, + ); + assert.deepEqual( + attempts.map((a) => a.attempt), + Array.from({ length: attempts.length }, (_, i) => i), + ); + } + } + const name = scenario.workers + ? `workers-thread-${native.threadId}` + : scenario.name; + if (diagnostics) { + cpSync( + path.join(output, nativeFile), + path.join(diagnostics, `${name}-native.json`), + ); + if (fileExists) + cpSync( + path.join(output, reportFile), + path.join(diagnostics, `${name}-report.json`), + ); + } + if (!fileExists) { + assert.equal( + scenario.name, + "actor-step", + "Unexpected missing report", + ); + assert.ok([0, 1].includes(outcome.status)); + const text = outcome.stdout + outcome.stderr; + const match = text.match( + /ValidationError: (CTRF validation failed:\n\/results\/tests\/0\/steps\/0: must have required property 'name')/u, + ); + failedEmissions.push({ + name: "actor-step", + emissionFailure: { + message: match?.[1] ?? text, + errorType: match + ? "ValidationError" + : "UnexpectedEmissionFailure", + absentOutput: true, + nativeSteps: native.attempts.flatMap((a) => + (a.steps ?? []).map((step) => ({ + title: step.title, + helperMethod: step.helperMethod, + name: step.rawName, + status: step.status, + })), + ), + }, + }); + continue; + } + samples.push({ + name, + base64: bytes.toString("base64"), + report: JSON.parse(bytes), + expected: expectedReport(native, options), + native, + options, + runWindow, + }); + } + const wanted = + scenario.shard === 1 + ? ["same"] + : scenario.shard === 0 + ? [ + "passing ✓ @smoke", + "skipped", + "retry recovery", + "retry exhausted", + "same", + ] + : [ + "passing ✓ @smoke", + "skipped", + "retry recovery", + "retry exhausted", + "same", + "same", + ]; + assert.deepEqual( + nativeCases.map((t) => t.title).sort(), + wanted.sort(), + `${scenario.name}: complete native inventory`, + ); + if (scenario.workers) + assert.equal( + new Set(workerThreads).size, + 2, + "Two real worker threads must run", + ); + if (scenario.actor) + assert.ok( + nativeCases.some((t) => + t.steps?.some((step) => step.helperMethod === "check"), + ), + "A real actor step must run", + ); + } + const full = samples.find((s) => s.name === "full"); + checkEntry( + catalogue.entries.find((e) => e.rule === "single-json-object"), + { + samples: [{ ...full, bytes: Buffer.from(full.base64, "base64") }], + warnings: [], + }, + ); + for (const [rule, mutate] of [ + [ + "single-json-object", + (r) => { + r.results.tests[0].duration = "invalid"; + }, + ], + [ + "summary-matches-final-results", + (r) => { + r.results.summary.passed++; + }, + ], + [ + "retry-count-matches-history-length", + (r) => { + r.results.tests.find((t) => t.retries > 0).retries++; + }, + ], + [ + "declares-supported-contract", + (r) => { + r.specVersion = "9.9.9"; + }, + ], + ]) { + const report = structuredClone(full.report); + mutate(report); + assert.throws(() => + checkEntry( + catalogue.entries.find((e) => e.rule === rule), + { + samples: [ + { + name: "corrupted installed output", + report, + bytes: Buffer.from(JSON.stringify(report)), + }, + ], + warnings: [], + }, + ), + ); + } + return { + samples, + failedEmissions, + repeated: ["full", "repeated"], + shards: ["shard-one", "shard-two"], + warnings: [], + }; + } finally { + rmSync(consumer, { recursive: true, force: true }); + } +} diff --git a/tests/standard/conformance/reporters/ctrf.mjs b/tests/standard/conformance/reporters/ctrf.mjs new file mode 100644 index 0000000..18838a2 --- /dev/null +++ b/tests/standard/conformance/reporters/ctrf.mjs @@ -0,0 +1,106 @@ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; +import { mkdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { getSchema } from "ctrf"; +import { expectedFailure } from "../expected-failures.mjs"; + +const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }); +addFormats(ajv); +const validate = ajv.compile(getSchema("0.2.0")); + +export function reportPath(level) { + const base = process.env.CTRF_STANDARD_ARTIFACTS ?? "ctrf"; + if (level === "checker") + return path.resolve(base, "checker-reports", "shared-checker.json"); + if (level === "tooling") return path.resolve(base, "reporter-tooling.json"); + return path.resolve(base, "reports", `conformance-${level}.json`); +} + +export function createReport(records, level, tool, start, stop = Date.now()) { + const tests = records.map((record) => { + assert.ok( + Number.isFinite(record.duration ?? 0) && (record.duration ?? 0) >= 0, + "Runner duration must be finite and nonnegative", + ); + const id = record.name.split(" ")[0]; + const issue = expectedFailure({ id }, level); + const observed = + record.status === "passed" && + issue && + record.name.endsWith(`[expected failure: ${issue.issue}]`); + return { + name: `${level} / ${record.name}`, + testId: `${level}:${record.name}`, + status: observed ? "other" : record.status, + duration: Math.round(record.duration ?? 0), + rawStatus: observed ? "expected-failure" : record.status, + type: level, + suite: + level === "checker" + ? ["Shared checker controls", id.split("/")[1] ?? "setup"] + : level === "tooling" + ? ["Reporter-specific tooling"] + : [ + level === "unit" + ? "Unit conformance" + : "Integration conformance", + observed + ? "Expected failures (tracked defects)" + : id.startsWith("conformance/property/") + ? "Schema properties" + : id.startsWith("conformance/report/") + ? "Specification relationships" + : "Runner/setup failures", + ], + ...(record.filePath ? { filePath: record.filePath } : {}), + ...(record.message ? { message: record.message } : {}), + ...(record.trace ? { trace: record.trace } : {}), + ...(observed + ? { + message: `Expected failure: ${issue.reason}. Follow-up: ${issue.followUp}`, + extra: { + expectedFailure: { + issue: issue.issue, + reason: issue.reason, + followUp: issue.followUp, + }, + runnerStatus: "passed", + }, + } + : {}), + }; + }); + const summary = { tests: tests.length, start, stop }; + for (const status of ["passed", "failed", "skipped", "pending", "other"]) + summary[status] = tests.filter((test) => test.status === status).length; + const report = { + reportFormat: "CTRF", + specVersion: "0.2.0", + reportId: randomUUID(), + timestamp: new Date(stop).toISOString(), + generatedBy: "ctrf-standard-tests", + results: { + tool: { name: tool }, + summary, + tests, + extra: { + expectedFailures: tests.filter((test) => test.extra?.expectedFailure) + .length, + }, + }, + }; + assert.ok(validate(report), ajv.errorsText(validate.errors)); + return report; +} + +export function writeReport(records, level, tool, start) { + const output = reportPath(level); + mkdirSync(path.dirname(output), { recursive: true }); + writeFileSync( + output, + JSON.stringify(createReport(records, level, tool, start), null, 2), + ); +} diff --git a/tests/standard/conformance/reporters/node.mjs b/tests/standard/conformance/reporters/node.mjs new file mode 100644 index 0000000..566e42b --- /dev/null +++ b/tests/standard/conformance/reporters/node.mjs @@ -0,0 +1,38 @@ +import { writeReport } from "./ctrf.mjs"; + +export default async function* conformanceCtrfReporter(events) { + const start = Date.now(); + const records = []; + for await (const { type, data } of events) { + if (type !== "test:pass" && type !== "test:fail") continue; + if ( + data.details.type === "suite" || + data.details.error?.failureType === "subtestsFailed" + ) + continue; + records.push({ + name: data.name, + status: data.todo + ? "pending" + : data.skip + ? "skipped" + : type === "test:fail" + ? "failed" + : "passed", + duration: data.details.duration_ms, + filePath: data.file, + message: + data.details.error?.cause?.message ?? + data.details.error?.message ?? + (typeof data.skip === "string" ? data.skip : undefined), + trace: data.details.error?.cause?.stack ?? data.details.error?.stack, + }); + } + writeReport( + records, + process.env.CTRF_CONFORMANCE_LEVEL ?? "integration", + "node:test", + start, + ); + yield ""; +} diff --git a/tests/standard/conformance/run-all.mjs b/tests/standard/conformance/run-all.mjs new file mode 100644 index 0000000..7c90614 --- /dev/null +++ b/tests/standard/conformance/run-all.mjs @@ -0,0 +1,12 @@ +import { spawnSync } from "node:child_process"; + +const npm = process.platform === "win32" ? "npm.cmd" : "npm"; +let failed = false; +for (const command of [ + "test:standard:conformance:unit", + "test:standard:conformance:integration", +]) { + const result = spawnSync(npm, ["run", command], { stdio: "inherit" }); + if (result.error || result.status !== 0) failed = true; +} +process.exitCode = failed ? 1 : 0; diff --git a/tests/standard/conformance/run.mjs b/tests/standard/conformance/run.mjs new file mode 100644 index 0000000..ff71dd0 --- /dev/null +++ b/tests/standard/conformance/run.mjs @@ -0,0 +1,70 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { + existsSync, + mkdtempSync, + mkdirSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { integrationContext } from "./integration/prepare.mjs"; +import { reportPath, writeReport } from "./reporters/ctrf.mjs"; +const root = fileURLToPath(new URL("../../../", import.meta.url)); +const level = process.argv[2]; +assert.ok(["unit", "integration"].includes(level)); +const scratch = mkdtempSync( + path.join(tmpdir(), "ctrf-codeceptjs-conformance-"), +); +const start = Date.now(); +rmSync(reportPath(level), { force: true }); +try { + const env = { ...process.env, CTRF_CONFORMANCE_LEVEL: level }; + if (level === "integration") { + const diagnostics = process.env.CTRF_STANDARD_ARTIFACTS; + if (diagnostics) mkdirSync(diagnostics, { recursive: true }); + env.CTRF_CONFORMANCE_CONTEXT = path.join(scratch, "context.json"); + writeFileSync( + env.CTRF_CONFORMANCE_CONTEXT, + JSON.stringify(integrationContext(root, diagnostics)), + ); + } + const result = spawnSync( + process.execPath, + [ + "--test", + "--test-reporter=spec", + "--test-reporter-destination=stdout", + "--test-reporter=./tests/standard/conformance/reporters/node.mjs", + "--test-reporter-destination=stdout", + `tests/standard/conformance/${level}/conformance.test.mjs`, + ], + { cwd: root, env, stdio: "inherit", timeout: 300_000 }, + ); + assert.ifError(result.error); + assert.equal(result.signal, null); + assert.ok( + existsSync(reportPath(level)), + "Native runner did not emit its CTRF result", + ); + process.exitCode = result.status; +} catch (error) { + writeReport( + [ + { + name: `${level} setup`, + status: "failed", + message: error.message, + trace: error.stack, + }, + ], + level, + "node:test", + start, + ); + throw error; +} finally { + rmSync(scratch, { recursive: true, force: true }); +} diff --git a/tests/standard/conformance/shared/catalogue.json b/tests/standard/conformance/shared/catalogue.json new file mode 100644 index 0000000..75d5c6e --- /dev/null +++ b/tests/standard/conformance/shared/catalogue.json @@ -0,0 +1,1096 @@ +{ + "version": 1, + "specVersion": "0.2.0", + "entries": [ + { + "id": "conformance/property/reportFormat", + "kind": "property", + "path": "reportFormat", + "required": true + }, + { + "id": "conformance/property/specVersion", + "kind": "property", + "path": "specVersion", + "required": true + }, + { + "id": "conformance/property/reportId", + "kind": "property", + "path": "reportId", + "required": false + }, + { + "id": "conformance/property/runId", + "kind": "property", + "path": "runId", + "required": false + }, + { + "id": "conformance/property/timestamp", + "kind": "property", + "path": "timestamp", + "required": false + }, + { + "id": "conformance/property/generatedBy", + "kind": "property", + "path": "generatedBy", + "required": false + }, + { + "id": "conformance/property/extra", + "kind": "property", + "path": "extra", + "required": false + }, + { + "id": "conformance/property/results", + "kind": "property", + "path": "results", + "required": true + }, + { + "id": "conformance/property/results.tool", + "kind": "property", + "path": "results.tool", + "required": true + }, + { + "id": "conformance/property/results.tool.name", + "kind": "property", + "path": "results.tool.name", + "required": true + }, + { + "id": "conformance/property/results.tool.version", + "kind": "property", + "path": "results.tool.version", + "required": false + }, + { + "id": "conformance/property/results.tool.extra", + "kind": "property", + "path": "results.tool.extra", + "required": false + }, + { + "id": "conformance/property/results.summary", + "kind": "property", + "path": "results.summary", + "required": true + }, + { + "id": "conformance/property/results.summary.tests", + "kind": "property", + "path": "results.summary.tests", + "required": true + }, + { + "id": "conformance/property/results.summary.passed", + "kind": "property", + "path": "results.summary.passed", + "required": true + }, + { + "id": "conformance/property/results.summary.failed", + "kind": "property", + "path": "results.summary.failed", + "required": true + }, + { + "id": "conformance/property/results.summary.skipped", + "kind": "property", + "path": "results.summary.skipped", + "required": true + }, + { + "id": "conformance/property/results.summary.pending", + "kind": "property", + "path": "results.summary.pending", + "required": true + }, + { + "id": "conformance/property/results.summary.other", + "kind": "property", + "path": "results.summary.other", + "required": true + }, + { + "id": "conformance/property/results.summary.flaky", + "kind": "property", + "path": "results.summary.flaky", + "required": false + }, + { + "id": "conformance/property/results.summary.suites", + "kind": "property", + "path": "results.summary.suites", + "required": false + }, + { + "id": "conformance/property/results.summary.start", + "kind": "property", + "path": "results.summary.start", + "required": true + }, + { + "id": "conformance/property/results.summary.stop", + "kind": "property", + "path": "results.summary.stop", + "required": true + }, + { + "id": "conformance/property/results.summary.duration", + "kind": "property", + "path": "results.summary.duration", + "required": false + }, + { + "id": "conformance/property/results.summary.extra", + "kind": "property", + "path": "results.summary.extra", + "required": false + }, + { + "id": "conformance/property/results.tests", + "kind": "property", + "path": "results.tests", + "required": true + }, + { + "id": "conformance/property/results.tests[].id", + "kind": "property", + "path": "results.tests[].id", + "required": false + }, + { + "id": "conformance/property/results.tests[].testId", + "kind": "property", + "path": "results.tests[].testId", + "required": false + }, + { + "id": "conformance/property/results.tests[].executionId", + "kind": "property", + "path": "results.tests[].executionId", + "required": false + }, + { + "id": "conformance/property/results.tests[].attemptId", + "kind": "property", + "path": "results.tests[].attemptId", + "required": false + }, + { + "id": "conformance/property/results.tests[].name", + "kind": "property", + "path": "results.tests[].name", + "required": true + }, + { + "id": "conformance/property/results.tests[].status", + "kind": "property", + "path": "results.tests[].status", + "required": true + }, + { + "id": "conformance/property/results.tests[].duration", + "kind": "property", + "path": "results.tests[].duration", + "required": true + }, + { + "id": "conformance/property/results.tests[].start", + "kind": "property", + "path": "results.tests[].start", + "required": false + }, + { + "id": "conformance/property/results.tests[].stop", + "kind": "property", + "path": "results.tests[].stop", + "required": false + }, + { + "id": "conformance/property/results.tests[].suite", + "kind": "property", + "path": "results.tests[].suite", + "required": false + }, + { + "id": "conformance/property/results.tests[].message", + "kind": "property", + "path": "results.tests[].message", + "required": false + }, + { + "id": "conformance/property/results.tests[].trace", + "kind": "property", + "path": "results.tests[].trace", + "required": false + }, + { + "id": "conformance/property/results.tests[].snippet", + "kind": "property", + "path": "results.tests[].snippet", + "required": false + }, + { + "id": "conformance/property/results.tests[].ai", + "kind": "property", + "path": "results.tests[].ai", + "required": false + }, + { + "id": "conformance/property/results.tests[].line", + "kind": "property", + "path": "results.tests[].line", + "required": false + }, + { + "id": "conformance/property/results.tests[].rawStatus", + "kind": "property", + "path": "results.tests[].rawStatus", + "required": false + }, + { + "id": "conformance/property/results.tests[].tags", + "kind": "property", + "path": "results.tests[].tags", + "required": false + }, + { + "id": "conformance/property/results.tests[].labels", + "kind": "property", + "path": "results.tests[].labels", + "required": false + }, + { + "id": "conformance/property/results.tests[].type", + "kind": "property", + "path": "results.tests[].type", + "required": false + }, + { + "id": "conformance/property/results.tests[].filePath", + "kind": "property", + "path": "results.tests[].filePath", + "required": false + }, + { + "id": "conformance/property/results.tests[].retries", + "kind": "property", + "path": "results.tests[].retries", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts", + "kind": "property", + "path": "results.tests[].retryAttempts", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attempt", + "kind": "property", + "path": "results.tests[].retryAttempts[].attempt", + "required": true + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attemptId", + "kind": "property", + "path": "results.tests[].retryAttempts[].attemptId", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].status", + "kind": "property", + "path": "results.tests[].retryAttempts[].status", + "required": true + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].duration", + "kind": "property", + "path": "results.tests[].retryAttempts[].duration", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].message", + "kind": "property", + "path": "results.tests[].retryAttempts[].message", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].trace", + "kind": "property", + "path": "results.tests[].retryAttempts[].trace", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].line", + "kind": "property", + "path": "results.tests[].retryAttempts[].line", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].snippet", + "kind": "property", + "path": "results.tests[].retryAttempts[].snippet", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].stdout", + "kind": "property", + "path": "results.tests[].retryAttempts[].stdout", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].stderr", + "kind": "property", + "path": "results.tests[].retryAttempts[].stderr", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].start", + "kind": "property", + "path": "results.tests[].retryAttempts[].start", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].stop", + "kind": "property", + "path": "results.tests[].retryAttempts[].stop", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments[].attachmentId", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments[].attachmentId", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments[].name", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments[].name", + "required": true + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments[].contentType", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments[].contentType", + "required": true + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments[].path", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments[].path", + "required": true + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].attachments[].extra", + "kind": "property", + "path": "results.tests[].retryAttempts[].attachments[].extra", + "required": false + }, + { + "id": "conformance/property/results.tests[].retryAttempts[].extra", + "kind": "property", + "path": "results.tests[].retryAttempts[].extra", + "required": false + }, + { + "id": "conformance/property/results.tests[].flaky", + "kind": "property", + "path": "results.tests[].flaky", + "required": false + }, + { + "id": "conformance/property/results.tests[].stdout", + "kind": "property", + "path": "results.tests[].stdout", + "required": false + }, + { + "id": "conformance/property/results.tests[].stderr", + "kind": "property", + "path": "results.tests[].stderr", + "required": false + }, + { + "id": "conformance/property/results.tests[].threadId", + "kind": "property", + "path": "results.tests[].threadId", + "required": false + }, + { + "id": "conformance/property/results.tests[].browser", + "kind": "property", + "path": "results.tests[].browser", + "required": false + }, + { + "id": "conformance/property/results.tests[].device", + "kind": "property", + "path": "results.tests[].device", + "required": false + }, + { + "id": "conformance/property/results.tests[].screenshot", + "kind": "property", + "path": "results.tests[].screenshot", + "required": false + }, + { + "id": "conformance/property/results.tests[].attachments", + "kind": "property", + "path": "results.tests[].attachments", + "required": false + }, + { + "id": "conformance/property/results.tests[].attachments[].attachmentId", + "kind": "property", + "path": "results.tests[].attachments[].attachmentId", + "required": false + }, + { + "id": "conformance/property/results.tests[].attachments[].name", + "kind": "property", + "path": "results.tests[].attachments[].name", + "required": true + }, + { + "id": "conformance/property/results.tests[].attachments[].contentType", + "kind": "property", + "path": "results.tests[].attachments[].contentType", + "required": true + }, + { + "id": "conformance/property/results.tests[].attachments[].path", + "kind": "property", + "path": "results.tests[].attachments[].path", + "required": true + }, + { + "id": "conformance/property/results.tests[].attachments[].extra", + "kind": "property", + "path": "results.tests[].attachments[].extra", + "required": false + }, + { + "id": "conformance/property/results.tests[].parameters", + "kind": "property", + "path": "results.tests[].parameters", + "required": false + }, + { + "id": "conformance/property/results.tests[].steps", + "kind": "property", + "path": "results.tests[].steps", + "required": false + }, + { + "id": "conformance/property/results.tests[].steps[].name", + "kind": "property", + "path": "results.tests[].steps[].name", + "required": true + }, + { + "id": "conformance/property/results.tests[].steps[].status", + "kind": "property", + "path": "results.tests[].steps[].status", + "required": true + }, + { + "id": "conformance/property/results.tests[].steps[].extra", + "kind": "property", + "path": "results.tests[].steps[].extra", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights", + "kind": "property", + "path": "results.tests[].insights", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.passRate", + "kind": "property", + "path": "results.tests[].insights.passRate", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.passRate.current", + "kind": "property", + "path": "results.tests[].insights.passRate.current", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.passRate.baseline", + "kind": "property", + "path": "results.tests[].insights.passRate.baseline", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.passRate.change", + "kind": "property", + "path": "results.tests[].insights.passRate.change", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.failRate", + "kind": "property", + "path": "results.tests[].insights.failRate", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.failRate.current", + "kind": "property", + "path": "results.tests[].insights.failRate.current", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.failRate.baseline", + "kind": "property", + "path": "results.tests[].insights.failRate.baseline", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.failRate.change", + "kind": "property", + "path": "results.tests[].insights.failRate.change", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.flakyRate", + "kind": "property", + "path": "results.tests[].insights.flakyRate", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.flakyRate.current", + "kind": "property", + "path": "results.tests[].insights.flakyRate.current", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.flakyRate.baseline", + "kind": "property", + "path": "results.tests[].insights.flakyRate.baseline", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.flakyRate.change", + "kind": "property", + "path": "results.tests[].insights.flakyRate.change", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.averageTestDuration", + "kind": "property", + "path": "results.tests[].insights.averageTestDuration", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.averageTestDuration.current", + "kind": "property", + "path": "results.tests[].insights.averageTestDuration.current", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.averageTestDuration.baseline", + "kind": "property", + "path": "results.tests[].insights.averageTestDuration.baseline", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.averageTestDuration.change", + "kind": "property", + "path": "results.tests[].insights.averageTestDuration.change", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.p95TestDuration", + "kind": "property", + "path": "results.tests[].insights.p95TestDuration", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.p95TestDuration.current", + "kind": "property", + "path": "results.tests[].insights.p95TestDuration.current", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.p95TestDuration.baseline", + "kind": "property", + "path": "results.tests[].insights.p95TestDuration.baseline", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.p95TestDuration.change", + "kind": "property", + "path": "results.tests[].insights.p95TestDuration.change", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.executedInRuns", + "kind": "property", + "path": "results.tests[].insights.executedInRuns", + "required": false + }, + { + "id": "conformance/property/results.tests[].insights.extra", + "kind": "property", + "path": "results.tests[].insights.extra", + "required": false + }, + { + "id": "conformance/property/results.tests[].extra", + "kind": "property", + "path": "results.tests[].extra", + "required": false + }, + { + "id": "conformance/property/results.environment", + "kind": "property", + "path": "results.environment", + "required": false + }, + { + "id": "conformance/property/results.environment.reportName", + "kind": "property", + "path": "results.environment.reportName", + "required": false + }, + { + "id": "conformance/property/results.environment.appName", + "kind": "property", + "path": "results.environment.appName", + "required": false + }, + { + "id": "conformance/property/results.environment.appVersion", + "kind": "property", + "path": "results.environment.appVersion", + "required": false + }, + { + "id": "conformance/property/results.environment.buildId", + "kind": "property", + "path": "results.environment.buildId", + "required": false + }, + { + "id": "conformance/property/results.environment.buildName", + "kind": "property", + "path": "results.environment.buildName", + "required": false + }, + { + "id": "conformance/property/results.environment.buildNumber", + "kind": "property", + "path": "results.environment.buildNumber", + "required": false + }, + { + "id": "conformance/property/results.environment.buildUrl", + "kind": "property", + "path": "results.environment.buildUrl", + "required": false + }, + { + "id": "conformance/property/results.environment.repositoryName", + "kind": "property", + "path": "results.environment.repositoryName", + "required": false + }, + { + "id": "conformance/property/results.environment.repositoryUrl", + "kind": "property", + "path": "results.environment.repositoryUrl", + "required": false + }, + { + "id": "conformance/property/results.environment.commit", + "kind": "property", + "path": "results.environment.commit", + "required": false + }, + { + "id": "conformance/property/results.environment.branchName", + "kind": "property", + "path": "results.environment.branchName", + "required": false + }, + { + "id": "conformance/property/results.environment.osPlatform", + "kind": "property", + "path": "results.environment.osPlatform", + "required": false + }, + { + "id": "conformance/property/results.environment.osRelease", + "kind": "property", + "path": "results.environment.osRelease", + "required": false + }, + { + "id": "conformance/property/results.environment.osVersion", + "kind": "property", + "path": "results.environment.osVersion", + "required": false + }, + { + "id": "conformance/property/results.environment.testEnvironment", + "kind": "property", + "path": "results.environment.testEnvironment", + "required": false + }, + { + "id": "conformance/property/results.environment.shardId", + "kind": "property", + "path": "results.environment.shardId", + "required": false + }, + { + "id": "conformance/property/results.environment.healthy", + "kind": "property", + "path": "results.environment.healthy", + "required": false + }, + { + "id": "conformance/property/results.environment.extra", + "kind": "property", + "path": "results.environment.extra", + "required": false + }, + { + "id": "conformance/property/results.extra", + "kind": "property", + "path": "results.extra", + "required": false + }, + { + "id": "conformance/property/insights", + "kind": "property", + "path": "insights", + "required": false + }, + { + "id": "conformance/property/insights.passRate", + "kind": "property", + "path": "insights.passRate", + "required": false + }, + { + "id": "conformance/property/insights.passRate.current", + "kind": "property", + "path": "insights.passRate.current", + "required": false + }, + { + "id": "conformance/property/insights.passRate.baseline", + "kind": "property", + "path": "insights.passRate.baseline", + "required": false + }, + { + "id": "conformance/property/insights.passRate.change", + "kind": "property", + "path": "insights.passRate.change", + "required": false + }, + { + "id": "conformance/property/insights.failRate", + "kind": "property", + "path": "insights.failRate", + "required": false + }, + { + "id": "conformance/property/insights.failRate.current", + "kind": "property", + "path": "insights.failRate.current", + "required": false + }, + { + "id": "conformance/property/insights.failRate.baseline", + "kind": "property", + "path": "insights.failRate.baseline", + "required": false + }, + { + "id": "conformance/property/insights.failRate.change", + "kind": "property", + "path": "insights.failRate.change", + "required": false + }, + { + "id": "conformance/property/insights.flakyRate", + "kind": "property", + "path": "insights.flakyRate", + "required": false + }, + { + "id": "conformance/property/insights.flakyRate.current", + "kind": "property", + "path": "insights.flakyRate.current", + "required": false + }, + { + "id": "conformance/property/insights.flakyRate.baseline", + "kind": "property", + "path": "insights.flakyRate.baseline", + "required": false + }, + { + "id": "conformance/property/insights.flakyRate.change", + "kind": "property", + "path": "insights.flakyRate.change", + "required": false + }, + { + "id": "conformance/property/insights.averageRunDuration", + "kind": "property", + "path": "insights.averageRunDuration", + "required": false + }, + { + "id": "conformance/property/insights.averageRunDuration.current", + "kind": "property", + "path": "insights.averageRunDuration.current", + "required": false + }, + { + "id": "conformance/property/insights.averageRunDuration.baseline", + "kind": "property", + "path": "insights.averageRunDuration.baseline", + "required": false + }, + { + "id": "conformance/property/insights.averageRunDuration.change", + "kind": "property", + "path": "insights.averageRunDuration.change", + "required": false + }, + { + "id": "conformance/property/insights.p95RunDuration", + "kind": "property", + "path": "insights.p95RunDuration", + "required": false + }, + { + "id": "conformance/property/insights.p95RunDuration.current", + "kind": "property", + "path": "insights.p95RunDuration.current", + "required": false + }, + { + "id": "conformance/property/insights.p95RunDuration.baseline", + "kind": "property", + "path": "insights.p95RunDuration.baseline", + "required": false + }, + { + "id": "conformance/property/insights.p95RunDuration.change", + "kind": "property", + "path": "insights.p95RunDuration.change", + "required": false + }, + { + "id": "conformance/property/insights.averageTestDuration", + "kind": "property", + "path": "insights.averageTestDuration", + "required": false + }, + { + "id": "conformance/property/insights.averageTestDuration.current", + "kind": "property", + "path": "insights.averageTestDuration.current", + "required": false + }, + { + "id": "conformance/property/insights.averageTestDuration.baseline", + "kind": "property", + "path": "insights.averageTestDuration.baseline", + "required": false + }, + { + "id": "conformance/property/insights.averageTestDuration.change", + "kind": "property", + "path": "insights.averageTestDuration.change", + "required": false + }, + { + "id": "conformance/property/insights.runsAnalyzed", + "kind": "property", + "path": "insights.runsAnalyzed", + "required": false + }, + { + "id": "conformance/property/insights.extra", + "kind": "property", + "path": "insights.extra", + "required": false + }, + { + "id": "conformance/property/baseline", + "kind": "property", + "path": "baseline", + "required": false + }, + { + "id": "conformance/property/baseline.reportId", + "kind": "property", + "path": "baseline.reportId", + "required": true + }, + { + "id": "conformance/property/baseline.timestamp", + "kind": "property", + "path": "baseline.timestamp", + "required": false + }, + { + "id": "conformance/property/baseline.source", + "kind": "property", + "path": "baseline.source", + "required": false + }, + { + "id": "conformance/property/baseline.buildNumber", + "kind": "property", + "path": "baseline.buildNumber", + "required": false + }, + { + "id": "conformance/property/baseline.buildName", + "kind": "property", + "path": "baseline.buildName", + "required": false + }, + { + "id": "conformance/property/baseline.buildUrl", + "kind": "property", + "path": "baseline.buildUrl", + "required": false + }, + { + "id": "conformance/property/baseline.commit", + "kind": "property", + "path": "baseline.commit", + "required": false + }, + { + "id": "conformance/property/baseline.extra", + "kind": "property", + "path": "baseline.extra", + "required": false + }, + { + "id": "conformance/report/utf8-without-bom", + "kind": "report", + "rule": "utf8-without-bom" + }, + { + "id": "conformance/report/single-json-object", + "kind": "report", + "rule": "single-json-object" + }, + { + "id": "conformance/report/declares-supported-contract", + "kind": "report", + "rule": "declares-supported-contract" + }, + { + "id": "conformance/report/summary-matches-final-results", + "kind": "report", + "rule": "summary-matches-final-results" + }, + { + "id": "conformance/report/summary-matches-flaky-results", + "kind": "report", + "rule": "summary-matches-flaky-results" + }, + { + "id": "conformance/report/orders-start-and-stop", + "kind": "report", + "rule": "orders-start-and-stop" + }, + { + "id": "conformance/report/history-requires-retry-count", + "kind": "report", + "rule": "history-requires-retry-count" + }, + { + "id": "conformance/report/positive-retries-require-history", + "kind": "report", + "rule": "positive-retries-require-history" + }, + { + "id": "conformance/report/zero-retries-omit-history", + "kind": "report", + "rule": "zero-retries-omit-history" + }, + { + "id": "conformance/report/retry-count-matches-history-length", + "kind": "report", + "rule": "retry-count-matches-history-length" + }, + { + "id": "conformance/report/history-orders-contiguous-attempts", + "kind": "report", + "rule": "history-orders-contiguous-attempts" + }, + { + "id": "conformance/report/history-excludes-final-attempt", + "kind": "report", + "rule": "history-excludes-final-attempt" + }, + { + "id": "conformance/report/flaky-matches-attempt-outcomes", + "kind": "report", + "rule": "flaky-matches-attempt-outcomes" + }, + { + "id": "conformance/report/case-identity-stable-across-runs", + "kind": "report", + "rule": "case-identity-stable-across-runs" + }, + { + "id": "conformance/report/execution-identity-distinguishes-executions", + "kind": "report", + "rule": "execution-identity-distinguishes-executions" + }, + { + "id": "conformance/report/attempt-identity-scoped-to-execution", + "kind": "report", + "rule": "attempt-identity-scoped-to-execution" + }, + { + "id": "conformance/report/attachment-identity-scoped-to-parent", + "kind": "report", + "rule": "attachment-identity-scoped-to-parent" + }, + { + "id": "conformance/report/document-identity-follows-emission", + "kind": "report", + "rule": "document-identity-follows-emission" + }, + { + "id": "conformance/report/coordinates-run-and-shard-identity", + "kind": "report", + "rule": "coordinates-run-and-shard-identity" + }, + { + "id": "conformance/report/respects-reserved-extension-ownership", + "kind": "report", + "rule": "respects-reserved-extension-ownership" + } + ] +} diff --git a/tests/standard/conformance/shared/checks.mjs b/tests/standard/conformance/shared/checks.mjs new file mode 100644 index 0000000..e040339 --- /dev/null +++ b/tests/standard/conformance/shared/checks.mjs @@ -0,0 +1,462 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { TextDecoder } from "node:util"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { getSchema } from "ctrf"; + +export const catalogue = JSON.parse( + readFileSync(new URL("./catalogue.json", import.meta.url), "utf8"), +); +const schema = getSchema(catalogue.specVersion); +const ajv = new Ajv({ allErrors: true, allowUnionTypes: true }); +addFormats(ajv); +const validateDocument = ajv.compile(schema); +const validators = new Map(); + +function resolve(node) { + if (!node.$ref) return node; + return node.$ref + .slice(2) + .split("/") + .reduce((value, key) => value[key], schema); +} + +function propertySchema(path) { + let node = schema; + for (const part of path.split(".")) { + const key = part.replace("[]", ""); + node = resolve(node).properties[key]; + if (part.endsWith("[]")) node = resolve(node).items; + } + return resolve(node); +} + +// Parent/array-item shape is checked here; child properties have their own +// named checks. Full-document validation still checks every nested constraint. +function localSchema(node) { + const value = structuredClone(resolve(node)); + if (value.properties) { + value.properties = Object.fromEntries( + Object.keys(value.properties).map((key) => [key, {}]), + ); + } + if (value.items?.properties) value.items = { type: "object" }; + return value; +} + +export function valuesAt(object, path) { + let values = [object]; + for (const part of path.split(".")) { + const key = part.replace("[]", ""); + values = values.flatMap((parent) => { + if (!parent || typeof parent !== "object" || !(key in parent)) return []; + const value = parent[key]; + return part.endsWith("[]") + ? Array.isArray(value) + ? value + : [] + : [value]; + }); + } + return values; +} + +function parentValues(report, path) { + const parts = path.split("."); + return { + key: parts.pop(), + parents: parts.length ? valuesAt(report, parts.join(".")) : [report], + }; +} + +export function checkProperty(entry, context) { + const node = propertySchema(entry.path); + if (!validators.has(entry.path)) { + validators.set(entry.path, ajv.compile(localSchema(node))); + } + const validate = validators.get(entry.path); + let witnessed = 0; + for (const sample of context.samples) { + const actual = valuesAt(sample.report, entry.path); + witnessed += actual.length; + const { key, parents } = parentValues(sample.report, entry.path); + if (entry.required) { + for (const parent of parents) { + assert.ok( + Object.hasOwn(parent, key), + `${sample.name}: missing ${entry.path}`, + ); + } + } + for (const value of actual) { + if ( + /^results\.summary\.(tests|passed|failed|skipped|pending|other|flaky|suites|duration)$/u.test( + entry.path, + ) || + entry.path.endsWith(".duration") || + entry.path.endsWith(".retries") + ) + assert.ok( + value >= 0, + `${sample.name}: ${entry.path} must be nonnegative`, + ); + assert.ok( + validate(value), + `${sample.name}: ${entry.path}: ${ajv.errorsText(validate.errors)}`, + ); + } + if ( + sample.runWindow && + ["timestamp", "results.summary.start", "results.summary.stop"].includes( + entry.path, + ) + ) { + for (const value of actual) { + const milliseconds = + entry.path === "timestamp" ? Date.parse(value) : value; + if (milliseconds === 0 && sample.report.results.tests.length === 0) + continue; + assert.ok( + milliseconds >= sample.runWindow[0] && + milliseconds <= sample.runWindow[1], + `${sample.name}: ${entry.path} outside independently observed run window; check units`, + ); + } + } + const expected = valuesAt(sample.expected ?? {}, entry.path); + // Source expectations are partial for objects with generated identity. + // Primitive values and arrays of primitives can be compared exactly. + if ( + entry.path !== "results.summary.tests" && + expected.length && + expected.every( + (value) => + value === null || + typeof value !== "object" || + (Array.isArray(value) && + value.every((item) => typeof item !== "object")), + ) + ) { + assert.deepEqual( + actual, + expected, + `${sample.name}: source mapping ${entry.path}`, + ); + } + if (entry.path.endsWith(".suite")) { + for (const suite of actual) assert.ok(suite.length > 0); + } + if (entry.path.endsWith(".screenshot")) { + for (const screenshot of actual) { + assert.match( + screenshot, + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u, + ); + const bytes = Buffer.from(screenshot, "base64"); + assert.ok(bytes.length > 0, "Screenshot must contain image bytes"); + assert.ok( + bytes + .subarray(0, 8) + .equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) || + bytes.subarray(0, 3).equals(Buffer.from([255, 216, 255])), + "Controlled screenshot fixture must be PNG or JPEG", + ); + } + } + } + assert.ok( + witnessed > 0, + `No positive evidence for supported property ${entry.path}`, + ); +} + +const tests = (context) => + context.samples.flatMap((sample) => sample.report.results.tests); +const histories = (context) => + tests(context).filter((test) => test.retryAttempts !== undefined); + +function unique(values, description) { + assert.equal(new Set(values).size, values.length, description); +} + +function sourcePairs(sample) { + const expected = sample.expected?.results?.tests ?? []; + return sample.report.results.tests.map((test, index) => [ + test, + expected[index], + ]); +} + +export const reportChecks = { + "utf8-without-bom": (context) => { + for (const sample of context.samples) { + assert.ok( + !sample.bytes.subarray(0, 3).equals(Buffer.from([239, 187, 191])), + sample.name, + ); + new TextDecoder("utf-8", { fatal: true }).decode(sample.bytes); + } + }, + "single-json-object": (context) => { + for (const sample of context.samples) { + const report = JSON.parse(sample.bytes.toString("utf8")); + assert.ok(report && !Array.isArray(report) && typeof report === "object"); + assert.ok( + validateDocument(report), + `${sample.name}: ${ajv.errorsText(validateDocument.errors)}`, + ); + } + }, + "declares-supported-contract": (context) => { + for (const { report } of context.samples) { + assert.equal(report.reportFormat, "CTRF"); + assert.equal(report.specVersion, catalogue.specVersion); + } + }, + "summary-matches-final-results": (context) => { + for (const { report } of context.samples) { + const { summary, tests: cases } = report.results; + if (summary.tests !== cases.length) { + context.warnings ??= []; + context.warnings.push( + "summary.tests differs from test array length (SHOULD, Core §8.1)", + ); + } + for (const status of [ + "passed", + "failed", + "skipped", + "pending", + "other", + ]) { + assert.equal( + summary[status], + cases.filter((test) => test.status === status).length, + ); + assert.ok(summary[status] >= 0); + } + } + }, + "summary-matches-flaky-results": (context) => { + let witnessed = false; + for (const { report } of context.samples) { + if (report.results.summary.flaky === undefined) continue; + witnessed = true; + assert.equal( + report.results.summary.flaky, + report.results.tests.filter((test) => test.flaky).length, + ); + } + assert.ok(witnessed, "No summary.flaky evidence"); + }, + "orders-start-and-stop": (context) => { + for (const sample of context.samples) { + for (const value of [ + sample.report.results.summary, + ...sample.report.results.tests, + ...sample.report.results.tests.flatMap( + (test) => test.retryAttempts ?? [], + ), + ]) { + if ( + value.start !== undefined && + value.stop !== undefined && + value.stop < value.start + ) { + context.warnings?.push( + `${sample.name}: stop precedes start (SHOULD)`, + ); + } + } + } + }, + "history-requires-retry-count": (context) => { + assert.ok(histories(context).length, "Missing retry fixture"); + for (const test of histories(context)) + assert.ok(Number.isInteger(test.retries)); + }, + "positive-retries-require-history": (context) => { + for (const test of tests(context)) + if (test.retries > 0) assert.ok(test.retryAttempts?.length > 0); + }, + "zero-retries-omit-history": (context) => { + for (const test of tests(context)) + if (test.retries === 0) assert.equal(test.retryAttempts, undefined); + }, + "retry-count-matches-history-length": (context) => { + for (const test of histories(context)) + assert.equal(test.retries, test.retryAttempts.length); + }, + "history-orders-contiguous-attempts": (context) => { + for (const test of histories(context)) + assert.deepEqual( + test.retryAttempts.map((attempt) => attempt.attempt), + Array.from({ length: test.retries }, (_, index) => index + 1), + ); + }, + "history-excludes-final-attempt": (context) => { + let witnessed = false; + for (const sample of context.samples) { + for (const [actual, source] of sourcePairs(sample)) { + if (!actual.retryAttempts || !source?.sourceAttempts) continue; + witnessed = true; + assert.equal( + actual.retryAttempts.length, + source.sourceAttempts.length - 1, + ); + assert.deepEqual( + actual.retryAttempts.map((attempt) => [ + attempt.status, + attempt.duration, + ]), + source.sourceAttempts + .slice(0, -1) + .map((attempt) => [attempt.status, attempt.duration]), + ); + assert.equal(actual.duration, source.sourceAttempts.at(-1).duration); + } + } + assert.ok(witnessed, "Missing independent attempt evidence"); + }, + "flaky-matches-attempt-outcomes": (context) => { + for (const sample of context.samples) { + for (const [actual, source] of sourcePairs(sample)) { + if (actual.flaky === undefined) continue; + const earlier = + source?.sourceAttempts?.slice(0, -1) ?? actual.retryAttempts ?? []; + assert.equal( + actual.flaky, + actual.status === "passed" && + earlier.some((attempt) => attempt.status === "failed"), + ); + } + } + }, + "case-identity-stable-across-runs": (context) => { + assert.ok( + context.repeated?.length >= 2, + "Missing independent repeated runs", + ); + const expected = context.repeated[0].report.results.tests.map((test) => [ + test.name, + test.testId, + ]); + for (const sample of context.repeated.slice(1)) + assert.deepEqual( + sample.report.results.tests.map((test) => [test.name, test.testId]), + expected, + ); + }, + "execution-identity-distinguishes-executions": (context) => { + const ids = tests(context) + .map((test) => test.executionId) + .filter((id) => id !== undefined); + assert.ok(ids.length); + unique(ids, "Execution IDs reused across independent emitted results"); + }, + "attempt-identity-scoped-to-execution": (context) => { + for (const test of tests(context)) + unique( + [ + test.attemptId, + ...(test.retryAttempts ?? []).map((attempt) => attempt.attemptId), + ].filter((id) => id !== undefined), + "Attempt ID reused within execution", + ); + }, + "attachment-identity-scoped-to-parent": (context) => { + for (const test of tests(context)) + for (const parent of [test, ...(test.retryAttempts ?? [])]) + unique( + (parent.attachments ?? []) + .map((attachment) => attachment.attachmentId) + .filter((id) => id !== undefined), + "Attachment ID reused within parent", + ); + }, + "document-identity-follows-emission": (context) => { + unique( + context.samples + .map((sample) => sample.report.reportId) + .filter((id) => id !== undefined), + "Independent emissions reused report identity", + ); + }, + "coordinates-run-and-shard-identity": (context) => { + assert.ok( + context.shards?.length >= 2, + "Missing coordinated shard evidence", + ); + const [first] = context.shards; + for (const sample of context.shards) + assert.equal(sample.report.runId, first.report.runId); + unique( + context.shards.map((sample) => sample.report.results.environment.shardId), + "Shard labels are not distinct", + ); + }, + "respects-reserved-extension-ownership": (context) => { + // These controlled fixtures declare which reserved extensions, if any, + // are CTRF-owned. Check actual output keys, not a list of expected keys. + for (const entry of catalogue.entries.filter( + (item) => + item.kind === "property" && + (item.path === "extra" || item.path.endsWith(".extra")), + )) { + for (const sample of context.samples) { + for (const extra of valuesAt(sample.report, entry.path)) { + for (const key of Object.keys(extra)) { + if (!key.startsWith("ctrf.") && !key.startsWith("ctrf/")) continue; + assert.ok( + context.ctrfDefinedExtensions?.includes(key), + `${sample.name}: fixture producer claimed reserved extension ${key}`, + ); + } + } + } + } + }, +}; + +const recommendedRules = new Set([ + "orders-start-and-stop", + "case-identity-stable-across-runs", + "execution-identity-distinguishes-executions", + "attempt-identity-scoped-to-execution", + "attachment-identity-scoped-to-parent", + "document-identity-follows-emission", + "coordinates-run-and-shard-identity", +]); + +export function checkEntry(entry, context) { + if (entry.kind === "property") checkProperty(entry, context); + else { + // Missing fixture evidence is not an allowed SHOULD deviation. + if (entry.rule === "case-identity-stable-across-runs") + assert.ok(context.repeated?.length >= 2, "Missing repeated-run evidence"); + if (entry.rule === "coordinates-run-and-shard-identity") + assert.ok(context.shards?.length >= 2, "Missing shard evidence"); + assert.ok( + reportChecks[entry.rule], + `Unimplemented shared rule ${entry.rule}`, + ); + try { + reportChecks[entry.rule](context); + } catch (error) { + if (!recommendedRules.has(entry.rule)) throw error; + context.warnings ??= []; + context.warnings.push(`${entry.id} (SHOULD): ${error.message}`); + } + } +} + +export function disposition(entry, manifest) { + const capability = + entry.kind === "property" + ? manifest.properties[entry.path] + : manifest.rules[entry.rule]; + assert.ok(capability, `No capability declaration for ${entry.id}`); + assert.ok(capability.reason, `Missing capability reason for ${entry.id}`); + return capability; +} diff --git a/tests/standard/conformance/shared/expected-failures.mjs b/tests/standard/conformance/shared/expected-failures.mjs new file mode 100644 index 0000000..de461f5 --- /dev/null +++ b/tests/standard/conformance/shared/expected-failures.mjs @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import { valuesAt } from "./checks.mjs"; + +export function runExpectedFailure( + entry, + context, + level, + issue, + check, + matchesKnownFailure, +) { + if (!issue?.samples[level]) return check(entry, context); + const expectedSamples = new Set(issue.samples[level]); + for (const name of expectedSamples) + assert.equal( + context.samples.filter((sample) => sample.name === name).length, + 1, + `Expected-failure fixture missing or duplicated: ${name}`, + ); + // Check every sample, including those after the original first failure. + for (const sample of context.samples) { + const parentPath = entry.path?.split(".").slice(0, -1).join("."); + const hasParent = + !parentPath || valuesAt(sample.report, parentPath).length > 0; + // Optional absence in other modes is legitimate. The required known-bug + // samples below still have to emit and reproduce their exact violation. + if ( + entry.kind === "property" && + (!entry.required || !hasParent) && + !expectedSamples.has(sample.name) && + valuesAt(sample.report, entry.path).length === 0 + ) + continue; + let failure; + try { + check(entry, { ...context, samples: [sample] }); + } catch (error) { + failure = error; + } + if (!expectedSamples.has(sample.name)) { + if (failure) throw failure; + continue; + } + assert.ok( + failure, + `Unexpected pass: ${entry.id} / ${sample.name}; review and remove the expected-failure marker`, + ); + if (!matchesKnownFailure(entry, sample, issue, failure)) throw failure; + } + return { + id: entry.id, + level, + samples: [...expectedSamples], + issue: issue.issue, + reason: issue.reason, + followUp: issue.followUp, + }; +} diff --git a/tests/standard/conformance/suite.mjs b/tests/standard/conformance/suite.mjs new file mode 100644 index 0000000..7e57bd5 --- /dev/null +++ b/tests/standard/conformance/suite.mjs @@ -0,0 +1,35 @@ +import { readFileSync } from "node:fs"; +import { after, test } from "node:test"; +import { catalogue, disposition } from "./shared/checks.mjs"; +import { expectedFailure, runConformance } from "./expected-failures.mjs"; +import { unitContext } from "./unit/fixtures.mjs"; +const level = process.env.CTRF_CONFORMANCE_LEVEL; +const manifest = JSON.parse( + readFileSync(new URL("./codeceptjs.json", import.meta.url), "utf8"), +); +const context = + level === "unit" + ? unitContext() + : JSON.parse(readFileSync(process.env.CTRF_CONFORMANCE_CONTEXT, "utf8")); +if (level !== "unit") { + for (const sample of context.samples) + sample.bytes = Buffer.from(sample.base64, "base64"); + for (const key of ["repeated", "shards"]) + context[key] = context[key].map((name) => + context.samples.find((sample) => sample.name === name), + ); +} +for (const entry of catalogue.entries) { + const capability = disposition(entry, manifest); + const issue = expectedFailure(entry, level); + test( + `${entry.id}${issue ? ` [expected failure: ${issue.issue}]` : ""}`, + { skip: capability.supported ? false : capability.reason }, + () => runConformance(entry, context, level), + ); +} +after(() => + console.log( + `Conformance recommendations: ${JSON.stringify(context.warnings)}`, + ), +); diff --git a/tests/standard/conformance/unit/conformance.test.mjs b/tests/standard/conformance/unit/conformance.test.mjs new file mode 100644 index 0000000..6321d3e --- /dev/null +++ b/tests/standard/conformance/unit/conformance.test.mjs @@ -0,0 +1 @@ +import "../suite.mjs"; diff --git a/tests/standard/conformance/unit/fixtures.mjs b/tests/standard/conformance/unit/fixtures.mjs new file mode 100644 index 0000000..45617c1 --- /dev/null +++ b/tests/standard/conformance/unit/fixtures.mjs @@ -0,0 +1,223 @@ +import { createRequire } from "node:module"; +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { metadata, expectedReport, nativeExtra } from "../expected.mjs"; +const require = createRequire(import.meta.url); +const createReporter = require("../../../../dist/index.cjs"); +const imported = require("codeceptjs/lib/event"); +const event = imported.default ?? imported; +export function nativeFixture() { + const now = Date.now(); + const base = { + file: "fixtures/one_test.js", + suite: ["outer", "inner"], + tags: ["@smoke"], + startedAt: now, + steps: [{ name: "check", status: "passed", duration: 3 }], + }; + return { + attempts: [ + { + ...base, + title: "passing ✓ @smoke", + status: "passed", + attempt: 0, + duration: 3, + }, + { + ...base, + title: "retry recovery", + status: "failed", + attempt: 0, + duration: 4, + error: { message: "first", stack: "Error: first" }, + }, + { + ...base, + title: "retry recovery", + status: "failed", + attempt: 1, + duration: 5, + error: { message: "second", stack: "Error: second" }, + }, + { + ...base, + title: "retry recovery", + status: "passed", + attempt: 2, + duration: 6, + }, + { + ...base, + title: "retry exhausted", + status: "failed", + attempt: 0, + duration: 4, + error: { message: "first", stack: "Error: first" }, + }, + { + ...base, + title: "retry exhausted", + status: "failed", + attempt: 1, + duration: 5, + error: { message: "last", stack: "Error: last" }, + }, + { ...base, title: "skipped", status: "skipped", attempt: 0, duration: 0 }, + { ...base, title: "same", status: "passed", attempt: 0, duration: 0 }, + { + ...base, + title: "same", + file: "fixtures/two_test.js", + suite: ["other"], + status: "passed", + attempt: 0, + }, + ], + }; +} +export function emitSample(name, native, options = {}, captureFailure = false) { + const scratch = mkdtempSync(path.join(tmpdir(), "ctrf-codecept-unit-")); + const key = Symbol.for("ctrf.codeceptjs.runtime"); + const previous = globalThis[key]; + let reporter; + try { + const runWindow = [Date.now()]; + reporter = createReporter({ + ...options, + outputDir: scratch, + outputFile: "report.json", + ...(options.customIdentity + ? { testIdResolver: ({ name }) => `custom:${name}` } + : {}), + }); + event.dispatcher.emit(event.all.before); + for (const a of native.attempts) { + let parent = { root: true }; + for (const title of a.suite ?? []) parent = { title, parent }; + const test = { + title: a.title, + uid: JSON.stringify([a.file, a.suite, a.title]), + file: a.file, + parent, + tags: a.tags, + startedAt: a.startedAt, + duration: a.duration, + steps: a.steps, + currentRetry: () => a.attempt, + }; + event.dispatcher.emit(event.test.started, test); + const extra = nativeExtra(a.title, a.attempt); + if (a.title.startsWith("passing")) { + createReporter.extra({ + owner: "first", + nested: { one: true }, + list: ["one"], + }); + createReporter.extra({ + owner: "last", + nested: { two: true }, + list: ["two"], + }); + } else if (extra) createReporter.extra(extra); + const error = a.error + ? Object.assign(new Error(a.error.message), { stack: a.error.stack }) + : undefined; + event.dispatcher.emit(event.test[a.status], test, error); + event.dispatcher.emit(event.test.finished, test); + createReporter.extra({ outside: "must not leak" }); + } + event.dispatcher.emit(event.all.result); + runWindow.push(Date.now()); + const bytes = readFileSync(path.join(scratch, "report.json")); + return { + name, + bytes, + report: JSON.parse(bytes), + native, + expected: expectedReport(native, options), + options, + runWindow, + }; + } catch (error) { + if (!captureFailure) throw error; + return { + name, + emissionFailure: { + message: error.message, + errorType: error.name, + absentOutput: !existsSync(path.join(scratch, "report.json")), + nativeSteps: native.attempts.flatMap((a) => + (a.steps ?? []).map((step) => ({ + title: step.title, + helperMethod: step.helperMethod, + name: step.name, + status: step.status, + })), + ), + }, + }; + } finally { + reporter?.dispose(); + if (previous === undefined) delete globalThis[key]; + else globalThis[key] = previous; + rmSync(scratch, { recursive: true, force: true }); + } +} +export function unitContext() { + const native = nativeFixture(); + const samples = [ + emitSample("full", native, metadata), + emitSample("repeated", native, metadata), + emitSample("defaults", native), + emitSample("minimal", native, { ...metadata, minimal: true }), + emitSample("empty", { attempts: [] }), + emitSample("empty-run-id", native, { runId: "" }), + emitSample("zero-build-number", native, { buildNumber: 0 }), + emitSample( + "custom-identity", + { attempts: native.attempts.slice(0, 1) }, + { customIdentity: true }, + ), + ]; + for (const [index, shardId] of ["one", "two"].entries()) + samples.push( + emitSample( + `shard-${shardId}`, + { attempts: native.attempts.slice(index, index + 1) }, + { ...metadata, shardId }, + ), + ); + const ImportedStep = require("codeceptjs/lib/step/helper"); + const Step = ImportedStep.default ?? ImportedStep; + const step = new Step({}, "check"); + step.setStatus("success"); + const probe = emitSample( + "actor-step", + { + attempts: [ + { + title: "actor-step", + file: "fixtures/actor_test.cjs", + suite: ["actor"], + status: "passed", + attempt: 0, + duration: 1, + steps: [step], + }, + ], + }, + {}, + true, + ); + const failedEmissions = probe.emissionFailure ? [probe] : []; + if (!probe.emissionFailure) samples.push(probe); + return { + samples, + failedEmissions, + repeated: samples.slice(0, 2), + shards: samples.filter((s) => s.name.startsWith("shard-")), + warnings: [], + }; +} diff --git a/tests/standard/run-all.mjs b/tests/standard/run-all.mjs new file mode 100644 index 0000000..a6ed88e --- /dev/null +++ b/tests/standard/run-all.mjs @@ -0,0 +1,13 @@ +import { spawnSync } from "node:child_process"; + +const npm = process.platform === "win32" ? "npm.cmd" : "npm"; +let failed = false; +for (const command of [ + "test:standard:checker", + "test:standard:conformance:unit", + "test:standard:conformance:integration", +]) { + const result = spawnSync(npm, ["run", command], { stdio: "inherit" }); + if (result.error || result.status !== 0) failed = true; +} +process.exitCode = failed ? 1 : 0; diff --git a/tests/standard/shared-files.json b/tests/standard/shared-files.json new file mode 100644 index 0000000..256976c --- /dev/null +++ b/tests/standard/shared-files.json @@ -0,0 +1,13 @@ +{ + "revision": "standard-checker-controls-v1", + "files": { + "conformance/shared/catalogue.json": "a700f698e2c7b9a695628fb56e491fea49eaecb464b74b790752d4abcfde1c6c", + "conformance/shared/checks.mjs": "08b1a64c56a57ce31305a01c39076518e46b55fdb0aa9bd3051803b5ed04402a", + "conformance/shared/expected-failures.mjs": "c8677174694a5ce433abd54205f5f2122e0d0b37ee062d6fa39df9f615d09120", + "checker/checker.test.mjs": "7aa7670edf15f2a5ae75b962141ab56bf282f5c3011b6c1accd25ae18489cd37", + "checker/run.mjs": "132eb103a0d2d939151110c62b639432d371d9d081f8d2adc26b7d6fd73836dc", + "conformance/reporters/ctrf.mjs": "4bf221fdce90646a8d9d43fca1aeb0b258d7cf4f56f32a66d8ba0ef4b36c5bc9", + "conformance/reporters/node.mjs": "dfe202dea4c8813c9bed6fea1ec0bcd7635c45066a95bf7e58d8df114f589c69", + "run-all.mjs": "0e7029e95ea4a36fc4438cd5eb6cf3703ffa56152f97bc6dc2a75bbf1cb6da4a" + } +} diff --git a/vitest.config.ts b/vitest.config.ts index 8866cac..895b17b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,7 +4,8 @@ export default defineConfig({ test: { environment: "node", globals: true, - reporters: ["default", "@d2t/vitest-ctrf-json-reporter"], + include: ["tests/**/*.test.ts"], + reporters: ["default", "./tests/reporters/vitest.mjs"], coverage: { provider: "v8", reporter: ["text", "json", "lcov"], From 4434b77a3bab75f2268f85c87c93c4c466046d3a Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 24 Oct 2026 10:25:46 +0100 Subject: [PATCH 2/2] Update dependencies to latest stable versions --- CONTRIBUTING.md | 2 +- README.md | 20 +- biome.json | 7 +- package-lock.json | 1328 ++++++----------- package.json | 18 +- tests/conformance-tooling.test.ts | 53 +- tests/integration/run.mjs | 2 +- .../conformance/expected-failures.mjs | 61 +- .../conformance/integration/prepare.mjs | 34 +- 9 files changed, 543 insertions(+), 982 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7d3ba5..c4332a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,7 +61,7 @@ Run `npm run build:check` to check source types with TypeScript 7, and ## Local verification -Use Node 22.12.0 or newer. CI tests Node 22, 24 and 26; quality and security +Use Node 22.18.0 or newer for development. CI tests Node 22, 24 and 26; quality and security stages run on Node 26. Keep direct dependencies, peer dependencies and explicit security overrides pinned to exact versions. Update the lockfile with changes. diff --git a/README.md b/README.md index 3f99631..8f2cfe1 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ If you find this project useful, consider giving it a GitHub star ⭐ It means a ## Node and framework support -Version 0.1.0 requires Node.js **22.12.0 or newer** and CodeceptJS **4.2.0**. +Version 0.1.0 requires Node.js **22.12.0 or newer** and CodeceptJS **4.2.2**. The package uses ESM internally and publishes ESM and CommonJS entry points, with declarations included for TypeScript consumers. The callable plugin factory is preserved for CommonJS configuration. Earlier CodeceptJS 3 and Node 18/20 @@ -199,13 +199,11 @@ npm does not propagate those overrides into a consumer's installation. ### Consumer dependency audit -A fresh consumer installing the CodeceptJS 4.2.0 peer currently reports ten -framework-related audit findings (three high, six moderate and one low). -The reporter checkout applies the exact development overrides below and retains -three moderate findings through CodeceptJS's `sprintf-js` dependency; its -production-only audit is clean. The overrides do not resolve every framework -finding. npm ignores a dependency package's overrides, so consumers who need -these patches must add them to their own root `package.json`: +Audit the application root after installing the reporter and its CodeceptJS peer. +The reporter checkout uses the tested development overrides below; npm does not +propagate a dependency package's overrides into consumer installations. A clean +checkout audit therefore does not establish a clean application audit. Consumers +that need these transitive patches must add them to their own root `package.json`: ```json { @@ -213,10 +211,10 @@ these patches must add them to their own root `package.json`: "codeceptjs": { "@xmldom/xmldom": "0.9.12", "axios": "1.20.0", - "uuid": "11.1.1", + "uuid": "14.0.3", "mocha": { - "diff": "8.0.3", - "serialize-javascript": "7.1.2" + "diff": "9.0.0", + "serialize-javascript": "7.1.4" } } } diff --git a/biome.json b/biome.json index 7b84f03..8cfa317 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.2/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -40,5 +40,10 @@ "organizeImports": "off" } } + }, + "markdown": { + "formatter": { + "enabled": false + } } } diff --git a/package-lock.json b/package-lock.json index b523ea9..1cb56ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,79 +12,80 @@ "ctrf": "0.5.0" }, "devDependencies": { - "@biomejs/biome": "2.5.15", + "@biomejs/biome": "2.6.2", "@d2t/vitest-ctrf-json-reporter": "1.3.0", "@types/node": "26.6.5", "@typescript/native": "npm:typescript@7.0.2", - "@vitest/coverage-v8": "5.0.3", + "@vitest/coverage-v8": "5.0.5", "ajv": "8.20.0", "ajv-formats": "3.0.1", - "codeceptjs": "4.2.0", - "tsup": "8.5.1", + "codeceptjs": "4.2.2", + "tsup": "9.0.0", "typescript": "npm:@typescript/typescript6@6.0.2", - "vitest": "5.0.3" + "vitest": "5.0.5" }, "engines": { "node": ">=22.12.0" }, "peerDependencies": { - "codeceptjs": "4.2.0" + "codeceptjs": "4.2.2" } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.212", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.212.tgz", - "integrity": "sha512-iTJSim/6LuSxiG3RF0FWU7bEQ8dF6EplcWci5Km4Vc8SUcR8vxFuC7T/pfCutW00pI/3WYsHm8RCQ5141/Xykw==", + "version": "4.0.107", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.107.tgz", + "integrity": "sha512-Z/e4lbywscQ86JHi+vgIE7paPzvCSypJFIQugADWpWZXGkQ3iZ6M08rqkjDNhj3y6Gi7O3A/sSL/Ib3LoVZv6w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.57", + "@ai-sdk/provider": "4.0.24", + "@ai-sdk/provider-utils": "5.0.56", "@vercel/oidc": "3.2.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/provider": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.18.tgz", - "integrity": "sha512-IpefF5ssZVOZCD4Wu7zxt6PwZoGT2HLVjxpJ10Bjx6EAmRfLYJPjyrfxb0RYDlwvXnDb9nkQ7Do/oJVDuRFzew==", + "version": "4.0.24", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.24.tgz", + "integrity": "sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA==", "dev": true, "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" }, "engines": { - "node": ">=18" + "node": ">=22" } }, "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.57", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.57.tgz", - "integrity": "sha512-89a7sPvZqXsP3PigcAQLWntu/duIVEcN/RyChAB+j6p3C7NwW767sVttvL8r0+BetTg63uAXQoYHCZZueQKBSw==", + "version": "5.0.56", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.56.tgz", + "integrity": "sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", + "@ai-sdk/provider": "4.0.24", "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", - "undici": "^6.28.0" + "undici": "^7.29.0" }, "engines": { - "node": ">=18.17" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz", + "integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==", "dev": true, "license": "MIT", "engines": { @@ -102,13 +103,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", - "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.11.tgz", + "integrity": "sha512-OZ6+oaK7GMyg3IfCpj8JOmn8n+w2rJbDT0LLfVDTLb1ZFeKDh3AiNELgCMUw175IJHSkLl74hG0M0VLsvx+gag==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.8" + "@babel/types": "^7.29.11" }, "bin": { "parser": "bin/babel-parser.js" @@ -118,13 +119,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-string-parser": "^7.29.11", "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { @@ -142,9 +143,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.15.tgz", - "integrity": "sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.6.2.tgz", + "integrity": "sha512-JLTFAFF9vfJcWwLhwuL4ZHflLg+XfTEC4IUnAlJRX1cMRt57++Xd1Ysb9Fdmlh9YUrvyCXfPEHjjEIqKJGdPKw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -158,20 +159,56 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.5.15", - "@biomejs/cli-darwin-x64": "2.5.15", - "@biomejs/cli-linux-arm64": "2.5.15", - "@biomejs/cli-linux-arm64-musl": "2.5.15", - "@biomejs/cli-linux-x64": "2.5.15", - "@biomejs/cli-linux-x64-musl": "2.5.15", - "@biomejs/cli-win32-arm64": "2.5.15", - "@biomejs/cli-win32-x64": "2.5.15" + "@biomejs/cli-android-arm64": "2.6.2", + "@biomejs/cli-android-x64": "2.6.2", + "@biomejs/cli-darwin-arm64": "2.6.2", + "@biomejs/cli-darwin-x64": "2.6.2", + "@biomejs/cli-linux-arm64": "2.6.2", + "@biomejs/cli-linux-arm64-musl": "2.6.2", + "@biomejs/cli-linux-x64": "2.6.2", + "@biomejs/cli-linux-x64-musl": "2.6.2", + "@biomejs/cli-win32-arm64": "2.6.2", + "@biomejs/cli-win32-x64": "2.6.2" + } + }, + "node_modules/@biomejs/cli-android-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.2.tgz", + "integrity": "sha512-dMpdpH7zFEL5FxuIA9LJW+RoNyC3oeDB7PJyjqVVwtMNoKB5uo8OZYmrVSBikn71txzYwy/3xga7UME6bJQZ6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-android-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-x64/-/cli-android-x64-2.6.2.tgz", + "integrity": "sha512-XCwEhPBGYzuRA502dRkCcQ+5cefQwuxQR7f9wa4do92dvIcuBL6VMLMFB/fL9hlpZ4dGSfHdpGFSIDlp52mg0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.15.tgz", - "integrity": "sha512-BZVzFhJ/mUvTLMYbc9x6el0o2Uv5zP7bACYyhFb6fSc1giroXm7PL0a5KqMJ9F+/BnkYRjagD/ROSDVOZ6eapg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-rpXtabRgyZNz3aCjd/cRoSZGYi92R9eAXdUUe1sigIsRDyfMwgThaa98Yxt1fiZ7mxquqkAyIHIo08N/e1ToBA==", "cpu": [ "arm64" ], @@ -186,9 +223,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.15.tgz", - "integrity": "sha512-V5Kw63V+fVGNFhFrizDxMbGXAzZCh8kYzJpy3GI6gTapWDg7bZK9oYC9CbVgQSEdTnupz0U5Efz8Ev+5vdBBXw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.2.tgz", + "integrity": "sha512-7GGzlm1GxUKoQ9CNV8tQZ+XXad4sz+v+uzNsWlhaGjsnZqUOVBw1OmAMpG3uNV/Mr0UrCpRJvSK0qvfuqSQOjg==", "cpu": [ "x64" ], @@ -203,9 +240,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.15.tgz", - "integrity": "sha512-XaG7P7eeSLYETD3K9grfB0mQmpLQZjygdbsxekWBJAg4am79fGAtRpfRyNnTvXB2fZFNnJZG3nXm7HBat0+VUw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.2.tgz", + "integrity": "sha512-Qs4CofCE8MdkjZFfEjswh2NAcbyQ7IVYtGSzQBvLqFlla/CSXroGbktUFndWG/kVSTywadauzAspYWtSfqAc2g==", "cpu": [ "arm64" ], @@ -223,9 +260,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.15.tgz", - "integrity": "sha512-tGzZUTcJCV7tj5Adh/Gn6nR4MycqA3iohq2LEcrarAgRBkU0h0OKd2UzCRkOsOYOuUGaUJb4YmqRqyy2feGGSw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.2.tgz", + "integrity": "sha512-I+FYCLVSmR3cYfKcQ+KNeLOZ5w0wRd9fQFlw3JbSvy7bkegvTqdHaBRRcJzHQB/1ldldTLIcG21ka6X2cTWmJw==", "cpu": [ "arm64" ], @@ -243,9 +280,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.15.tgz", - "integrity": "sha512-xE4iEW/3LqlYj9GFgGrFsFSH51dEEpUbYWBfeOv0q87WkUDxK2o/HhreSb7qMJqck70RVM6Lg96hgedcAYcOkA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.2.tgz", + "integrity": "sha512-FJKsb95418139xDdbv9XnNJsIIwvsgR21Of0aE9B9U+nQlOtRq5gJl2ps0jZUmYBxTaOlP9BKo8N+Ff8+Rui1g==", "cpu": [ "x64" ], @@ -263,9 +300,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.15.tgz", - "integrity": "sha512-IlxUcyxilVGPsE008x13pWdkTbU3nQpP2i9b5UrbOYj6goBkKsRX1XB7yJLcV+O1H9LBchIIm4adOaVLZBX0ZQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.2.tgz", + "integrity": "sha512-wnjEXAK2pDd2j8r9gKI3L+44sIxjkYzS5aHmD1+K57RNJ3sFyT4n22NH6QFPqqeVdRBxN54H3kckUUCrFKCcJw==", "cpu": [ "x64" ], @@ -283,9 +320,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.15.tgz", - "integrity": "sha512-2kPKzhNlm8C+Ru3GcO0Me2ODkCBLrVOUNuyi84RJyWDVKAA4+Kjj3jMjL938lF6BRfvltC4vB2nPY9SvN51Ovg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.2.tgz", + "integrity": "sha512-/PEOo6evlgrQOfKhMNtC/4Gk8ai3hySXXEtjUzCqZe/4iAbMdqelpp7qvZEDyLxlZS+BjOMQFpq2gXI/BPaAqw==", "cpu": [ "arm64" ], @@ -300,9 +337,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.5.15", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.15.tgz", - "integrity": "sha512-yAzh4UqEImV6Hcy0zjUqNfJAsUhoD64FBQCdTG8Md/Z2wQeVP/ZmJ3XRYAs1g8J9fjTFyl2fCtc5FB5til47+g==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.2.tgz", + "integrity": "sha512-FVlWD+mDDz8nN5LWKRWUhhb7wKIK0VE3oXQvQBX7Fwwj00CvqMVhMyiz1iz8O5aZGmnkhFlKSwJPrUvMh5prQw==", "cpu": [ "x64" ], @@ -843,9 +880,9 @@ } }, "node_modules/@hono/node-server": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.4.tgz", - "integrity": "sha512-2g1qeHl4BduwDn6L8nkAvDrPUWPMlkQ//a5NhP/zIyaXKMjZNRV+cG9Csu+BiJFf6+68pqzjf4Hi5sJGKHuS1g==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.5.tgz", + "integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==", "dev": true, "license": "MIT", "engines": { @@ -1048,9 +1085,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.32.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", - "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", + "version": "1.32.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.2.tgz", + "integrity": "sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==", "dev": true, "license": "MIT", "dependencies": { @@ -1115,20 +1152,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@oxc-project/types": { - "version": "0.153.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", - "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz", + "integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==", "dev": true, "license": "MIT", "peer": true, @@ -1148,9 +1175,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", - "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz", + "integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==", "cpu": [ "arm" ], @@ -1166,9 +1193,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", - "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz", + "integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==", "cpu": [ "arm64" ], @@ -1184,9 +1211,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", - "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz", + "integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==", "cpu": [ "arm64" ], @@ -1202,9 +1229,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", - "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz", + "integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==", "cpu": [ "x64" ], @@ -1220,9 +1247,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", - "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz", + "integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==", "cpu": [ "x64" ], @@ -1238,9 +1265,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", - "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz", + "integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==", "cpu": [ "arm" ], @@ -1256,9 +1283,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", - "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz", + "integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==", "cpu": [ "arm64" ], @@ -1277,9 +1304,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", - "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz", + "integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==", "cpu": [ "arm64" ], @@ -1298,9 +1325,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", - "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz", + "integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==", "cpu": [ "ppc64" ], @@ -1319,9 +1346,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", - "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz", + "integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==", "cpu": [ "s390x" ], @@ -1340,9 +1367,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", - "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz", + "integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==", "cpu": [ "x64" ], @@ -1361,9 +1388,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", - "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz", + "integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==", "cpu": [ "x64" ], @@ -1382,9 +1409,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", - "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz", + "integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==", "cpu": [ "arm64" ], @@ -1400,9 +1427,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", - "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz", + "integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==", "cpu": [ "arm64" ], @@ -1418,9 +1445,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", - "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz", + "integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==", "cpu": [ "x64" ], @@ -1444,9 +1471,9 @@ "peer": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.4.tgz", - "integrity": "sha512-7sdQKCYk/JCWa7rlXOgIa7AEAfZnUu/twNX4m4ECouPWsYhzxHfslKCFpOs43J8uux356TUU8NCuQus3XISCbQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.65.0.tgz", + "integrity": "sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==", "cpu": [ "arm" ], @@ -1458,9 +1485,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.4.tgz", - "integrity": "sha512-iV5jlPMChTQgD0UCTcT9rLHN4DNce34v5Mw0IN7csTSg4RDaKq9AximXPL35k+sGXcfrR4OeQMvFNe+OmV1rhw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.65.0.tgz", + "integrity": "sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==", "cpu": [ "arm64" ], @@ -1472,9 +1499,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.4.tgz", - "integrity": "sha512-a0Sswc+5v8BVkjHGOaJCTbFArZZUJCGFi2y39FWGdiplOToMNzr1tE4v3l67DW+vGyYD2sS5PfhDdUCyrr3mjA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.65.0.tgz", + "integrity": "sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==", "cpu": [ "arm64" ], @@ -1486,9 +1513,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.4.tgz", - "integrity": "sha512-qHH6RSwVPuNkqbOv0hgZKonHBW/ZIaHpd3bgH5LRPJ/8oRE+6Dl36OfS7wUQyeSeqQsAh1NrpwmUemgH1fh1WQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.65.0.tgz", + "integrity": "sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==", "cpu": [ "x64" ], @@ -1500,9 +1527,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.4.tgz", - "integrity": "sha512-hs9CVjHH9WPGfeHZ5yvHYEiEm7hhm7M/16ytRTxrir7q+KnGaQ6EO1JYcrGooVIHYN1Z3H7sLIwxe97NNBYg6A==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.65.0.tgz", + "integrity": "sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==", "cpu": [ "arm64" ], @@ -1514,9 +1541,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.4.tgz", - "integrity": "sha512-4UYtjV0FBOToshUTYvwoTbfvig9Ec94EcE56WehhqaFZK43rK9sLNYO+vRMnJ9GTOxanFkKB/IvBKt96VmCYiA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.65.0.tgz", + "integrity": "sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==", "cpu": [ "x64" ], @@ -1528,9 +1555,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.4.tgz", - "integrity": "sha512-Md2BnPCGXVtoyaqPCxxahW4f8r5h8e1Iago6LFQRf+b9m8YOnkfW2L9l1KK9UsQa2gXYoVAVTi6mQgkVNSrOuQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.65.0.tgz", + "integrity": "sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==", "cpu": [ "arm" ], @@ -1545,9 +1572,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.4.tgz", - "integrity": "sha512-oBAOMLpMKggD7Z9oTZCVUp4dDMa84Br/PV5mm6IL1kykBgk5Kp3gyu2buW8KFzvkktyFj+NdZdZCxpRqNgO71A==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.65.0.tgz", + "integrity": "sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==", "cpu": [ "arm" ], @@ -1562,9 +1589,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.4.tgz", - "integrity": "sha512-KYgCzHJjmFhXb7kPv6ih9c6HdY/4JXr6MmLtWFRmK0hAgXxLBx2tI24J1OYJyFl7DfxnzREWSLDj3FxKHfPHjQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.65.0.tgz", + "integrity": "sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==", "cpu": [ "arm64" ], @@ -1579,9 +1606,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.4.tgz", - "integrity": "sha512-xpeOpK+6OQfqZL3HQ1leCKM/JFZz0BmpVGkeydWRBdjYYgDlry7a2RWZ0rxnoJ29wMUK/xa/e1CSnnjV7UnxBw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.65.0.tgz", + "integrity": "sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==", "cpu": [ "arm64" ], @@ -1596,9 +1623,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.4.tgz", - "integrity": "sha512-Iju/GGedDha6/e8lseZIxO2i3xPg+aJFw+l07QijRsEkGiUzQbsdNGaouTZwa6N1nu4q9WVJUbc/ejwPN24i+Q==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.65.0.tgz", + "integrity": "sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==", "cpu": [ "loong64" ], @@ -1613,9 +1640,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.4.tgz", - "integrity": "sha512-v3fNKbpq8uHqlMayTSaRCX9GVa/OECoY5beetD1qa5MKeRmrlgWve98UpcxgQAqGKQpqhcflDAe1AvjIUMenWQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.65.0.tgz", + "integrity": "sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==", "cpu": [ "loong64" ], @@ -1630,9 +1657,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.4.tgz", - "integrity": "sha512-ngxCMEz/wPVGkB/Fx7yGW8LkkL1mtg/V/KOYZQytTUe/MCh/w3nsvS0CnjhdS6YEJb98StA2aUnPaQZte0uCZg==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.65.0.tgz", + "integrity": "sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==", "cpu": [ "ppc64" ], @@ -1647,9 +1674,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.4.tgz", - "integrity": "sha512-ob/5q9wu+GPAIJvqOzcr0tUo7vIuczYoVmxgfCjbSCE2zwbi/rxkR35b/fnQKGrI5fCua7ULhnB7w7wHseLlNQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.65.0.tgz", + "integrity": "sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==", "cpu": [ "ppc64" ], @@ -1664,9 +1691,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.4.tgz", - "integrity": "sha512-FsFK/3sO22/ogdbJ1TtWFyZAfru07l+4ya1OHn0L7renzB7qoQp0gaaTya7zKezubV5OoG9mSek1Z4qcT5rdew==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.65.0.tgz", + "integrity": "sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==", "cpu": [ "riscv64" ], @@ -1681,9 +1708,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.4.tgz", - "integrity": "sha512-id50wkt+Jt20/66TkMr04OXdUB64Sj4W3WVhMVf/KuTiy5NFQLGgmCwE0RBobDPr22zyKxEE1U6zMAeBTgnZaQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.65.0.tgz", + "integrity": "sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==", "cpu": [ "riscv64" ], @@ -1698,9 +1725,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.4.tgz", - "integrity": "sha512-WinoPEOOZz+rKnluSytyemucpXtygq1LJaU+HBxclUXPz7IVBJIjcdDH9V4nN/yvYjyiVeb/fH32HSX2acLmrw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.65.0.tgz", + "integrity": "sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==", "cpu": [ "s390x" ], @@ -1715,9 +1742,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.4.tgz", - "integrity": "sha512-Fs5AqGrSuvXIPSGpZQAUHyM+7eRpKkWvIxN8eTf/8hoTCiKybb2FeqBnB5R9OyVOYI3/vP+VgSBOHT1zkYkKSA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.65.0.tgz", + "integrity": "sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==", "cpu": [ "x64" ], @@ -1732,9 +1759,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.4.tgz", - "integrity": "sha512-EdzYVEpaQG2YSRW/qi/uHcersoPi119NmUK/hgBQJdAZvk7HjlSKHsBWQvofZ8ZoL98R9SUNJUAtBwRlAsZ7WA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.65.0.tgz", + "integrity": "sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==", "cpu": [ "x64" ], @@ -1749,9 +1776,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.4.tgz", - "integrity": "sha512-FZarHZsoTVzvMfCS0hDWcybs+VvkC5TPEYREZhuyD93GGMBBYKa413z2zUsXJEGU7hHJ7EEr4mUUMV1WfHIVmA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.65.0.tgz", + "integrity": "sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==", "cpu": [ "x64" ], @@ -1763,9 +1790,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.4.tgz", - "integrity": "sha512-A+nCwcEGR1r2MET1kikO4O9nkq6bjpA4gGwZiyZ8CsL3FMqPvh4OREYyZHv/WJjl8eCgfUqh9dFUc01qBOUOgQ==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.65.0.tgz", + "integrity": "sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==", "cpu": [ "arm64" ], @@ -1777,9 +1804,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.4.tgz", - "integrity": "sha512-9dS3eF2HL38ItWUF6wbStGGoLALgh3e+BbBfq5VEAm/NKhnEibz/B+D719uTET83+E1lZL2TKlv6idlzOorrgA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.65.0.tgz", + "integrity": "sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==", "cpu": [ "arm64" ], @@ -1791,9 +1818,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.4.tgz", - "integrity": "sha512-n2waAp+bsaATQlfM0dSsFWG+Juj3GBGYI5MS4prChfjn3z+PvijnWZT9FmRtcK6Db0F4rfGzMJUP5VENvb7wig==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.65.0.tgz", + "integrity": "sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==", "cpu": [ "ia32" ], @@ -1805,9 +1832,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.4.tgz", - "integrity": "sha512-kXMhVM8CVi/jRZAN8+SPVTdd1kl71WCVAajeu5JaVwsmjmAO40MiEj4QBDJeoWkLICE3b7jnRQK/mBQyhh0BCw==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.65.0.tgz", + "integrity": "sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==", "cpu": [ "x64" ], @@ -1819,9 +1846,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.4.tgz", - "integrity": "sha512-OWiFOGLjX+ZPsRJjo9yQpU3rq5wBNxyPjB9Sc6PmSPVQgUKVSHQu2zR5y1YkZ+HFzy4rHfYhwnT2PQSUgatnJA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.65.0.tgz", + "integrity": "sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==", "cpu": [ "x64" ], @@ -2276,27 +2303,27 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.3.tgz", - "integrity": "sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.5.tgz", + "integrity": "sha512-4KpkGdIrSqBihx/JyP/e+S+IKB98etg1XDQiLAxksLfcShnwi1VReGeOT9NI43hLHbknosKp/1CCLn9Zgcs8mg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/istanbul-lib-coverage": "^1.0.0", - "@vitest/istanbul-lib-report": "^1.0.0", - "ast-v8-to-istanbul": "^1.0.5", - "magicast": "^0.5.4", - "obug": "^2.1.4", - "std-env": "^4.2.0", - "tinyrainbow": "^3.1.1" + "@vitest/istanbul-lib-coverage": "^1.0.2", + "@vitest/istanbul-lib-report": "^1.0.2", + "ast-v8-to-istanbul": "^1.0.7", + "magicast": "^0.5.5", + "obug": "^3.0.0", + "std-env": "^4.3.0", + "tinyrainbow": "^3.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "5.0.3", - "vitest": "5.0.3" + "@vitest/browser": "5.0.5", + "vitest": "5.0.5" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2328,22 +2355,22 @@ } }, "node_modules/@vitest/mocker": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", - "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", + "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.3", + "@vitest/spy": "5.0.5", "estree-walker": "^3.0.3", - "magic-string": "^1.2.3" + "magic-string": "^1.4.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "msw": "^2.4.9", + "msw": "^2.4.9 || ^3.0.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -2366,15 +2393,22 @@ } }, "node_modules/@vitest/spy": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", - "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", + "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", "dev": true, "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@workflow/serde": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz", + "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@xmldom/xmldom": { "version": "0.9.12", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", @@ -2462,19 +2496,18 @@ } }, "node_modules/ai": { - "version": "6.0.303", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.303.tgz", - "integrity": "sha512-4HE3naLXgRLN4XOoEJaPHfIz7/hO0A21ltUG5DM3NDrGVTrqhnfs7s3ItwHtiWazCDOIuc4cCE4MgiuEvVryCw==", + "version": "7.0.132", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.132.tgz", + "integrity": "sha512-4WoiX6bpklwbpK441CmQoeZtEVwbpCZNyaTQ1Bc2vSS8jhzrVosRA2L9zNO1fLtvPk7yQfhKtrgNUschez1hGQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.212", - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.57", - "@opentelemetry/api": "^1.9.0" + "@ai-sdk/gateway": "4.0.107", + "@ai-sdk/provider": "4.0.24", + "@ai-sdk/provider-utils": "5.0.56" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" @@ -2570,11 +2603,21 @@ "license": "MIT" }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-3.0.4.tgz", + "integrity": "sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==", "dev": true, - "license": "Python-2.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "PSF-2.0" }, "node_modules/arrify": { "version": "3.0.0", @@ -2681,19 +2724,19 @@ "license": "BSD" }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^2.1.0", "debug": "^4.4.3", "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", - "qs": "^6.15.2", + "qs": "^6.16.1", "raw-body": "^3.0.2", "type-is": "^2.1.0" }, @@ -2833,13 +2876,13 @@ } }, "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.1.0.tgz", + "integrity": "sha512-LMDU/U2yA4IPIY8KT0NsNPWEYZvAMkbgyspxJ6btJaYV+WMDOHAZCmB0lsTObiHaRS1V/ztKAl7+TSkTqXyeoA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.19.0" } }, "node_modules/call-bind-apply-helpers": { @@ -2884,19 +2927,6 @@ "tslib": "^2.0.3" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chai": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", @@ -2975,16 +3005,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cheerio/node_modules/undici": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", - "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -3161,9 +3181,9 @@ } }, "node_modules/codeceptjs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/codeceptjs/-/codeceptjs-4.2.0.tgz", - "integrity": "sha512-eHhm0G9/0kRbEpEu/hA4vWtgVAV/hH/JKzxlN5zE1wM1KGnoPMJ+kBqboeNucDff9OK+sSW11BemijzGJmhpuA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/codeceptjs/-/codeceptjs-4.2.2.tgz", + "integrity": "sha512-1R11gvgCmUGHtddzOLHwZWIxXsKtQR0xmS0ATjUouOvmVF7CRr2tQs9Yum9e5Z10RJKbmKvSqp2rbHqnX+u9cw==", "dev": true, "license": "MIT", "dependencies": { @@ -3173,11 +3193,11 @@ "@cucumber/gherkin": "38.0.0", "@cucumber/messages": "32.3.1", "@modelcontextprotocol/sdk": "^1.26.0", - "@xmldom/xmldom": "0.9.10", + "@xmldom/xmldom": "^0.9.12", "acorn": "8.15.0", - "ai": "^6.0.43", + "ai": "7.0.132", "arrify": "3.0.0", - "axios": "1.16.1", + "axios": "^1.20.0", "chalk": "4.1.2", "cheerio": "^1.0.0", "chokidar": "^5.0.0", @@ -3200,7 +3220,7 @@ "lodash.merge": "4.6.2", "lodash.shuffle": "4.2.0", "mkdirp": "3.0.1", - "mocha": "11.7.5", + "mocha": "^12.0.3", "monocart-coverage-reports": "2.12.12", "ms": "2.1.3", "multer": "^2.0.2", @@ -3487,19 +3507,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -3545,9 +3552,9 @@ } }, "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4051,9 +4058,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -4132,18 +4139,14 @@ "url": "https://opencollective.com/express" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4161,16 +4164,6 @@ "rollup": "^4.34.8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/fn-args": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-4.0.0.tgz", @@ -4481,16 +4474,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hono": { "version": "4.13.13", "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", @@ -4691,9 +4674,9 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", - "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "version": "10.7.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "dev": true, "license": "MIT", "engines": { @@ -4731,23 +4714,16 @@ } }, "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-promise": { @@ -4833,9 +4809,9 @@ } }, "node_modules/jose": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.13.tgz", + "integrity": "sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==", "dev": true, "license": "MIT", "funding": { @@ -4985,9 +4961,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.5.0.tgz", + "integrity": "sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==", "dev": true, "funding": [ { @@ -5001,10 +4977,10 @@ ], "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^3.0.3" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "js-yaml": "bin/js-yaml.mjs" } }, "node_modules/json-schema": { @@ -5326,9 +5302,9 @@ } }, "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", "dev": true, "license": "MIT", "engines": { @@ -5355,22 +5331,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -5628,181 +5588,34 @@ } }, "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-12.0.3.tgz", + "integrity": "sha512-beammieDdiWdkjANWeWPpkxUROYGi5FN88fPOkLiticz7HTg3Y+Pho3EYE6SUdcuWnZ3h2WD8EXCA55JF4o6wg==", "dev": true, "license": "MIT", "dependencies": { "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", + "chokidar": "^5.0.0", "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", + "diff": "^9.0.0", + "find-up-simple": "^1.0.1", + "glob": "^13.0.0", + "is-path-inside": "^4.0.0", + "is-unicode-supported": "^0.1.0", + "js-yaml": "^5.0.0", + "minimatch": "^10.2.2", "ms": "^2.1.3", "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", + "serialize-javascript": "^7.1.1", + "strip-json-comments": "^5.0.3", "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" + "workerpool": "^10.0.0" }, "bin": { - "_mocha": "bin/_mocha", "mocha": "bin/mocha.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/mocha/node_modules/supports-color": { @@ -5821,43 +5634,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/monocart-coverage-reports": { "version": "2.12.12", "resolved": "https://registry.npmjs.org/monocart-coverage-reports/-/monocart-coverage-reports-2.12.12.tgz", @@ -5920,9 +5696,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.4.0.tgz", - "integrity": "sha512-7dqa0ZcFfzbefdTuIkzOSMvZWC0J7FLqBOjJUZvDCXShIURWKxAyTT1wHhnE5q19c7jOJf43IYYKjBmZVZmvhg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.5.0.tgz", + "integrity": "sha512-BsqOzYGVq49JcW2IzoSAxeKYRevb+dGGPPWleOR8Lrlk4igwxu1/9pwSXlByhCb7MVZDrNJFC0IBP5/O5nLYRw==", "dev": true, "license": "MIT", "dependencies": { @@ -6119,9 +5895,9 @@ } }, "node_modules/obug": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", - "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -6133,9 +5909,9 @@ } }, "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz", + "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6143,6 +5919,10 @@ }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/once": { @@ -6219,38 +5999,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -6373,16 +6121,6 @@ "tslib": "^2.0.3" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6480,9 +6218,9 @@ } }, "node_modules/postcss": { - "version": "8.5.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", - "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -6501,7 +6239,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.19", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" }, @@ -6602,9 +6340,9 @@ } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", + "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6731,16 +6469,6 @@ "node": ">= 0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6792,14 +6520,14 @@ } }, "node_modules/rolldown": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", - "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz", + "integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@oxc-project/types": "=0.153.0", + "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6809,27 +6537,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" + "@rolldown/binding-android-arm-eabi": "1.2.15", + "@rolldown/binding-android-arm64": "1.2.15", + "@rolldown/binding-darwin-arm64": "1.2.15", + "@rolldown/binding-darwin-x64": "1.2.15", + "@rolldown/binding-freebsd-x64": "1.2.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", + "@rolldown/binding-linux-arm64-gnu": "1.2.15", + "@rolldown/binding-linux-arm64-musl": "1.2.15", + "@rolldown/binding-linux-ppc64-gnu": "1.2.15", + "@rolldown/binding-linux-s390x-gnu": "1.2.15", + "@rolldown/binding-linux-x64-gnu": "1.2.15", + "@rolldown/binding-linux-x64-musl": "1.2.15", + "@rolldown/binding-openharmony-arm64": "1.2.15", + "@rolldown/binding-win32-arm64-msvc": "1.2.15", + "@rolldown/binding-win32-x64-msvc": "1.2.15" } }, "node_modules/rollup": { - "version": "4.64.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.4.tgz", - "integrity": "sha512-1DTowQbct6FOZTjZoYrZ8BwsjIwsB89HxwOn2pWPEyEKWWvKI8OrpTgmicB3fUbQzhlooCQRIn6JLUPhE0CZUA==", + "version": "4.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.65.0.tgz", + "integrity": "sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -6844,31 +6572,31 @@ }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.64.4", - "@rollup/rollup-android-arm64": "4.64.4", - "@rollup/rollup-darwin-arm64": "4.64.4", - "@rollup/rollup-darwin-x64": "4.64.4", - "@rollup/rollup-freebsd-arm64": "4.64.4", - "@rollup/rollup-freebsd-x64": "4.64.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.64.4", - "@rollup/rollup-linux-arm-musleabihf": "4.64.4", - "@rollup/rollup-linux-arm64-gnu": "4.64.4", - "@rollup/rollup-linux-arm64-musl": "4.64.4", - "@rollup/rollup-linux-loong64-gnu": "4.64.4", - "@rollup/rollup-linux-loong64-musl": "4.64.4", - "@rollup/rollup-linux-ppc64-gnu": "4.64.4", - "@rollup/rollup-linux-ppc64-musl": "4.64.4", - "@rollup/rollup-linux-riscv64-gnu": "4.64.4", - "@rollup/rollup-linux-riscv64-musl": "4.64.4", - "@rollup/rollup-linux-s390x-gnu": "4.64.4", - "@rollup/rollup-linux-x64-gnu": "4.64.4", - "@rollup/rollup-linux-x64-musl": "4.64.4", - "@rollup/rollup-openbsd-x64": "4.64.4", - "@rollup/rollup-openharmony-arm64": "4.64.4", - "@rollup/rollup-win32-arm64-msvc": "4.64.4", - "@rollup/rollup-win32-ia32-msvc": "4.64.4", - "@rollup/rollup-win32-x64-gnu": "4.64.4", - "@rollup/rollup-win32-x64-msvc": "4.64.4", + "@rollup/rollup-android-arm-eabi": "4.65.0", + "@rollup/rollup-android-arm64": "4.65.0", + "@rollup/rollup-darwin-arm64": "4.65.0", + "@rollup/rollup-darwin-x64": "4.65.0", + "@rollup/rollup-freebsd-arm64": "4.65.0", + "@rollup/rollup-freebsd-x64": "4.65.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.65.0", + "@rollup/rollup-linux-arm-musleabihf": "4.65.0", + "@rollup/rollup-linux-arm64-gnu": "4.65.0", + "@rollup/rollup-linux-arm64-musl": "4.65.0", + "@rollup/rollup-linux-loong64-gnu": "4.65.0", + "@rollup/rollup-linux-loong64-musl": "4.65.0", + "@rollup/rollup-linux-ppc64-gnu": "4.65.0", + "@rollup/rollup-linux-ppc64-musl": "4.65.0", + "@rollup/rollup-linux-riscv64-gnu": "4.65.0", + "@rollup/rollup-linux-riscv64-musl": "4.65.0", + "@rollup/rollup-linux-s390x-gnu": "4.65.0", + "@rollup/rollup-linux-x64-gnu": "4.65.0", + "@rollup/rollup-linux-x64-musl": "4.65.0", + "@rollup/rollup-openbsd-x64": "4.65.0", + "@rollup/rollup-openharmony-arm64": "4.65.0", + "@rollup/rollup-win32-arm64-msvc": "4.65.0", + "@rollup/rollup-win32-ia32-msvc": "4.65.0", + "@rollup/rollup-win32-x64-gnu": "4.65.0", + "@rollup/rollup-win32-x64-msvc": "4.65.0", "fsevents": "~2.3.2" } }, @@ -6978,9 +6706,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.2.tgz", - "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.4.tgz", + "integrity": "sha512-FqlEuoTq/zRmA4Lg6aOqyH3lCHbUtCB0bmHCeFniX2GBdDqMJM3FwdvEhlE5Pim1dlt2FfBdVPmUNNiGhhKqpw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -7259,13 +6987,13 @@ } }, "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7374,9 +7102,9 @@ "license": "MIT" }, "node_modules/tinybench": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", - "integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", + "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==", "dev": true, "license": "MIT", "engines": { @@ -7384,11 +7112,14 @@ } }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.17", @@ -7452,28 +7183,28 @@ "license": "0BSD" }, "node_modules/tsup": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", - "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-9.0.0.tgz", + "integrity": "sha512-DHgb0bfo2gFKybOa8dRFMiHSqzWWhMkfNK0f902VVquWQtDBWExNgidHszjjDKaFd12pWPgzumpWf9nGr3gzFg==", "dev": true, "license": "MIT", "dependencies": { "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.27.0", - "fix-dts-default-cjs-exports": "^1.0.0", + "cac": "^7.1.0", + "chokidar": "^5.0.0", + "consola": "^3.4.2", + "esbuild": "^0.28.2", + "fix-dts-default-cjs-exports": "^1.0.1", "joycon": "^3.1.1", + "obug": "^3.0.0", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "^0.7.6", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", + "rollup": "^4.64.6", + "source-map-js": "^1.2.2", + "sucrase": "^3.35.1", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", "tree-kill": "^1.2.2" }, "bin": { @@ -7481,7 +7212,7 @@ "tsup-node": "dist/cli-node.js" }, "engines": { - "node": ">=18" + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", @@ -7504,46 +7235,6 @@ } } }, - "node_modules/tsup/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/tsup/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/tsup/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -7612,13 +7303,13 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", - "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { @@ -7656,9 +7347,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz", + "integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -7666,7 +7357,7 @@ ], "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, "node_modules/vary": { @@ -7680,17 +7371,17 @@ } }, "node_modules/vite": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", - "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -7707,7 +7398,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7759,23 +7450,23 @@ } }, "node_modules/vitest": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz", - "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", + "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/mocker": "5.0.3", - "chai": "^6.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", - "magic-string": "^1.2.3", - "obug": "^2.1.4", + "magic-string": "^1.4.3", + "obug": "^3.0.0", "picomatch": "^4.0.7", - "std-env": "^4.2.0", - "tinybench": "^6.1.4", - "tinyexec": "^1.3.0", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "why-is-node-running": "3.2.1" }, @@ -7792,12 +7483,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "5.0.3", - "@vitest/browser-preview": "5.0.3", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", - "@vitest/coverage-istanbul": "5.0.3", - "@vitest/coverage-v8": "5.0.3", - "@vitest/ui": "5.0.3", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" @@ -7851,16 +7542,6 @@ "@jridgewell/sourcemap-codec": "^1.6.0" } }, - "node_modules/vitest/node_modules/tinyexec": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", - "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -7925,9 +7606,9 @@ } }, "node_modules/workerpool": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", - "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.3.tgz", + "integrity": "sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==", "dev": true, "license": "Apache-2.0" }, @@ -8038,29 +7719,12 @@ } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs/node_modules/ansi-regex": { @@ -8106,32 +7770,10 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", - "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 7b00adb..c0a81dc 100644 --- a/package.json +++ b/package.json @@ -36,15 +36,15 @@ "author": "Matthew Poulton-White", "license": "MIT", "devDependencies": { - "@biomejs/biome": "2.5.15", + "@biomejs/biome": "2.6.2", "@d2t/vitest-ctrf-json-reporter": "1.3.0", "@types/node": "26.6.5", "@typescript/native": "npm:typescript@7.0.2", - "@vitest/coverage-v8": "5.0.3", - "codeceptjs": "4.2.0", - "tsup": "8.5.1", + "@vitest/coverage-v8": "5.0.5", + "codeceptjs": "4.2.2", + "tsup": "9.0.0", "typescript": "npm:@typescript/typescript6@6.0.2", - "vitest": "5.0.3", + "vitest": "5.0.5", "ajv": "8.20.0", "ajv-formats": "3.0.1" }, @@ -70,16 +70,16 @@ "ctrf": "0.5.0" }, "peerDependencies": { - "codeceptjs": "4.2.0" + "codeceptjs": "4.2.2" }, "overrides": { "codeceptjs": { "@xmldom/xmldom": "0.9.12", "axios": "1.20.0", - "uuid": "11.1.1", + "uuid": "14.0.3", "mocha": { - "diff": "8.0.3", - "serialize-javascript": "7.1.2" + "diff": "9.0.0", + "serialize-javascript": "7.1.4" } }, "esbuild": "0.28.2" diff --git a/tests/conformance-tooling.test.ts b/tests/conformance-tooling.test.ts index 76d72ec..e931a94 100644 --- a/tests/conformance-tooling.test.ts +++ b/tests/conformance-tooling.test.ts @@ -56,34 +56,27 @@ test("Codecept tooling / capabilities cover exactly the shared inventory", () => .sort(), ); }); -for (const id of Object.keys(expectedFailures)) { +for (const id of [ + "conformance/report/single-json-object", + "conformance/property/results.tests[].steps", + "conformance/property/results.tests[].steps[].name", + "conformance/property/results.tests[].steps[].status", +]) { const entry = catalogue.entries.find((e) => e.id === id); - test(`Codecept emission matcher / ${id} matches exact native rejection at both levels`, () => { + test(`Codecept actor steps / ${id} accepts native serialized output`, () => { + assert.deepEqual(expectedFailures, {}); + assert.ok(context.samples.some((s) => s.name === "actor-step")); + assert.equal(context.failedEmissions.length, 0); for (const level of ["unit", "integration"]) - assert.equal( - runConformance(entry, context, level).issue, - "native-step-output-rejected", - ); + runConformance(entry, context, level); }); - test(`Codecept emission matcher / ${id} rejects a successful emission`, () => { + test(`Codecept actor steps / ${id} rejects corrupted native output`, () => { const c = clone(); - c.failedEmissions = []; - c.samples.push({ ...c.samples[0], name: "actor-step" }); - assert.throws(() => runConformance(entry, c, "unit"), /Unexpected pass/); - }); - test(`Codecept emission matcher / ${id} rejects changed validation errors`, () => { - const c = clone(); - c.failedEmissions[0].emissionFailure.message += "; another failure"; - assert.throws(() => runConformance(entry, c, "unit")); - }); - test(`Codecept emission matcher / ${id} requires native failure evidence`, () => { - const c = clone(); - c.failedEmissions = []; - assert.throws(() => runConformance(entry, c, "unit"), /fixture missing/); - }); - test(`Codecept emission matcher / ${id} rejects a missing file caused by infrastructure`, () => { - const c = clone(); - c.failedEmissions[0].emissionFailure.errorType = "ENOENT"; + const sample = c.samples.find((s) => s.name === "actor-step"); + const step = sample.report.results.tests[0].steps[0]; + if (id.endsWith(".status")) step.status = "invalid"; + else delete step.name; + sample.bytes = Buffer.from(JSON.stringify(sample.report)); assert.throws(() => runConformance(entry, c, "unit")); }); } @@ -120,12 +113,12 @@ test("Codecept oracle / catches a schema-valid native status mapping change", () ), ); }); -test("Codecept emission evidence / never fabricates a report or bytes", () => { - assert.equal(context.failedEmissions.length, 1); - const evidence = context.failedEmissions[0]; - assert.equal(Object.hasOwn(evidence, "report"), false); - assert.equal(Object.hasOwn(evidence, "bytes"), false); - assert.equal(evidence.emissionFailure.absentOutput, true); +test("Codecept actor steps / retains actual bytes and native name evidence", () => { + assert.equal(context.failedEmissions.length, 0); + const sample = context.samples.find((s) => s.name === "actor-step"); + assert.deepEqual(JSON.parse(sample.bytes.toString()), sample.report); + assert.equal(sample.native.attempts[0].steps[0].name, "check"); + assert.equal(sample.report.results.tests[0].steps[0].name, "check"); }); test("Reporter results / completed records preserve pass, failure, pending and skip", () => { const modes = [ diff --git a/tests/integration/run.mjs b/tests/integration/run.mjs index ff00348..deae8e6 100644 --- a/tests/integration/run.mjs +++ b/tests/integration/run.mjs @@ -85,7 +85,7 @@ try { ); assert.equal(failed.message, "expected integration failure"); assert.equal(report.results.environment.buildNumber, 42); - console.log(`CodeceptJS 4.2.0 ${extension} plugin integration passed`); + console.log(`CodeceptJS ${extension} plugin integration passed`); } } finally { rmSync(dir, { recursive: true, force: true }); diff --git a/tests/standard/conformance/expected-failures.mjs b/tests/standard/conformance/expected-failures.mjs index a004124..28e5cb6 100644 --- a/tests/standard/conformance/expected-failures.mjs +++ b/tests/standard/conformance/expected-failures.mjs @@ -1,63 +1,16 @@ -import { AssertionError } from "node:assert/strict"; -import { checkCodeceptEntry } from "./codeceptjs-checks.mjs"; import { runExpectedFailure } from "./shared/expected-failures.mjs"; -const issue = { - issue: "native-step-output-rejected", - reason: - "Native CodeceptJS actor steps have no name field; producer validation prevents report emission", - followUp: - "https://github.com/ctrf-io/codeceptjs-ctrf-json-reporter/issues/26", - samples: { unit: ["actor-step"], integration: ["actor-step"] }, -}; -export const expectedFailures = Object.fromEntries( - [ - "conformance/report/single-json-object", - "conformance/property/results.tests[].steps", - "conformance/property/results.tests[].steps[].name", - "conformance/property/results.tests[].steps[].status", - ].map((id) => [id, issue]), -); -export function expectedFailure(entry, level) { - return expectedFailures[entry.id]?.samples[level] - ? expectedFailures[entry.id] - : undefined; -} -export function matchesKnownFailure(_entry, sample, record, error) { - const failure = sample.emissionFailure; - const message = - "CTRF validation failed:\n/results/tests/0/steps/0: must have required property 'name'"; - return ( - record.issue === "native-step-output-rejected" && - sample.name === "actor-step" && - error instanceof AssertionError && - failure?.absentOutput === true && - failure.errorType === "ValidationError" && - failure.message === message && - failure.nativeSteps.length === 1 && - failure.nativeSteps[0].name === undefined && - failure.nativeSteps[0].title === "check" && - failure.nativeSteps[0].helperMethod === "check" && - failure.nativeSteps[0].status === "success" && - error.message === - `${sample.name}: producer rejected native step output: ValidationError: ${message}` - ); +import { checkCodeceptEntry } from "./codeceptjs-checks.mjs"; +export const expectedFailures = {}; +export function expectedFailure() { + return undefined; } export function runConformance(entry, context, level) { - const record = expectedFailure(entry, level); - // Only the affected families receive failed-emission evidence. Every other - // family checks real captured reports. Shared code and IDs stay unchanged. - const evidence = record - ? { - ...context, - samples: [...context.samples, ...(context.failedEmissions ?? [])], - } - : context; return runExpectedFailure( entry, - evidence, + context, level, - record, + expectedFailure(entry, level), checkCodeceptEntry, - matchesKnownFailure, + () => false, ); } diff --git a/tests/standard/conformance/integration/prepare.mjs b/tests/standard/conformance/integration/prepare.mjs index 63ee7ff..fadd39d 100644 --- a/tests/standard/conformance/integration/prepare.mjs +++ b/tests/standard/conformance/integration/prepare.mjs @@ -82,7 +82,7 @@ export function integrationContext(root, diagnostics) { const samples = []; const failedEmissions = []; const scenarios = [ - { name: "actor-step", actor: true, passed: true, exit: undefined }, + { name: "actor-step", actor: true, passed: true, exit: 0 }, { name: "actor-minimal", actor: true, @@ -217,37 +217,7 @@ export function integrationContext(root, diagnostics) { path.join(diagnostics, `${name}-report.json`), ); } - if (!fileExists) { - assert.equal( - scenario.name, - "actor-step", - "Unexpected missing report", - ); - assert.ok([0, 1].includes(outcome.status)); - const text = outcome.stdout + outcome.stderr; - const match = text.match( - /ValidationError: (CTRF validation failed:\n\/results\/tests\/0\/steps\/0: must have required property 'name')/u, - ); - failedEmissions.push({ - name: "actor-step", - emissionFailure: { - message: match?.[1] ?? text, - errorType: match - ? "ValidationError" - : "UnexpectedEmissionFailure", - absentOutput: true, - nativeSteps: native.attempts.flatMap((a) => - (a.steps ?? []).map((step) => ({ - title: step.title, - helperMethod: step.helperMethod, - name: step.rawName, - status: step.status, - })), - ), - }, - }); - continue; - } + assert.ok(fileExists, `${scenario.name}: missing serialized report`); samples.push({ name, base64: bytes.toString("base64"),