From d0e9176001803b9e98c485c8d2794f23750a1944 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:03:21 +0000 Subject: [PATCH 1/7] ci(validate-deps): judge a pull request's OSV findings against the merge base, keep main's absolute daily scan, add a job timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On pull_request the OSV step now scans the pull request's lockfile and the merge base's (each under its own osv-scanner.toml) and fails only on an advisory the merge base does not match; inherited advisories become notices naming the advisory and its open finding card. A pull request that changes neither pnpm-lock.yaml nor osv-scanner.toml short-circuits to "lockfile unchanged — inherits main's verdict". schedule and workflow_dispatch keep the absolute scan unchanged. The job gains timeout-minutes: 15. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- .github/workflows/validate-deps.yml | 109 +++++- scripts/osv-base-relative.mjs | 527 ++++++++++++++++++++++++++++ 2 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 scripts/osv-base-relative.mjs diff --git a/.github/workflows/validate-deps.yml b/.github/workflows/validate-deps.yml index 0f49514e30a..308422b6a0d 100644 --- a/.github/workflows/validate-deps.yml +++ b/.github/workflows/validate-deps.yml @@ -17,6 +17,9 @@ on: # that governs it ever running on the PR that adds it. - 'osv-scanner.toml' - 'scripts/check-osv-exemptions.mjs' + # The pull_request verdict of the OSV step (#22082): an edit to it must + # be exercised on the PR that makes it. + - 'scripts/osv-base-relative.mjs' # Re-run when the workflow itself changes, so edits to these gates are # exercised on the PR that introduces them. - '.github/workflows/validate-deps.yml' @@ -32,6 +35,11 @@ jobs: validate: name: Validate Package Dependencies runs-on: ubuntu-latest + # Measured over the 100 most recent completed runs: p50 1.3 min, max + # 1.9 min (#22082). 15 leaves room for a cold pnpm store and two OSV + # scans on a pull request, and stops a hung step long before GitHub's + # 360-minute default would. + timeout-minutes: 15 permissions: contents: read issues: write @@ -138,12 +146,111 @@ jobs: node scripts/check-osv-exemptions.mjs --self-test node scripts/check-osv-exemptions.mjs + # Two verdicts, one scanner pin (#22082, ruling A). + # + # `schedule` and `workflow_dispatch` judge `main` ABSOLUTELY: every + # advisory the lockfile matches fails the step. That daily red is the + # signal that files the finding card, and this step is unchanged by the + # pull_request verdict below. + # + # A pull request is judged BASE-RELATIVELY: it answers for the advisories + # it introduces, not for the ones `main` already carries -- those are + # `main`'s to fix, and a pull request that changes no dependency cannot + # fix them. The steps below scan the pull request's lockfile and the merge + # base's, each under its own `osv-scanner.toml`, and + # scripts/osv-base-relative.mjs fails only on an advisory the merge base + # does not match; the others become notices naming the advisory and its + # open finding card. A scan that does not reach a verdict fails closed. + # + # The pin is an untagged upstream commit after the v2.5.0 tag whose + # action.yml runs the ghcr.io/google/osv-scanner-action:v2.5.0 image (the + # comment here used to read v2.3.8). Keep all three `uses:` lines on ONE + # sha: the two verdicts must come from the same scanner. - name: Audit dependencies for known vulnerabilities (OSV-Scanner) - uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # v2.3.8 + if: github.event_name != 'pull_request' + uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image with: scan-args: |- --lockfile=pnpm-lock.yaml + # The checkout is the pull request's test merge (refs/pull/N/merge) at + # depth 1. Its first parent is the base-branch commit GitHub merged the + # pull request into -- the merge base of the scanned tree and the base + # branch -- so ONE depth-1 fetch of that sha is all the history this needs + # (never fetch-depth: 0). When neither pnpm-lock.yaml nor osv-scanner.toml + # differs from it, the pull request cannot introduce an advisory and no + # scan runs. The ledger is part of the condition: a pull request that + # deletes an exemption changes the verdict without touching the lockfile. + - name: Stage the OSV comparison against the merge base (pull request) + id: osv-stage + if: github.event_name == 'pull_request' + run: | + node scripts/osv-base-relative.mjs --self-test + mapfile -t parents < <(git cat-file -p HEAD | sed -n 's/^parent //p') + if [ "${#parents[@]}" -ne 2 ]; then + echo "::error title=OSV base-relative verdict unavailable::HEAD is not the pull request's two-parent test merge (${#parents[@]} parent(s) found), so there is no merge base to judge against" + exit 1 + fi + base="${parents[0]}" + git fetch --no-tags --depth=1 origin "$base" + echo "base=$base" >> "$GITHUB_OUTPUT" + if git diff --quiet "$base" HEAD -- pnpm-lock.yaml osv-scanner.toml; then + echo "::notice title=OSV-Scanner verdict (base-relative)::lockfile unchanged — inherits main's verdict. pnpm-lock.yaml and osv-scanner.toml are identical to the merge base ${base:0:12}, so this pull request introduces no advisory; main's daily scheduled scan judges that lockfile." + echo "compare=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + for side in base head; do + if [ "$side" = base ]; then rev="$base"; else rev=HEAD; fi + mkdir -p ".osv-compare/$side" + git show "$rev:pnpm-lock.yaml" > ".osv-compare/$side/pnpm-lock.yaml" + git show "$rev:osv-scanner.toml" > ".osv-compare/$side/osv-scanner.toml" + done + echo "compare=true" >> "$GITHUB_OUTPUT" + + # Both scans may exit non-zero (findings), so neither stops the job; the + # judge below reads each result file against its step's outcome. + # `--config` is explicit because the scanner looks for osv-scanner.toml + # only in the scanned lockfile's own directory (measured on 2.5.0: a + # ledger one directory up is not applied). + - name: OSV-Scanner on the merge base's lockfile (pull request) + id: osv-scan-base + if: steps.osv-stage.outputs.compare == 'true' + continue-on-error: true + uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image + with: + scan-args: |- + --config=.osv-compare/base/osv-scanner.toml + --format=json + --output-file=.osv-compare/base/results.json + --lockfile=.osv-compare/base/pnpm-lock.yaml + + - name: OSV-Scanner on the pull request's lockfile (pull request) + id: osv-scan-head + if: steps.osv-stage.outputs.compare == 'true' + continue-on-error: true + uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image + with: + scan-args: |- + --config=.osv-compare/head/osv-scanner.toml + --format=json + --output-file=.osv-compare/head/results.json + --lockfile=.osv-compare/head/pnpm-lock.yaml + + - name: Judge the pull request's OSV findings against the merge base's (pull request) + if: steps.osv-stage.outputs.compare == 'true' + env: + BASE_OUTCOME: ${{ steps.osv-scan-base.outcome }} + HEAD_OUTCOME: ${{ steps.osv-scan-head.outcome }} + BASE_SHA: ${{ steps.osv-stage.outputs.base }} + # Read-only use: the open-issue search that names an inherited + # advisory's finding card. The verdict never depends on it. + GITHUB_TOKEN: ${{ github.token }} + run: | + node scripts/osv-base-relative.mjs \ + --base-results .osv-compare/base/results.json --base-outcome "$BASE_OUTCOME" \ + --head-results .osv-compare/head/results.json --head-outcome "$HEAD_OUTCOME" \ + --base-sha "$BASE_SHA" + - name: List outdated packages if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' run: | diff --git a/scripts/osv-base-relative.mjs b/scripts/osv-base-relative.mjs new file mode 100644 index 00000000000..91837a5108a --- /dev/null +++ b/scripts/osv-base-relative.mjs @@ -0,0 +1,527 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * osv-base-relative -- the pull_request verdict of the OSV step in + * .github/workflows/validate-deps.yml: judge the pull request's OSV findings + * against the merge base's, not against an empty set. + * + * node scripts/osv-base-relative.mjs \ + * --base-results --base-outcome \ + * --head-results --head-outcome [--base-sha ] + * node scripts/osv-base-relative.mjs --self-test + * + * ## What it decides (the ruling recorded on the card that asked for it) + * + * The workflow scans two lockfiles with the same pinned OSV-Scanner action and + * hands this script both JSON result files and both step outcomes: + * + * - the merge base's `pnpm-lock.yaml`, judged by the merge base's own + * `osv-scanner.toml` -- exactly what `main`'s scheduled scan judges; + * - the pull request's `pnpm-lock.yaml`, judged by the pull request's own + * `osv-scanner.toml` -- exactly what the absolute scan judged before. + * + * An advisory matched by BOTH is INHERITED: it is reported in a notice that + * names it and, when one exists, the open finding card that names it, and it + * does not fail the step -- it is `main`'s to fix, and `main`'s daily scan is + * the signal that files its card. An advisory matched ONLY by the pull + * request's lockfile is INTRODUCED and fails the step, as the absolute scan + * did. The unit is the advisory id; the package versions each side matched are + * printed beside it, so a pull request that adds another vulnerable version of + * an inherited advisory shows that in its notice. + * + * Each side is scanned with its OWN ledger. Scanning the base with the pull + * request's ledger would let a pull request that deletes an exemption read the + * advisory it re-exposes as inherited; scanning the head with the base's would + * fail the pull request that adds one. + * + * ## Fail-closed, because the outcome alone cannot tell findings from errors + * + * The action is a container action, so the workflow sees only the step's + * outcome, never the scanner's exit code. Measured against osv-scanner 2.5.0 + * (the binary the pinned action's image ships), with `--config` and + * `--output-file` set as the workflow sets them: + * + * exit 0 result file written, zero findings + * exit 1 result file written, one or more findings + * exit 127 NO result file (unreadable or unknown-key config, missing + * lockfile, or a failed database query) + * exit 128 NO result file (no packages found) -- and the action's wrapper + * rewrites 128 to 0, so this one arrives as a SUCCESS outcome + * + * So a side is read only when its result file parses AND its outcome agrees + * with its finding count (success <=> zero, failure <=> some). Anything else + * is a scan that did not reach a verdict, and the step fails naming the side. + * The upstream `osv-reporter` was not used for this reason: it reads a missing + * new-side result file as "no findings" and exits 0. + * + * Exit codes: 0 = nothing introduced; 1 = an advisory introduced, or a side + * without a verdict; 2 = usage. + */ + +import { existsSync, readFileSync } from 'node:fs'; + +const OUTCOMES = new Set(['success', 'failure']); +const ANCHOR_LOOKUP_CAP = 10; + +// ── Reading one side ──────────────────────────────────────────────────────── + +/** + * Read one scan's result file against its step outcome. + * Returns `{ findings: Map> }` or `{ error }`. + */ +export function readSide(label, text, outcome) { + if (!OUTCOMES.has(outcome)) { + return { error: `the OSV-Scanner step on the ${label} lockfile has outcome '${outcome}', not a verdict` }; + } + if (text === null) { + return { + error: + `the OSV-Scanner step on the ${label} lockfile wrote no result file (outcome '${outcome}'): ` + + 'the scanner errored before judging (unreadable ledger, missing lockfile, no packages, or a failed ' + + 'database query) -- read that step\'s log', + }; + } + let doc; + try { + doc = JSON.parse(text); + } catch (e) { + return { error: `the ${label} result file is not JSON (${e.message})` }; + } + if (!doc || !Array.isArray(doc.results)) { + return { error: `the ${label} result file has no 'results' array` }; + } + const findings = new Map(); + let count = 0; + for (const source of doc.results) { + for (const pkg of source?.packages ?? []) { + const name = pkg?.package?.name; + const version = pkg?.package?.version; + for (const vuln of pkg?.vulnerabilities ?? []) { + if (typeof vuln?.id !== 'string' || vuln.id === '') { + return { error: `the ${label} result file has a vulnerability without an id` }; + } + count += 1; + if (!findings.has(vuln.id)) findings.set(vuln.id, new Set()); + findings.get(vuln.id).add(`${name}@${version}`); + } + } + } + if (outcome === 'success' && count > 0) { + return { + error: + `the ${label} scan reports ${count} finding(s) but its step succeeded -- the outcome and the result ` + + 'file disagree, so neither is read', + }; + } + if (outcome === 'failure' && count === 0) { + return { + error: + `the OSV-Scanner step on the ${label} lockfile failed with zero findings in its result file: ` + + 'the scanner errored rather than judging -- read that step\'s log', + }; + } + return { findings }; +} + +// ── The comparison ────────────────────────────────────────────────────────── + +const sorted = (set) => [...set].sort(); + +/** Split the head's findings into introduced and inherited; list the resolved. */ +export function compare(base, head) { + const introduced = []; + const inherited = []; + const resolved = []; + for (const id of sorted(head.keys())) { + const headPkgs = sorted(head.get(id)); + if (base.has(id)) inherited.push({ id, headPkgs, basePkgs: sorted(base.get(id)) }); + else introduced.push({ id, headPkgs }); + } + for (const id of sorted(base.keys())) { + if (!head.has(id)) resolved.push({ id, basePkgs: sorted(base.get(id)) }); + } + return { introduced, inherited, resolved }; +} + +// ── Anchor finding cards (informational; never part of the verdict) ───────── + +/** + * Look up open issues that name each id in their title or body. + * Returns Map. + */ +export async function findAnchors(ids, { repo, token, fetchImpl }) { + const out = new Map(); + for (const [i, id] of ids.entries()) { + if (i >= ANCHOR_LOOKUP_CAP) { + out.set(id, { error: `not looked up (lookup cap ${ANCHOR_LOOKUP_CAP})` }); + continue; + } + if (!repo) { + out.set(id, { error: 'not looked up (GITHUB_REPOSITORY unset)' }); + continue; + } + const q = `repo:${repo} is:issue is:open in:title,body "${id}"`; + const url = `https://api.github.com/search/issues?per_page=10&q=${encodeURIComponent(q)}`; + const headers = { + accept: 'application/vnd.github+json', + 'user-agent': 'objectstack-osv-base-relative', + 'x-github-api-version': '2022-11-28', + }; + if (token) headers.authorization = `Bearer ${token}`; + try { + const res = await fetchImpl(url, { headers, signal: AbortSignal.timeout(15000) }); + if (!res.ok) { + out.set(id, { error: `lookup answered HTTP ${res.status}` }); + continue; + } + const body = await res.json(); + const cards = (body?.items ?? []) + .filter((it) => !it.pull_request) + .filter((it) => `${it.title ?? ''}\n${it.body ?? ''}`.includes(id)) + .map((it) => ({ number: it.number, title: it.title ?? '' })) + .sort((a, b) => a.number - b.number); + out.set(id, { cards }); + } catch (e) { + out.set(id, { error: `lookup failed (${e?.name ?? 'Error'}: ${e?.message ?? e})` }); + } + } + return out; +} + +// ── Rendering ─────────────────────────────────────────────────────────────── + +// The runner's workflow-command escaping (the same rules as @actions/core). +export const escapeData = (s) => String(s).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); +export const escapeProperty = (s) => escapeData(s).replace(/:/g, '%3A').replace(/,/g, '%2C'); +const command = (kind, title, message) => `::${kind} title=${escapeProperty(title)}::${escapeData(message)}`; + +function anchorText(anchor) { + if (!anchor) return 'anchor finding card: not looked up'; + if (anchor.error) return `anchor finding card: ${anchor.error}`; + if (anchor.cards.length === 0) { + return 'no open issue names it yet; main\'s daily scheduled scan is the signal that files its finding card'; + } + return `anchor finding card: ${anchor.cards.slice(0, 3).map((c) => `#${c.number}`).join(', ')}`; +} + +/** Every line the verdict prints, in order, and the exit code. Pure. */ +export function render({ errors, verdict, anchors, baseSha }) { + const lines = []; + const base = baseSha ? `the merge base (${baseSha.slice(0, 12)})` : 'the merge base'; + if (errors.length > 0) { + for (const e of errors) lines.push(command('error', 'OSV base-relative verdict unavailable', e)); + lines.push('OSV base-relative verdict: NONE -- a scan did not reach a verdict, so this step fails closed.'); + return { lines, code: 1 }; + } + const { introduced, inherited, resolved } = verdict; + for (const f of inherited) { + lines.push( + command( + 'notice', + 'OSV advisory inherited from the merge base', + `${f.id} (here: ${f.headPkgs.join(', ')}; at the merge base: ${f.basePkgs.join(', ')}) is matched by ` + + `${base}'s lockfile too, so it is main's to fix and does not fail this pull request; ` + + `${anchorText(anchors.get(f.id))}.`, + ), + ); + } + for (const f of introduced) { + lines.push( + command( + 'error', + 'OSV advisory introduced by this pull request', + `${f.id} (${f.headPkgs.join(', ')}) is matched by this pull request's lockfile and not by ${base}'s. ` + + 'Take the fixed version; an advisory with no fix goes through the osv-scanner.toml ledger in its own PR.', + ), + ); + } + lines.push( + `OSV base-relative verdict against ${base}: ${introduced.length} introduced, ` + + `${inherited.length} inherited, ${resolved.length} resolved.`, + ); + for (const f of introduced) lines.push(` introduced ${f.id} ${f.headPkgs.join(', ')}`); + for (const f of inherited) lines.push(` inherited ${f.id} ${f.headPkgs.join(', ')}`); + for (const f of resolved) lines.push(` resolved ${f.id} ${f.basePkgs.join(', ')}`); + return { lines, code: introduced.length > 0 ? 1 : 0 }; +} + +// ── The whole run, with its I/O injected ──────────────────────────────────── + +export async function run({ base, head, baseSha, repo, token, fetchImpl }) { + const errors = []; + const b = readSide('merge base\'s', base.text, base.outcome); + const h = readSide('pull request\'s', head.text, head.outcome); + if (b.error) errors.push(b.error); + if (h.error) errors.push(h.error); + if (errors.length > 0) return render({ errors, verdict: null, anchors: new Map(), baseSha }); + const verdict = compare(b.findings, h.findings); + const anchors = await findAnchors( + verdict.inherited.map((f) => f.id), + { repo, token, fetchImpl }, + ); + return render({ errors, verdict, anchors, baseSha }); +} + +// ── Self-test ─────────────────────────────────────────────────────────────── + +// The pinned minimum number of cases: a battery that silently shrinks below it +// fails, so "every case held" cannot be printed by a battery that never ran. +const SELF_TEST_MIN_CASES = 18; +let selfTestReachedVerdict = false; + +const result = (rows) => + JSON.stringify({ + results: [ + { + source: { path: '/github/workspace/.osv-compare/x/pnpm-lock.yaml', type: 'lockfile' }, + packages: rows.map(([name, version, ids]) => ({ + package: { name, version, ecosystem: 'npm' }, + vulnerabilities: ids.map((id) => ({ id })), + groups: ids.map((id) => ({ ids: [id] })), + })), + }, + ].filter((r) => r.packages.length > 0), + }); + +const fakeFetch = (cardsById, { status = 200, throws = false } = {}) => async (url) => { + if (throws) throw new Error('network down'); + const q = decodeURIComponent(new URL(url).searchParams.get('q')); + const id = /"([^"]+)"/.exec(q)[1]; + return { + ok: status === 200, + status, + json: async () => ({ + items: (cardsById[id] ?? []).map((c) => ({ number: c.number, title: c.title, body: c.body ?? '' })), + }), + }; +}; + +async function selfTest() { + const SHARP = 'GHSA-wq5f-xc86-pv6w'; + const QUOTE = 'GHSA-pqg4-j6r4-53mv'; + const MINI = 'GHSA-xvch-5gv4-984h'; + const mainRed = result([ + ['sharp', '0.35.4', [SHARP]], + ['shell-quote', '1.10.0', [QUOTE]], + ]); + const cards = { [SHARP]: [{ number: 22013, title: `[finding] sharp ${SHARP}` }] }; + const ok = (code) => (r) => r.code === code; + const has = (re) => (r) => r.lines.some((l) => re.test(l)); + const hasNot = (re) => (r) => !r.lines.some((l) => re.test(l)); + const count = (re, n) => (r) => r.lines.filter((l) => re.test(l)).length === n; + const side = (text, outcome) => ({ text, outcome }); + + const cases = [ + { + name: 'inherited only: passes, a notice per advisory naming it and its anchor card', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch(cards), + expect: [ + ok(0), + count(/^::notice title=OSV advisory inherited/, 2), + has(new RegExp(`^::notice .*::${SHARP} .*anchor finding card: #22013`)), + has(new RegExp(`^::notice .*::${QUOTE} .*no open issue names it yet`)), + hasNot(/^::error/), + ], + }, + { + name: 'introduced: fails, the error names only the introduced advisory', + args: { + base: side(mainRed, 'failure'), + head: side( + result([ + ['sharp', '0.35.4', [SHARP]], + ['shell-quote', '1.10.0', [QUOTE]], + ['minimist', '1.2.5', [MINI]], + ]), + 'failure', + ), + }, + fetch: fakeFetch(cards), + expect: [ + ok(1), + count(/^::error/, 1), + has(new RegExp(`^::error title=OSV advisory introduced by this pull request::${MINI} \\(minimist@1.2.5\\)`)), + count(/^::notice/, 2), + ], + }, + { + name: 'introduced against a clean base: fails', + args: { base: side(result([]), 'success'), head: side(result([['minimist', '1.2.5', [MINI]]]), 'failure') }, + fetch: fakeFetch({}), + expect: [ok(1), has(new RegExp(`^::error .*::${MINI} `)), hasNot(/^::notice/)], + }, + { + name: 'clean on both sides: passes silently', + args: { base: side(result([]), 'success'), head: side(result([]), 'success') }, + fetch: fakeFetch({}), + expect: [ok(0), hasNot(/^::/), has(/0 introduced, 0 inherited, 0 resolved/)], + }, + { + name: 'resolved only (the pull request takes the fix): passes', + args: { base: side(mainRed, 'failure'), head: side(result([]), 'success') }, + fetch: fakeFetch(cards), + expect: [ok(0), hasNot(/^::/), has(/0 introduced, 0 inherited, 2 resolved/)], + }, + { + name: 'same advisory at another version on the head: inherited, both sides\' versions printed', + args: { + base: side(result([['sharp', '0.35.4', [SHARP]]]), 'failure'), + head: side(result([['sharp', '0.35.4', [SHARP]], ['sharp', '0.34.0', [SHARP]]]), 'failure'), + }, + fetch: fakeFetch(cards), + expect: [ok(0), has(/here: sharp@0\.34\.0, sharp@0\.35\.4; at the merge base: sharp@0\.35\.4/)], + }, + { + name: 'head scan wrote no result file: fails closed, never reads as clean', + args: { base: side(mainRed, 'failure'), head: side(null, 'failure') }, + fetch: fakeFetch(cards), + expect: [ok(1), has(/^::error title=OSV base-relative verdict unavailable::.*pull request's lockfile wrote no result file/)], + }, + { + name: 'head scan answered success with no result file (the wrapper\'s 128 -> 0): fails closed', + args: { base: side(result([]), 'success'), head: side(null, 'success') }, + fetch: fakeFetch({}), + expect: [ok(1), has(/wrote no result file \(outcome 'success'\)/)], + }, + { + name: 'base scan wrote no result file: fails closed, never reads the head as all-introduced or all-clean', + args: { base: side(null, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch(cards), + expect: [ok(1), has(/merge base's lockfile wrote no result file/), hasNot(/^::notice/)], + }, + { + name: 'failure outcome with zero findings: the scanner errored, fails closed', + args: { base: side(result([]), 'success'), head: side(result([]), 'failure') }, + fetch: fakeFetch({}), + expect: [ok(1), has(/failed with zero findings/)], + }, + { + name: 'success outcome with findings: outcome and file disagree, fails closed', + args: { base: side(result([]), 'success'), head: side(mainRed, 'success') }, + fetch: fakeFetch(cards), + expect: [ok(1), has(/outcome and the result file disagree/)], + }, + { + name: 'a skipped or cancelled scan step is not a verdict', + args: { base: side(result([]), 'skipped'), head: side(result([]), 'cancelled') }, + fetch: fakeFetch({}), + expect: [ok(1), count(/^::error title=OSV base-relative verdict unavailable/, 2)], + }, + { + name: 'a result file that is not JSON fails closed', + args: { base: side('{', 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch(cards), + expect: [ok(1), has(/is not JSON/)], + }, + { + name: 'the anchor lookup failing never changes the verdict', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch(cards, { throws: true }), + expect: [ok(0), count(/anchor finding card: lookup failed \(Error: network down\)/, 2)], + }, + { + name: 'an anchor lookup answering 403 is named, and the verdict holds', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch(cards, { status: 403 }), + expect: [ok(0), count(/anchor finding card: lookup answered HTTP 403/, 2)], + }, + { + name: 'a search hit that does not name the id is not an anchor', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch({ [SHARP]: [{ number: 1, title: 'sharp is slow' }] }), + expect: [ok(0), has(new RegExp(`::${SHARP} .*no open issue names it yet`))], + }, + ]; + + const escapeCases = [ + ['data escapes % CR LF', escapeData('a%b\r\nc') === 'a%25b%0D%0Ac'], + ['property escapes : and ,', escapeProperty('a:b,c%') === 'a%3Ab%2Cc%25'], + ]; + + const failures = []; + let ran = 0; + for (const c of cases) { + ran += 1; + const r = await run({ ...c.args, baseSha: 'abcdef0123456789', repo: 'o/r', token: 't', fetchImpl: c.fetch }); + c.expect.forEach((check, i) => { + if (!check(r)) failures.push(`${c.name} -- expectation ${i + 1} failed; output was:\n ${r.lines.join('\n ')}`); + }); + } + for (const [name, held] of escapeCases) { + ran += 1; + if (!held) failures.push(name); + } + if (ran < SELF_TEST_MIN_CASES) { + failures.push(`only ${ran} case(s) ran, below the pinned floor of ${SELF_TEST_MIN_CASES}`); + } + + // The verdict text is indented so no self-test line can start with a + // workflow command, whatever a failing case's output contains. + if (failures.length > 0) { + console.error(`osv-base-relative self-test: ${failures.length} failure(s) over ${ran} case(s)`); + for (const f of failures) console.error(` - ${f.replace(/\n/g, '\n ')}`); + selfTestReachedVerdict = true; + return 1; + } + console.log(`osv-base-relative self-test: ${ran} case(s), all held (floor ${SELF_TEST_MIN_CASES})`); + selfTestReachedVerdict = true; + return 0; +} + +// ── Entry ─────────────────────────────────────────────────────────────────── + +function parseArgs(argv) { + const opts = {}; + for (let i = 0; i < argv.length; i += 1) { + const a = argv[i]; + if (!a.startsWith('--')) return { usage: `unexpected argument '${a}'` }; + const key = a.slice(2); + if (key === 'self-test') { + opts.selfTest = true; + continue; + } + if (!['base-results', 'base-outcome', 'head-results', 'head-outcome', 'base-sha'].includes(key)) { + return { usage: `unknown flag '${a}'` }; + } + if (i + 1 >= argv.length) return { usage: `flag '${a}' needs a value` }; + opts[key] = argv[(i += 1)]; + } + if (!opts.selfTest) { + for (const k of ['base-results', 'base-outcome', 'head-results', 'head-outcome']) { + if (opts[k] === undefined) return { usage: `missing --${k}` }; + } + } + return opts; +} + +async function main() { + const opts = parseArgs(process.argv.slice(2)); + if (opts.usage) { + console.error(`osv-base-relative: ${opts.usage}`); + console.error('usage: osv-base-relative.mjs --base-results F --base-outcome O --head-results F --head-outcome O [--base-sha S] | --self-test'); + process.exit(2); + } + if (opts.selfTest) { + const code = await selfTest(); + if (!selfTestReachedVerdict) { + console.error('osv-base-relative self-test: returned without reaching its verdict -- treated as a failure'); + process.exit(1); + } + process.exit(code); + } + const read = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : null); + const { lines, code } = await run({ + base: { text: read(opts['base-results']), outcome: opts['base-outcome'] }, + head: { text: read(opts['head-results']), outcome: opts['head-outcome'] }, + baseSha: opts['base-sha'], + repo: process.env.GITHUB_REPOSITORY, + token: process.env.GITHUB_TOKEN, + fetchImpl: globalThis.fetch, + }); + for (const l of lines) console.log(l); + process.exit(code); +} + +await main(); From bf15ed3831ade6ad9c447043bab79b49b096d785 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:08:56 +0000 Subject: [PATCH 2/7] ci(validate-deps): find the anchor finding card through the repo-scoped open-issue listing, not the search API The search index lags and this session cannot reach it to verify the query; one listing of the open issues serves every id, finds a card filed minutes ago, and skips seat board posts that quote advisories in passing. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- scripts/osv-base-relative.mjs | 163 ++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 55 deletions(-) diff --git a/scripts/osv-base-relative.mjs b/scripts/osv-base-relative.mjs index 91837a5108a..f35d31ee107 100644 --- a/scripts/osv-base-relative.mjs +++ b/scripts/osv-base-relative.mjs @@ -62,7 +62,7 @@ import { existsSync, readFileSync } from 'node:fs'; const OUTCOMES = new Set(['success', 'failure']); -const ANCHOR_LOOKUP_CAP = 10; +const ANCHOR_PAGE_CAP = 10; // ── Reading one side ──────────────────────────────────────────────────────── @@ -147,44 +147,53 @@ export function compare(base, head) { // ── Anchor finding cards (informational; never part of the verdict) ───────── /** - * Look up open issues that name each id in their title or body. + * Find the open issues whose title or body names each id. One repo-scoped + * listing of the open issues serves every id: it has no search-index lag, so a + * finding card filed minutes ago is found, and it does not depend on the card + * carrying a label (the red-main cards have not all carried the same ones). + * A seat's board post (label `pm:seat`) quotes advisories in passing and is + * never a finding card, so it is skipped. * Returns Map. */ +const isBoardPost = (it) => (it?.labels ?? []).some((l) => (typeof l === 'string' ? l : l?.name) === 'pm:seat'); + export async function findAnchors(ids, { repo, token, fetchImpl }) { const out = new Map(); - for (const [i, id] of ids.entries()) { - if (i >= ANCHOR_LOOKUP_CAP) { - out.set(id, { error: `not looked up (lookup cap ${ANCHOR_LOOKUP_CAP})` }); - continue; - } - if (!repo) { - out.set(id, { error: 'not looked up (GITHUB_REPOSITORY unset)' }); - continue; - } - const q = `repo:${repo} is:issue is:open in:title,body "${id}"`; - const url = `https://api.github.com/search/issues?per_page=10&q=${encodeURIComponent(q)}`; - const headers = { - accept: 'application/vnd.github+json', - 'user-agent': 'objectstack-osv-base-relative', - 'x-github-api-version': '2022-11-28', - }; - if (token) headers.authorization = `Bearer ${token}`; - try { - const res = await fetchImpl(url, { headers, signal: AbortSignal.timeout(15000) }); - if (!res.ok) { - out.set(id, { error: `lookup answered HTTP ${res.status}` }); - continue; + const failAll = (error) => { + for (const id of ids) out.set(id, { error }); + return out; + }; + if (ids.length === 0) return out; + if (!repo) return failAll('not looked up (GITHUB_REPOSITORY unset)'); + const headers = { + accept: 'application/vnd.github+json', + 'user-agent': 'objectstack-osv-base-relative', + 'x-github-api-version': '2022-11-28', + }; + if (token) headers.authorization = `Bearer ${token}`; + const issues = []; + try { + for (let page = 1; ; page += 1) { + if (page > ANCHOR_PAGE_CAP) { + return failAll(`lookup incomplete (more than ${ANCHOR_PAGE_CAP * 100} open issues and pull requests)`); } - const body = await res.json(); - const cards = (body?.items ?? []) - .filter((it) => !it.pull_request) - .filter((it) => `${it.title ?? ''}\n${it.body ?? ''}`.includes(id)) - .map((it) => ({ number: it.number, title: it.title ?? '' })) - .sort((a, b) => a.number - b.number); - out.set(id, { cards }); - } catch (e) { - out.set(id, { error: `lookup failed (${e?.name ?? 'Error'}: ${e?.message ?? e})` }); + const url = `https://api.github.com/repos/${repo}/issues?state=open&per_page=100&page=${page}`; + const res = await fetchImpl(url, { headers, signal: AbortSignal.timeout(15000) }); + if (!res.ok) return failAll(`lookup answered HTTP ${res.status}`); + const batch = await res.json(); + if (!Array.isArray(batch)) return failAll('lookup answered something other than a list'); + issues.push(...batch.filter((it) => !it?.pull_request && !isBoardPost(it))); + if (batch.length < 100) break; } + } catch (e) { + return failAll(`lookup failed (${e?.name ?? 'Error'}: ${e?.message ?? e})`); + } + for (const id of ids) { + const cards = issues + .filter((it) => `${it.title ?? ''}\n${it.body ?? ''}`.includes(id)) + .map((it) => ({ number: it.number, title: it.title ?? '' })) + .sort((a, b) => a.number - b.number); + out.set(id, { cards }); } return out; } @@ -208,7 +217,7 @@ function anchorText(anchor) { /** Every line the verdict prints, in order, and the exit code. Pure. */ export function render({ errors, verdict, anchors, baseSha }) { const lines = []; - const base = baseSha ? `the merge base (${baseSha.slice(0, 12)})` : 'the merge base'; + const at = baseSha ? ` (${baseSha.slice(0, 12)})` : ''; if (errors.length > 0) { for (const e of errors) lines.push(command('error', 'OSV base-relative verdict unavailable', e)); lines.push('OSV base-relative verdict: NONE -- a scan did not reach a verdict, so this step fails closed.'); @@ -221,7 +230,7 @@ export function render({ errors, verdict, anchors, baseSha }) { 'notice', 'OSV advisory inherited from the merge base', `${f.id} (here: ${f.headPkgs.join(', ')}; at the merge base: ${f.basePkgs.join(', ')}) is matched by ` + - `${base}'s lockfile too, so it is main's to fix and does not fail this pull request; ` + + `the merge base's lockfile${at} too, so it is main's to fix and does not fail this pull request; ` + `${anchorText(anchors.get(f.id))}.`, ), ); @@ -231,13 +240,13 @@ export function render({ errors, verdict, anchors, baseSha }) { command( 'error', 'OSV advisory introduced by this pull request', - `${f.id} (${f.headPkgs.join(', ')}) is matched by this pull request's lockfile and not by ${base}'s. ` + + `${f.id} (${f.headPkgs.join(', ')}) is matched by this pull request's lockfile and not by the merge base's${at}. ` + 'Take the fixed version; an advisory with no fix goes through the osv-scanner.toml ledger in its own PR.', ), ); } lines.push( - `OSV base-relative verdict against ${base}: ${introduced.length} introduced, ` + + `OSV base-relative verdict against the merge base${at}: ${introduced.length} introduced, ` + `${inherited.length} inherited, ${resolved.length} resolved.`, ); for (const f of introduced) lines.push(` introduced ${f.id} ${f.headPkgs.join(', ')}`); @@ -267,7 +276,7 @@ export async function run({ base, head, baseSha, repo, token, fetchImpl }) { // The pinned minimum number of cases: a battery that silently shrinks below it // fails, so "every case held" cannot be printed by a battery that never ran. -const SELF_TEST_MIN_CASES = 18; +const SELF_TEST_MIN_CASES = 23; let selfTestReachedVerdict = false; const result = (rows) => @@ -284,17 +293,16 @@ const result = (rows) => ].filter((r) => r.packages.length > 0), }); -const fakeFetch = (cardsById, { status = 200, throws = false } = {}) => async (url) => { +// A fake of the open-issues listing: `cards` is the open issues (and pull +// requests) the repository holds, served 100 to a page like the real endpoint. +const fakeFetch = (cards, { status = 200, throws = false } = {}) => async (url) => { if (throws) throw new Error('network down'); - const q = decodeURIComponent(new URL(url).searchParams.get('q')); - const id = /"([^"]+)"/.exec(q)[1]; - return { - ok: status === 200, - status, - json: async () => ({ - items: (cardsById[id] ?? []).map((c) => ({ number: c.number, title: c.title, body: c.body ?? '' })), - }), - }; + const u = new URL(url); + if (u.pathname !== '/repos/o/r/issues' || u.searchParams.get('state') !== 'open') { + return { ok: false, status: 404, json: async () => ({}) }; + } + const page = Number(u.searchParams.get('page')); + return { ok: status === 200, status, json: async () => cards.slice((page - 1) * 100, page * 100) }; }; async function selfTest() { @@ -305,7 +313,10 @@ async function selfTest() { ['sharp', '0.35.4', [SHARP]], ['shell-quote', '1.10.0', [QUOTE]], ]); - const cards = { [SHARP]: [{ number: 22013, title: `[finding] sharp ${SHARP}` }] }; + const cards = [ + { number: 22013, title: `[finding] main's lockfile matches sharp 0.35.4 ${SHARP}`, body: 'measured' }, + { number: 22000, title: 'an unrelated open card', body: 'nothing to see' }, + ]; const ok = (code) => (r) => r.code === code; const has = (re) => (r) => r.lines.some((l) => re.test(l)); const hasNot = (re) => (r) => !r.lines.some((l) => re.test(l)); @@ -349,13 +360,13 @@ async function selfTest() { { name: 'introduced against a clean base: fails', args: { base: side(result([]), 'success'), head: side(result([['minimist', '1.2.5', [MINI]]]), 'failure') }, - fetch: fakeFetch({}), + fetch: fakeFetch([]), expect: [ok(1), has(new RegExp(`^::error .*::${MINI} `)), hasNot(/^::notice/)], }, { name: 'clean on both sides: passes silently', args: { base: side(result([]), 'success'), head: side(result([]), 'success') }, - fetch: fakeFetch({}), + fetch: fakeFetch([]), expect: [ok(0), hasNot(/^::/), has(/0 introduced, 0 inherited, 0 resolved/)], }, { @@ -382,7 +393,7 @@ async function selfTest() { { name: 'head scan answered success with no result file (the wrapper\'s 128 -> 0): fails closed', args: { base: side(result([]), 'success'), head: side(null, 'success') }, - fetch: fakeFetch({}), + fetch: fakeFetch([]), expect: [ok(1), has(/wrote no result file \(outcome 'success'\)/)], }, { @@ -394,7 +405,7 @@ async function selfTest() { { name: 'failure outcome with zero findings: the scanner errored, fails closed', args: { base: side(result([]), 'success'), head: side(result([]), 'failure') }, - fetch: fakeFetch({}), + fetch: fakeFetch([]), expect: [ok(1), has(/failed with zero findings/)], }, { @@ -406,7 +417,7 @@ async function selfTest() { { name: 'a skipped or cancelled scan step is not a verdict', args: { base: side(result([]), 'skipped'), head: side(result([]), 'cancelled') }, - fetch: fakeFetch({}), + fetch: fakeFetch([]), expect: [ok(1), count(/^::error title=OSV base-relative verdict unavailable/, 2)], }, { @@ -430,9 +441,51 @@ async function selfTest() { { name: 'a search hit that does not name the id is not an anchor', args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, - fetch: fakeFetch({ [SHARP]: [{ number: 1, title: 'sharp is slow' }] }), + fetch: fakeFetch([{ number: 1, title: 'sharp is slow', body: 'GHSA-wq5f is not the whole id' }]), expect: [ok(0), has(new RegExp(`::${SHARP} .*no open issue names it yet`))], }, + { + name: 'the anchor is found on the second page, and in the body as well as the title', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch([ + ...Array.from({ length: 100 }, (_, i) => ({ number: 100 + i, title: 'filler', body: '' })), + { number: 21000, title: 'red main again', body: `the scan matches ${QUOTE} on shell-quote` }, + ]), + expect: [ok(0), has(new RegExp(`::${QUOTE} .*anchor finding card: #21000`))], + }, + { + name: 'a seat board post that quotes the id is not a finding card', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch([{ number: 6024, title: '[PM seat] domain:cli', body: `exempt ${SHARP}`, labels: [{ name: 'pm:seat' }] }]), + expect: [ok(0), has(new RegExp(`::${SHARP} .*no open issue names it yet`))], + }, + { + name: 'a pull request that names the id is not a finding card', + args: { base: side(mainRed, 'failure'), head: side(mainRed, 'failure') }, + fetch: fakeFetch([{ number: 5, title: `fix ${SHARP}`, body: '', pull_request: { url: 'x' } }]), + expect: [ok(0), has(new RegExp(`::${SHARP} .*no open issue names it yet`))], + }, + { + name: 'a listing that is not a list is named, and the verdict holds', + args: { base: side(mainRed, 'failure'), head: side(result([['minimist', '1.2.5', [MINI]]]), 'failure') }, + fetch: async () => ({ ok: true, status: 200, json: async () => ({ message: 'odd' }) }), + expect: [ok(1), has(/^::error title=OSV advisory introduced/), hasNot(/^::notice/)], + }, + { + name: 'introduced and inherited together: one error, one notice per inherited advisory', + args: { + base: side(result([['sharp', '0.35.4', [SHARP]]]), 'failure'), + head: side(result([['sharp', '0.35.4', [SHARP]], ['minimist', '1.2.5', [MINI]]]), 'failure'), + }, + fetch: fakeFetch(cards), + expect: [ + ok(1), + count(/^::error/, 1), + count(/^::notice/, 1), + has(new RegExp(`^::notice .*::${SHARP} .*merge base's lockfile \\(abcdef012345\\) too.*#22013`)), + has(/1 introduced, 1 inherited, 0 resolved/), + ], + }, ]; const escapeCases = [ From ce598b1ab75b2d2f30f1fb018b71f996c7c9b9be Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:21:44 +0000 Subject: [PATCH 3/7] ci(validate-deps): put osv-base-relative's dispatch behind the entry guard check:entry-guard: a scripts/ file that exports bindings must not run on import. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- scripts/osv-base-relative.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/osv-base-relative.mjs b/scripts/osv-base-relative.mjs index f35d31ee107..566316c6e01 100644 --- a/scripts/osv-base-relative.mjs +++ b/scripts/osv-base-relative.mjs @@ -61,6 +61,8 @@ import { existsSync, readFileSync } from 'node:fs'; +import { isEntrypoint } from './invoked-as.mjs'; + const OUTCOMES = new Set(['success', 'failure']); const ANCHOR_PAGE_CAP = 10; @@ -577,4 +579,4 @@ async function main() { process.exit(code); } -await main(); +if (isEntrypoint(import.meta.url)) await main(); From 501569be495986b4878b9e0a7a0ca9b7a3b96fe1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:37:48 +0000 Subject: [PATCH 4/7] =?UTF-8?q?probe(validate-deps):=20TEMPORARY=20?= =?UTF-8?q?=E2=80=94=20add=20minimist=201.2.5=20(GHSA-xvch-5gv4-984h)=20so?= =?UTF-8?q?=20the=20pull=5Frequest=20OSV=20verdict=20has=20a=20real=20advi?= =?UTF-8?q?sory=20to=20introduce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverted by the next commit; the PR squashes. Its run is the pin's "a lockfile that introduces a new advisory fails" half. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- package.json | 1 + pnpm-lock.yaml | 65 +++++++------------------------------------------- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index c6b33eca0ce..642d04464c5 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,7 @@ "@typescript-eslint/parser": "^8.70.1", "eslint": "^10.11.0", "github-slugger": "2.0.0", + "minimist": "1.2.5", "semver": "^7.8.5", "svelte": "^5.57.1", "tsup": "^8.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac884e5176..fd325b3e56f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: github-slugger: specifier: 2.0.0 version: 2.0.0 + minimist: + specifier: 1.2.5 + version: 1.2.5 semver: specifier: ^7.8.5 version: 7.8.5 @@ -402,7 +405,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) packages/apps/setup: dependencies: @@ -7842,6 +7845,9 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} + minimist@1.2.5: + resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -11759,15 +11765,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.14.6(@types/node@26.6.3)(typescript@6.0.3) - vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': dependencies: '@vitest/spy': 4.1.11 @@ -14384,6 +14381,8 @@ snapshots: dependencies: brace-expansion: 5.0.12 + minimist@1.2.5: {} + minimist@1.2.8: {} minipass@7.1.3: {} @@ -15875,21 +15874,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.0.3 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 26.6.3 - esbuild: 0.28.1 - fsevents: 2.3.3 - jiti: 2.7.0 - tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) - yaml: 2.9.1 - vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -15905,37 +15889,6 @@ snapshots: tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) yaml: 2.9.1 - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): - dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - es-module-lexer: 2.3.1 - expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.4 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 26.6.3 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) - happy-dom: 20.10.2 - jsdom: 30.1.1(@noble/hashes@2.4.0) - transitivePeerDependencies: - - msw - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): dependencies: '@vitest/expect': 4.1.11 From 3576cdc33498796ff50a7e05eead01a54e8793e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:37:48 +0000 Subject: [PATCH 5/7] Revert the minimist 1.2.5 probe: the pull request's lockfile and root manifest are main's again Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- package.json | 1 - pnpm-lock.yaml | 65 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 642d04464c5..c6b33eca0ce 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,6 @@ "@typescript-eslint/parser": "^8.70.1", "eslint": "^10.11.0", "github-slugger": "2.0.0", - "minimist": "1.2.5", "semver": "^7.8.5", "svelte": "^5.57.1", "tsup": "^8.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd325b3e56f..dac884e5176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,9 +71,6 @@ importers: github-slugger: specifier: 2.0.0 version: 2.0.0 - minimist: - specifier: 1.2.5 - version: 1.2.5 semver: specifier: ^7.8.5 version: 7.8.5 @@ -405,7 +402,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) packages/apps/setup: dependencies: @@ -7845,9 +7842,6 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} - minimist@1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -11765,6 +11759,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.14.6(@types/node@26.6.3)(typescript@6.0.3) + vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) + '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': dependencies: '@vitest/spy': 4.1.11 @@ -14381,8 +14384,6 @@ snapshots: dependencies: brace-expansion: 5.0.12 - minimist@1.2.5: {} - minimist@1.2.8: {} minipass@7.1.3: {} @@ -15874,6 +15875,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.3 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) + yaml: 2.9.1 + vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -15889,6 +15905,37 @@ snapshots: tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) yaml: 2.9.1 + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 26.6.3 + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + happy-dom: 20.10.2 + jsdom: 30.1.1(@noble/hashes@2.4.0) + transitivePeerDependencies: + - msw + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): dependencies: '@vitest/expect': 4.1.11 From 5473cad5143673e06cdeb2e81f92f017b12ab2ad Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:46:53 +0000 Subject: [PATCH 6/7] =?UTF-8?q?probe(validate-deps):=20TEMPORARY=20?= =?UTF-8?q?=E2=80=94=20pin=20root=20devDependency=20minimist=201.2.8=20(no?= =?UTF-8?q?=20advisory)=20so=20the=20lockfile=20changes=20without=20introd?= =?UTF-8?q?ucing=20one?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main's lockfile carries live advisories today, so this run is the pin's "inherited passes with a notice" half on a real merge base. Reverted by the next commit; the PR squashes. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- package.json | 1 + pnpm-lock.yaml | 60 ++++---------------------------------------------- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index c6b33eca0ce..2a1e4e18071 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,7 @@ "@typescript-eslint/parser": "^8.70.1", "eslint": "^10.11.0", "github-slugger": "2.0.0", + "minimist": "1.2.8", "semver": "^7.8.5", "svelte": "^5.57.1", "tsup": "^8.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac884e5176..0c135618cd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: github-slugger: specifier: 2.0.0 version: 2.0.0 + minimist: + specifier: 1.2.8 + version: 1.2.8 semver: specifier: ^7.8.5 version: 7.8.5 @@ -402,7 +405,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) packages/apps/setup: dependencies: @@ -11759,15 +11762,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': - dependencies: - '@vitest/spy': 4.1.11 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.14.6(@types/node@26.6.3)(typescript@6.0.3) - vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) - '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': dependencies: '@vitest/spy': 4.1.11 @@ -15875,21 +15869,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.0.3 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 26.6.3 - esbuild: 0.28.1 - fsevents: 2.3.3 - jiti: 2.7.0 - tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) - yaml: 2.9.1 - vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -15905,37 +15884,6 @@ snapshots: tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) yaml: 2.9.1 - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): - dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) - '@vitest/pretty-format': 4.1.11 - '@vitest/runner': 4.1.11 - '@vitest/snapshot': 4.1.11 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - es-module-lexer: 2.3.1 - expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.4 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@types/node': 26.6.3 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) - happy-dom: 20.10.2 - jsdom: 30.1.1(@noble/hashes@2.4.0) - transitivePeerDependencies: - - msw - vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): dependencies: '@vitest/expect': 4.1.11 From cc98faf47ab747f9c73b0bb21424f9813411b3ee Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 02:49:03 +0000 Subject: [PATCH 7/7] Revert the minimist 1.2.8 probe: the pull request's lockfile and root manifest are main's again Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw --- package.json | 1 - pnpm-lock.yaml | 60 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2a1e4e18071..c6b33eca0ce 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,6 @@ "@typescript-eslint/parser": "^8.70.1", "eslint": "^10.11.0", "github-slugger": "2.0.0", - "minimist": "1.2.8", "semver": "^7.8.5", "svelte": "^5.57.1", "tsup": "^8.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c135618cd0..dac884e5176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,9 +71,6 @@ importers: github-slugger: specifier: 2.0.0 version: 2.0.0 - minimist: - specifier: 1.2.8 - version: 1.2.8 semver: specifier: ^7.8.5 version: 7.8.5 @@ -405,7 +402,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) packages/apps/setup: dependencies: @@ -11762,6 +11759,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.14.6(@types/node@26.6.3)(typescript@6.0.3) + vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) + '@vitest/mocker@4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1))': dependencies: '@vitest/spy': 4.1.11 @@ -15869,6 +15875,21 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.3 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) + yaml: 2.9.1 + vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1): dependencies: lightningcss: 1.33.0 @@ -15884,6 +15905,37 @@ snapshots: tsx: 4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6) yaml: 2.9.1 + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@26.6.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 26.6.3 + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + happy-dom: 20.10.2 + jsdom: 30.1.1(@noble/hashes@2.4.0) + transitivePeerDependencies: + - msw + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.3)(@vitest/coverage-v8@4.1.11)(happy-dom@20.10.2)(jsdom@30.1.1(@noble/hashes@2.4.0))(msw@2.14.6(@types/node@26.6.3)(typescript@6.0.3))(vite@8.0.16(@types/node@26.6.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15(patch_hash=4d1d35da1809be2a945519cb26890e48107810b9b66c6913a6a89abb5ad6a1e6))(yaml@2.9.1)): dependencies: '@vitest/expect': 4.1.11