diff --git a/.github/workflows/vendored-updates.yml b/.github/workflows/vendored-updates.yml index f5f60f09..cedd07c9 100644 --- a/.github/workflows/vendored-updates.yml +++ b/.github/workflows/vendored-updates.yml @@ -1,6 +1,6 @@ name: Vendored updates -# Two vendored files no other workflow watches (U1): +# Vendored files no other workflow watches (U1): # # chrome/player/modules/vad/silero_vad_half.onnx - snakers4/silero-vad's published # half-precision model, shipped as released and pinned by TAG and SHA256 in @@ -14,6 +14,12 @@ name: Vendored updates # dash.js release changes that file, an issue "vtt.js changed in dash.js ": the # changes may need re-deriving, so a person moves the pin. # +# The adapted copies of other projects' code in tools/vendored-sources.json (knob.mjs, +# StreamSaver.mjs, crosstalk/fft.mjs, vad/vad.mjs; 2026-10-01): when upstream changes the +# file one came from after its ref, an issue "Vendored source changed upstream: +# ( )" lists the commits. A copy is adapted, so a person ports what matters +# and moves the ref; the issue then closes itself, and a newer upstream change replaces it. +# # Each is assigned to the owner and @mentions them. A title is never raised twice, open or # closed: closing one skips that release for good. Each closes itself once its pin is at # its tag or later, and a newer release's item closes the older ones. Nothing merges here: @@ -302,3 +308,110 @@ jobs: printf '@%s vendored-updates.yml failed: %s\n\nThe run log names the failed command. Close this issue once it is fixed; a later failure opens a new one.\n' \ "$OWNER" "$RUN_URL" > "$RUNNER_TEMP/body.md" gh issue create --title "$TITLE" --assignee "$OWNER" --body-file "$RUNNER_TEMP/body.md" + + sources: + name: Check the adapted copies' upstream code + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read # the checkout + issues: write # the "changed upstream" issues; this job's own failure + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + PREFIX: 'Vendored source changed upstream: ' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Open the issue for a changed upstream source + run: | + set -euo pipefail + jq -c '.sources[]' tools/vendored-sources.json > "$RUNNER_TEMP/sources.jsonl" + if [ ! -s "$RUNNER_TEMP/sources.jsonl" ]; then + echo 'tools/vendored-sources.json lists no sources.' + exit 1 + fi + gh api --paginate "repos/$GH_REPO/issues?state=all&per_page=100" \ + --jq '.[] | select(.pull_request == null and .user.login == "github-actions[bot]" and (.title | startswith(env.PREFIX))) | [.number, .state, .title] | @tsv' \ + > "$RUNNER_TEMP/mine.tsv" + + # Both loops read their lists on their own descriptors (3, 4): a command inside that + # reads standard input would otherwise swallow the rest of the list. + while IFS= read -r source <&3; do + name=$(jq -r .name <<< "$source") + repo=$(jq -r .repo <<< "$source") + path=$(jq -r .path <<< "$source") + ref=$(jq -r .ref <<< "$source") + copy=$(jq -r .copy <<< "$source") + prefix="$PREFIX$name (" + # Closes this source's open issues, all but the one titled $2 if given. + close_open() { # [title to keep] + while IFS=$'\t' read -r number state title <&4; do + [ "$state" = open ] || continue + case $title in "$prefix"*) ;; *) continue ;; esac + if [ "$title" = "${2-}" ]; then continue; fi + gh issue close "$number" --comment "$1" + echo "Closed #$number ($title)" + done 4< "$RUNNER_TEMP/mine.tsv" + } + + # The newest upstream commit that changed the path, and whether the copy's ref + # holds it (the ref is that commit, or after it). + newest=$(gh api -X GET "repos/$repo/commits" -f path="$path" -f per_page=1 --jq '.[0].sha // ""') + if [ -z "$newest" ]; then + echo "$repo has no commit that changed $path." + exit 1 + fi + status=$(gh api "repos/$repo/compare/$newest...$ref" --jq .status) + if [ "$status" = identical ] || [ "$status" = ahead ]; then + echo "$name: $repo's newest change to $path is in ${ref:0:8}." + close_open "tools/vendored-sources.json has $name at ${ref:0:8}, which holds $repo's newest change to $path." + continue + fi + + title="$prefix$repo ${newest:0:8})" + if awk -F '\t' -v t="$title" '$3 == t {found = 1} END {exit !found}' "$RUNNER_TEMP/mine.tsv"; then + echo "$name: $repo ${newest:0:8} was raised before." + close_open "$repo changed $path again: the issue for ${newest:0:8} replaces this one." "$title" + continue + fi + # The commits after the ref (the compare: exact, where a date is not) that changed + # the path (upstream's history of it, newest first). + gh api "repos/$repo/compare/$ref...$newest" --jq '[.commits[].sha]' > "$RUNNER_TEMP/after.json" + gh api -X GET "repos/$repo/commits" -f path="$path" -f sha="$newest" -f per_page=100 > "$RUNNER_TEMP/commits.json" + jq -r --slurpfile after "$RUNNER_TEMP/after.json" '.[] | select(.sha as $s | $after[0] | index($s)) | + "- [\(.sha[0:8])](\(.html_url)) \(.commit.committer.date[0:10]) \(.commit.message | split("\n")[0])"' \ + "$RUNNER_TEMP/commits.json" > "$RUNNER_TEMP/commits.md" + # shellcheck disable=SC2016 # backticks are Markdown + { + printf '@%s %s changed `%s` after the commit `%s` was taken from (%s):\n\n' "$OWNER" "$repo" "$path" "$copy" "${ref:0:8}" + cat "$RUNNER_TEMP/commits.md" + printf '\nAll of it: https://github.com/%s/compare/%s...%s\n\n' "$repo" "$ref" "$newest" + printf '`%s` is an adapted copy, so nothing updates it by itself: port what matters, then set the `ref` of %s in `tools/vendored-sources.json` to `%s`, and this issue closes itself. Closing it skips this change; a later one raises a new issue.\n' \ + "$copy" "$name" "$newest" + } > "$RUNNER_TEMP/body.md" + url=$(gh issue create --title "$title" --assignee "$OWNER" --body-file "$RUNNER_TEMP/body.md") + echo "Opened $url" + close_open "$repo changed $path again: $url replaces this one." "$title" + done 3< "$RUNNER_TEMP/sources.jsonl" + + - name: Report the sources job's own failure + if: failure() + env: + TITLE: 'Vendored updates workflow failed (sources)' + run: | + set -euo pipefail + number=$(gh issue list --state open --limit 200 --json number,title \ + --jq '[.[] | select(.title == env.TITLE)][0].number // empty') || number='' + if [ -n "$number" ]; then + printf '@%s Failed again: %s\n' "$OWNER" "$RUN_URL" > "$RUNNER_TEMP/comment.md" + gh issue comment "$number" --body-file "$RUNNER_TEMP/comment.md" + exit 0 + fi + printf '@%s vendored-updates.yml failed: %s\n\nThe run log names the failed command. Close this issue once it is fixed; a later failure opens a new one.\n' \ + "$OWNER" "$RUN_URL" > "$RUNNER_TEMP/body.md" + gh issue create --title "$TITLE" --assignee "$OWNER" --body-file "$RUNNER_TEMP/body.md" diff --git a/CLAUDE.md b/CLAUDE.md index 8b35998c..bb9fe7c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1360,6 +1360,16 @@ the change went in. were current in effect: silero-vad v6.2.3 and dash.js v5.2.1 publish the same bytes as the pins (v6.2.1, v5.1.0). `tests/workflows/vendored-updates.test.sh`: 10 scenarios (real git pushing to a local bare origin), and 7 undone rules each fail it. + **Adapted copies** (job `sources`, 2026-10-01): `tools/vendored-sources.json` lists code + copied from other projects and changed here (knob.mjs from jherrm/knobs, StreamSaver.mjs, + crosstalk/fft.mjs from indutny/fft.js, vad/vad.mjs from ricky0123/vad), each with the + upstream path and the commit it was taken from. When upstream's newest commit to that path + is not in the ref (compare status behind or diverged), an issue "Vendored source changed + upstream: ( )" lists the commits after the ref that changed it; port by + hand, move the ref, and it closes itself. The lists are read on fds 3/4 (gh.exe via WSL + swallowed stdin and ended the loop after the first source). On 2026-10-01 knob (16 commits + after its 2012 pin) and vad-web (37 since 2023-03-30) were behind, StreamSaver and fft.js + current. `tests/workflows/vendored-sources.test.sh`: 8 scenarios, 10 undone rules caught. - **`dependency-review.yml`** fails a PR that adds a package with a high-severity advisory. Since 2026-09-30 it also runs on `workflow_dispatch`, which `sync-upstream.yml` and `patched-libraries.yml` send next to CI's: their PRs are opened by the workflow token, so diff --git a/docs/maintenance.md b/docs/maintenance.md index 83491c55..1ed3fb2f 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -30,6 +30,7 @@ workflows, which stay silent while they pass. | runner images | `runner-images.yml` runs `ci.yml` on the new image | the run is recorded, so the same image is not retested | nothing; an issue per image on failure | | actionlint and zizmor images | a Dependabot PR (docker, weekly) changing the tag and digest in `.github/actionlint/Dockerfile` or `.github/zizmor/Dockerfile` (or only the digest, when the same tag was pushed again: dependabot/dependabot-core#15081), which `ci.yml`'s workflows job and the WSL verify read | waits for you: it changes the check every workflow file has to pass | one comment, and the assignment | | Firefox stable, beta | `firefox-stable.yml` (daily) and `firefox-beta.yml` (Mon, Thu) run the e2e suites on that Firefox | a stable version is recorded as tested, so later days skip it | nothing; an issue on failure, closed by the next green run | +| adapted copies of other projects' code (knob, StreamSaver, fft.js, vad-web) | `vendored-updates.yml` (daily) checks the upstream file each came from (`tools/vendored-sources.json`) | nothing to merge: the copies are changed here, so a person ports upstream's changes | an issue per copy and upstream change, "Vendored source changed upstream: (...)", with the commits; it closes once `tools/vendored-sources.json` has the copy at that commit | | security alerts | a Dependabot alert on the Security tab, and a Dependabot PR that fixes it (the rows above), skipping the cooldown; `security-alerts.yml` (daily) watches for an alert with no such PR | as the PR's row says | GitHub's alert email; for an alert over 6 hours old with no Dependabot PR, an issue per package, "Security alert: ", closed once its alerts are fixed on `main` or dismissed | Dependabot proposes a release once it is 5 days old (`cooldown` in `.github/dependabot.yml`); diff --git a/tests/workflows/vendored-sources.test.sh b/tests/workflows/vendored-sources.test.sh new file mode 100644 index 00000000..72bf40f0 --- /dev/null +++ b/tests/workflows/vendored-sources.test.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +# vendored-updates.yml, job sources: an issue when upstream changes the file an adapted copy +# (tools/vendored-sources.json) came from after its ref; none while the ref holds upstream's +# newest change, which closes the open one; never the same change twice; a newer change +# replaces the older issue. gh is a stub that answers from fixture files and logs the calls. +source "$(dirname "$0")/lib.sh" + +here=$(mktemp -d) +trap 'rm -rf "$here"' EXIT +step=$here/sources.sh +step_script vendored-updates.yml 'Open the issue for a changed upstream source' > "$step" || exit 1 +step_script vendored-updates.yml "Report the sources job's own failure" > /dev/null || exit 1 + +mkdir -p "$here/bin" +cat > "$here/bin/gh" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf 'GH %s\n' "$*" >> "$LOG" +# As gh.exe through WSL did: it reads whatever standard input it is given. +cat > /dev/null +jqf='' path='' sha='' +args=("$@") +for ((i = 0; i < ${#args[@]}; i++)); do + case "${args[i]}" in + --jq) jqf=${args[i + 1]-} ;; + -f) case "${args[i + 1]-}" in path=*) path=${args[i + 1]#path=} ;; sha=*) sha=${args[i + 1]#sha=} ;; esac ;; + esac +done +out() { if [ -n "$jqf" ]; then jq -r "$jqf" "$1"; else cat "$1"; fi; } +key() { printf '%s' "$1" | tr '/' '_'; } +if [ "$1 $2" = 'api --paginate' ]; then + [ "$3" = "repos/$GH_REPO/issues?state=all&per_page=100" ] || { echo "stub gh: unexpected api path $3" >&2; exit 2; } + out "$FIX/issues.json" + exit 0 +fi +case "$1 $2" in + 'issue create') + { + printf 'CREATE'; printf ' [%s]' "${@:3}"; printf '\n' + while [ $# -gt 0 ]; do + if [ "$1" = --body-file ]; then echo '--- body:'; cat "$2"; echo '--- end body'; fi + shift + done + } >> "$LOG" + echo "https://github.com/$GH_REPO/issues/99" + exit 0 ;; + 'issue close') + { printf 'CLOSE'; printf ' [%s]' "${@:3}"; printf '\n'; } >> "$LOG" + exit 0 ;; +esac +[ "$1" = api ] || { echo "stub gh: unexpected call: $*" >&2; exit 2; } +shift +[ "$1" = -X ] && shift 2 +url=$1 +case $url in + repos/*/*/commits) + repo=${url#repos/}; repo=${repo%/commits} + if [ -n "$sha" ]; then + # Upstream's history of the path, from the newest commit down. + f=$FIX/commits_$(key "$repo").json + [ -f "$f" ] || echo '[]' > "$f" + out "$f" + else + # The newest one. + sha=$(cat "$FIX/newest_$(key "$repo")" 2> /dev/null || true) + if [ -n "$sha" ]; then jq -n --arg s "$sha" '[{sha: $s}]' > "$FIX/n.json"; else echo '[]' > "$FIX/n.json"; fi + out "$FIX/n.json" + fi ;; + repos/*/*/compare/*) + repo=${url#repos/}; repo=${repo%/compare/*} + # The status, and the commits after the base (after_.json: their shas). + a=$FIX/after_$(key "$repo").json + [ -f "$a" ] || echo '[]' > "$a" + jq -n --arg s "$(cat "$FIX/status_$(key "$repo")" 2> /dev/null || echo identical)" --slurpfile a "$a" '{status: $s, commits: [$a[0][] | {sha: .}]}' > "$FIX/c.json" + out "$FIX/c.json" ;; + repos/*/*/commits/*) + echo '{"commit":{"committer":{"date":"2020-01-01T00:00:00Z"}}}' > "$FIX/d.json" + out "$FIX/d.json" ;; + *) echo "stub gh: unexpected api call: $*" >&2; exit 2 ;; +esac +EOF +chmod +x "$here/bin/gh" + +export GH_TOKEN=stub GH_REPO='Nawid3333/FastStream' OWNER='Nawid3333' PREFIX='Vendored source changed upstream: ' +export RUN_URL='https://github.com/Nawid3333/FastStream/actions/runs/1' +bot='{"login":"github-actions[bot]"}' +KREF=1111111111111111111111111111111111111111 +VREF=2222222222222222222222222222222222222222 +NEW=3333333333333333333333333333333333333333 +NEWER=4444444444444444444444444444444444444444 + +# fixtures : a fresh scenario, with two sources, both up to date. +fixtures() { + export FIX="$here/fix" RUNNER_TEMP="$here/fix/tmp" LOG="$here/fix/log" + rm -rf "$FIX" + mkdir -p "$RUNNER_TEMP" "$FIX/work/tools" + : > "$LOG" + printf '%s' "$1" > "$FIX/issues.json" + jq -n --arg k "$KREF" --arg v "$VREF" '{sources: [ + {name: "knob", copy: "chrome/player/modules/knob.mjs", repo: "jherrm/knobs", path: "Knob.js", ref: $k}, + {name: "vad-web", copy: "chrome/player/modules/vad/vad.mjs", repo: "ricky0123/vad", path: "packages/web/src", ref: $v}]}' \ + > "$FIX/work/tools/vendored-sources.json" + echo "$KREF" > "$FIX/newest_jherrm_knobs" + echo "$VREF" > "$FIX/newest_ricky0123_vad" +} +# changed : upstream changed the path after the ref. +changed() { + echo "$2" > "$FIX/newest_$1" + echo behind > "$FIX/status_$1" + jq -n --arg n "$2" '[$n]' > "$FIX/after_$1.json" + jq -n --arg n "$2" --arg r "$KREF" '[ + {sha: $n, html_url: "https://github.com/x/y/commit/\($n)", commit: {committer: {date: "2022-11-08T10:00:00Z"}, message: "Fix the rotation\n\nlong text"}}, + {sha: $r, html_url: "https://github.com/x/y/commit/\($r)", commit: {committer: {date: "2020-01-01T00:00:00Z"}, message: "the ref itself"}}]' \ + > "$FIX/commits_$1.json" +} +run() { + echo "$1" + ( + cd "$FIX/work" || exit 9 + PATH="$here/bin:$PATH" run_step "$step" + ) < /dev/null > "$FIX/out" 2>&1 + status=$? +} + +fixtures "[{\"number\":70,\"state\":\"open\",\"title\":\"${PREFIX}knob (jherrm/knobs 99999999)\",\"user\":$bot},{\"number\":76,\"state\":\"open\",\"title\":\"${PREFIX}knob (jherrm/knobs 88888888)\",\"user\":$bot}]" +run 's1 both up to date -> nothing raised; an open issue closes' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens nothing' lacks "$LOG" 'CREATE' +check 'asks whether the ref holds the newest change' contains "$LOG" "GH api repos/jherrm/knobs/compare/$KREF...$KREF --jq .status" +check 'closes #76 too' contains "$LOG" 'CLOSE [76]' +check 'closes #70, saying why' contains "$LOG" "CLOSE [70] [--comment] [tools/vendored-sources.json has knob at ${KREF:0:8}, which holds jherrm/knobs's newest change to Knob.js.]" + +fixtures '[]' +changed jherrm_knobs "$NEW" +run 's2 knob changed upstream -> one issue with the commits' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens it for the newest change, assigned' contains "$LOG" "CREATE [--title] [${PREFIX}knob (jherrm/knobs ${NEW:0:8})] [--assignee] [Nawid3333]" +check 'one issue only (vad-web is up to date)' test "$(grep -c '^CREATE' "$LOG")" -eq 1 +check '@mentions, naming the file and the copy' contains "$LOG" "@Nawid3333 jherrm/knobs changed \`Knob.js\` after the commit \`chrome/player/modules/knob.mjs\` was taken from (${KREF:0:8}):" +check 'lists the change, first line only' contains "$LOG" "- [${NEW:0:8}](https://github.com/x/y/commit/$NEW) 2022-11-08 Fix the rotation" +check 'not the ref itself' lacks "$LOG" 'the ref itself' +check 'links all of it' contains "$LOG" "All of it: https://github.com/jherrm/knobs/compare/$KREF...$NEW" +check 'says how it closes' contains "$LOG" "set the \`ref\` of knob in \`tools/vendored-sources.json\` to \`$NEW\`" +check 'asks for the commits after the ref' contains "$LOG" "GH api repos/jherrm/knobs/compare/$KREF...$NEW --jq [.commits[].sha]" +check 'and the history of the file' contains "$LOG" "GH api -X GET repos/jherrm/knobs/commits -f path=Knob.js -f sha=$NEW -f per_page=100" + +fixtures "[{\"number\":71,\"state\":\"closed\",\"title\":\"${PREFIX}knob (jherrm/knobs ${NEW:0:8})\",\"user\":$bot},{\"number\":72,\"state\":\"open\",\"title\":\"${PREFIX}knob (jherrm/knobs 99999999)\",\"user\":$bot}]" +changed jherrm_knobs "$NEW" +run 's3 raised before (closed: skipped) -> nothing new; an older open one closes' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens nothing' lacks "$LOG" 'CREATE' +check 'closes the older #72' contains "$LOG" "CLOSE [72] [--comment] [jherrm/knobs changed Knob.js again: the issue for ${NEW:0:8} replaces this one.]" +check 'leaves #71 alone' lacks "$LOG" 'CLOSE [71]' + +fixtures "[{\"number\":75,\"state\":\"open\",\"title\":\"${PREFIX}knob (jherrm/knobs ${NEW:0:8})\",\"user\":$bot}]" +changed jherrm_knobs "$NEW" +run 's3b raised before and still open -> left open, nothing new' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens nothing' lacks "$LOG" 'CREATE' +check 'closes nothing' lacks "$LOG" 'CLOSE' + +fixtures "[{\"number\":73,\"state\":\"open\",\"title\":\"${PREFIX}knob (jherrm/knobs ${NEW:0:8})\",\"user\":$bot},{\"number\":74,\"state\":\"open\",\"title\":\"${PREFIX}vad-web (ricky0123/vad ${NEW:0:8})\",\"user\":$bot}]" +changed jherrm_knobs "$NEWER" +run 's4 a newer change -> a new issue replaces the older one, of that source only' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens the newer one' contains "$LOG" "CREATE [--title] [${PREFIX}knob (jherrm/knobs ${NEWER:0:8})]" +check 'closes #73 for it' contains "$LOG" "CLOSE [73] [--comment] [jherrm/knobs changed Knob.js again: https://github.com/Nawid3333/FastStream/issues/99 replaces this one.]" +check "closes #74 only as vad-web's (up to date)" contains "$LOG" 'CLOSE [74] [--comment] [tools/vendored-sources.json has vad-web at' +check "not with knob's comment" lacks "$LOG" 'CLOSE [74] [--comment] [jherrm/knobs' + +fixtures '[]' +changed ricky0123_vad "$NEW" +echo diverged > "$FIX/status_ricky0123_vad" +run 's5 diverged from the ref -> raised as a change' "$step" +check 'succeeds' test "$status" -eq 0 +check 'opens it' contains "$LOG" "CREATE [--title] [${PREFIX}vad-web (ricky0123/vad ${NEW:0:8})]" + +fixtures '[]' +: > "$FIX/newest_jherrm_knobs" +run 's6 no upstream commit for the path -> fails (the report step tells the owner)' "$step" +check 'fails' test "$status" -ne 0 +check 'says so' contains "$FIX/out" 'jherrm/knobs has no commit that changed Knob.js.' + +fixtures '[]' +echo '{"sources": []}' > "$FIX/work/tools/vendored-sources.json" +run 's7 an empty list -> fails' "$step" +check 'fails' test "$status" -ne 0 +check 'says so' contains "$FIX/out" 'tools/vendored-sources.json lists no sources.' + +finish diff --git a/tools/vendored-sources.json b/tools/vendored-sources.json new file mode 100644 index 00000000..23ee963c --- /dev/null +++ b/tools/vendored-sources.json @@ -0,0 +1,33 @@ +{ + "about": "Adapted copies of other projects' code, which no package manager updates. vendored-updates.yml (job sources) opens an issue when upstream changes the file a copy came from after its ref; port what matters by hand, then move ref to the upstream commit you reviewed, and the issue closes itself. Each ref is the last upstream commit to that path before the copy entered this repository (2026-10-01), or the pin its verify script proves.", + "sources": [ + { + "name": "knob", + "copy": "chrome/player/modules/knob.mjs", + "repo": "jherrm/knobs", + "path": "Knob.js", + "ref": "cf2db70f804d04556e8ebb264dfe590d5215ffa8" + }, + { + "name": "StreamSaver", + "copy": "chrome/player/modules/StreamSaver.mjs", + "repo": "jimmywarting/StreamSaver.js", + "path": "StreamSaver.js", + "ref": "5297ec4a16b91126b66dedc8ef215368edcfe08a" + }, + { + "name": "fft.js", + "copy": "chrome/player/modules/crosstalk/fft.mjs", + "repo": "indutny/fft.js", + "path": "lib/fft.js", + "ref": "d73c1546ec9e43757a6d5a7a24fa3e1061b67f84" + }, + { + "name": "vad-web", + "copy": "chrome/player/modules/vad/vad.mjs", + "repo": "ricky0123/vad", + "path": "packages/web/src", + "ref": "7c2f259b9977125e6b8665118f24cdc1ed59c8ff" + } + ] +}