From d45639bf2f44992fdd2dec3689b37445394754a5 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 7 Sep 2026 19:35:20 +0200 Subject: [PATCH] Build, publish to Gitea releases, and install the three extensions Answers two things: how to install these without a marketplace, and how to keep settings in sync when VS Code's Settings Sync cannot be pointed at Gitea. - build.ps1 packages each extension into dist/ - publish.ps1 creates a release and uploads them, replacing same-named assets so a rolling "latest" tag stays clean - install.ps1 downloads a release's assets and installs them, or -Local from dist/ - settings.ps1 copies User/ between the repo and the machine, and installs the marketplace extensions recorded in extensions.txt - dev-link.ps1 junctions the sources into ~/.vscode/extensions for development - ci-release.sh plus a Gitea Actions workflow do the build and publish on a tag push Settings are copied, not symlinked: an atomic save replaces a symlink with a regular file and the sync stops without looking broken. vsce/ pins vsce and undici so packaging works on Node 18, which current vsce does not support. test/run.ps1 drives the real scripts against a stub of the Gitea release API. It caught the multipart Content-Disposition being unquoted on .NET Framework, and Invoke-WebRequest dropping the Authorization header across a redirect. --- .gitea/workflows/release.yml | 51 + .gitignore | 4 + README.md | 180 +++ config.json | 26 + scripts/build.ps1 | 98 ++ scripts/ci-release.sh | 142 +++ scripts/common.ps1 | 301 +++++ scripts/dev-link.ps1 | 111 ++ scripts/install.ps1 | 135 ++ scripts/publish.ps1 | 112 ++ scripts/settings.ps1 | 192 +++ test/fake-gitea.js | 215 ++++ test/run.ps1 | 204 +++ vsce/package-lock.json | 2260 ++++++++++++++++++++++++++++++++++ vsce/package.json | 12 + 15 files changed, 4043 insertions(+) create mode 100644 .gitea/workflows/release.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.json create mode 100644 scripts/build.ps1 create mode 100644 scripts/ci-release.sh create mode 100644 scripts/common.ps1 create mode 100644 scripts/dev-link.ps1 create mode 100644 scripts/install.ps1 create mode 100644 scripts/publish.ps1 create mode 100644 scripts/settings.ps1 create mode 100644 test/fake-gitea.js create mode 100644 test/run.ps1 create mode 100644 vsce/package-lock.json create mode 100644 vsce/package.json diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..9e5e319 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,51 @@ +# Packages the extensions and publishes them to a Gitea release. +# +# Needs a registered act_runner on the instance, and a GITEA_TOKEN secret with +# write:repository plus read access to the extension repos. Gitea fetches the +# actions/* actions from github.com unless the instance overrides +# [actions] DEFAULT_ACTIONS_URL, so a locked-down instance may need those mirrored. +# +# ci-release.sh needs only bash, git, node and curl -- everything the runner image +# already has once setup-node has run. +# +# The runner uses Node 20, which is also why CI is the easy way to build these: +# vsce does not run on Node 18. See the README. + +name: Release extensions + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish to' + required: false + default: 'latest' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Package and publish + env: + GITEA_SERVER: ${{ github.server_url }} + GITEA_OWNER: ${{ github.repository_owner }} + GITEA_REPO: ${{ github.event.repository.name }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + # A tag push publishes to that tag; a manual run to whatever was asked for. + TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }} + run: bash scripts/ci-release.sh + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: vsix + path: dist/*.vsix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ddb3b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +.token +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5f675d --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# VS Code setup + +Builds three local VS Code extensions, publishes them to a **Gitea release**, installs +them from there, and keeps user settings in git. + +| Extension | What it does | +| --- | --- | +| `colored-references` | Find All References in a syntax-highlighted virtual document, plus a sortable results panel | +| `vertical-tabs` | A vertical list of open tabs, colour-coded by project, with pinning | +| `dotnet-hot-reload` | A hot reload button in the debug toolbar, driving `dotnet watch` | + +## The short version + +```powershell +# once per machine, on the machine that builds +$env:GITEA_TOKEN = '' +.\scripts\publish.ps1 -Build # package all three, upload to the "latest" release + +# on any machine, including a fresh one +.\scripts\install.ps1 # download from the release and install +.\scripts\settings.ps1 -Pull # apply settings, keybindings, marketplace extensions +``` + +Set the server first — either in [`config.json`](config.json) or with `GITEA_URL`, +`GITEA_OWNER` and `GITEA_REPO`. The repo named there is where releases go; it does not +have to be this repo. + +## Why not Settings Sync + +**VS Code's built-in Settings Sync cannot be pointed at Gitea.** It signs in with a +Microsoft or GitHub account and talks to Microsoft's own sync service; there is no +setting for a different backend, self-hosted or otherwise. The old +Gist-based *Settings Sync* extension is out too, because Gitea has no gists. + +So settings live in [`User/`](User/) as ordinary files, and +[`scripts/settings.ps1`](scripts/settings.ps1) copies them in either direction. That is +less magic than Settings Sync and more legible: a diff shows what changed. + +It **copies rather than symlinks** on purpose. A symlink at +`%APPDATA%\Code\User\settings.json` is fragile — an editor that saves atomically (write +a temp file, rename it over the target) replaces the link with a regular file, and the +sync stops without anything looking broken. + +Extensions from the marketplace are handled separately: `settings.ps1 -Push` records +them in `User/extensions.txt`, `-Pull` installs the missing ones. Ids starting `local.` +are skipped, because those are the three above and they come from the release. + +## Scripts + +| Script | Does | +| --- | --- | +| [`build.ps1`](scripts/build.ps1) | Packages each extension into `dist/*.vsix`. `-Clone` fetches missing sources from Gitea, `-Pull` updates them, `-Only ` narrows it | +| [`publish.ps1`](scripts/publish.ps1) | Creates the release if needed and uploads `dist/*.vsix`. `-Build` builds first, `-Tag` picks the tag | +| [`install.ps1`](scripts/install.ps1) | Downloads a release's assets and installs them. `-Local` installs from `dist/` instead, `-Uninstall` removes them | +| [`settings.ps1`](scripts/settings.ps1) | `-Push` machine to repo, `-Pull` repo to machine, `-Diff` compares | +| [`dev-link.ps1`](scripts/dev-link.ps1) | Junctions the sources into `~/.vscode/extensions` for development. `-Unlink` undoes it | +| [`ci-release.sh`](scripts/ci-release.sh) | The bash equivalent of `publish.ps1`, for the Gitea Actions runner. `SKIP_PACKAGE=1` uploads `dist/` without rebuilding | + +Every script takes `-?` for its full help. + +### Tags + +`publish.ps1` defaults to a rolling **`latest`** release: publishing again replaces the +assets in place rather than adding duplicates, so `install.ps1` with no arguments always +gets current builds. Use `-Tag v0.2.0` for a snapshot you want to keep, and +`install.ps1 -Tag v0.2.0` to go back to it. + +The tag itself is created on the default branch the first time. For a release that only +carries binaries the commit it points at does not mean much, but it is why `latest` +keeps pointing at an old commit — the assets are what move. + +## Tokens + +Never in this repo. `publish.ps1` and `install.ps1` read, in order: + +1. `-Token ` +2. `$env:GITEA_TOKEN` +3. `%USERPROFILE%\.gitea-token` + +Create one at `/user/settings/applications` with **`write:repository`** scope. +`install.ps1` only reads, so a `read:repository` token is enough there — and if the +release repo is public it needs no token at all for the download, though the API call +that finds the release still wants one on most instances. + +## Building on Node 18 + +`vsce` needs **Node 20 or newer**. On Node 18 it fails with +`ReferenceError: File is not defined`, from a transitive `undici`, and pinning an older +`vsce` does not help because `undici` still resolves to a current version. + +[`vsce/package.json`](vsce/package.json) works around it by pinning both, and +`build.ps1` installs into that folder on first run. If you upgrade Node to 20+ you can +delete `vsce/` and use `npx @vscode/vsce package` directly. + +Upgrading Node is the better fix. The workaround exists so a machine stuck on 18 is not +blocked. + +## Automating it + +[`.gitea/workflows/release.yml`](.gitea/workflows/release.yml) builds and publishes on a +`v*` tag push, or on demand. It needs: + +- a registered `act_runner` on the instance +- a `GITEA_TOKEN` secret with `write:repository`, and read access to the extension repos +- the `actions/checkout` and `actions/setup-node` actions reachable — Gitea pulls those + from github.com unless the instance sets `[actions] DEFAULT_ACTIONS_URL` + +The runner uses Node 20, so CI sidesteps the problem above entirely. + +`ci-release.sh` needs only bash, git, node and curl — JSON goes through `node`, not `jq`, +since node is required anyway and `jq` frequently is not. + +## Why not `tea` + +Gitea's CLI ([`tea`](https://gitea.com/gitea/tea)) can do this — `tea release create` +and `tea release assets create` cover the upload. It is not used here because these +scripts then need nothing but PowerShell and `code`: no Go binary to install per +machine, no `tea login add` step, no second place a token lives. Downloading assets on +the install side is also plainer over the API than through `tea`. + +If you already have `tea` set up, `tea release create --tag latest --asset dist/*.vsix` +is a fine substitute for `publish.ps1`. + +## Layout + +``` +config.json Gitea server + the extensions and where they live +User/ settings.json, keybindings.json, snippets/, extensions.txt +dist/ built .vsix files (ignored) +scripts/ the scripts above +vsce/ pinned vsce, for Node 18 (node_modules ignored) +.gitea/workflows/ the release workflow +``` + +`config.json` paths are relative to this repo, so it expects the extension folders as +siblings. Move them and it is one edit. + +## Tests + +```powershell +.\test\run.ps1 +``` + +Runs the real scripts against [`test/fake-gitea.js`](test/fake-gitea.js), a stub of the +release endpoints, with a stub `code` on `PATH` so nothing is installed into the editor. +It checks the things that fail quietly: + +- the `.vsix` survives the upload **byte for byte** — it is a zip, so any text-encoding + step in the multipart body would corrupt it and still look like a success +- the token reaches the API, and survives the redirect on the download +- republishing a tag replaces its assets instead of accumulating duplicates +- every install passes `--force` +- `build.ps1` and `install.ps1 -Local` work with no Gitea configured at all +- `ci-release.sh` publishes the same set as `publish.ps1` + +Two bugs came out of writing it. `MultipartFormDataContent.Add(content, name, fileName)` +emits an unquoted `name=attachment` on .NET Framework, which Gitea's Go parser accepts +but nothing guarantees — the header is now set explicitly. And `Invoke-WebRequest` drops +the `Authorization` header when it follows a redirect, so downloading a private repo's +asset returned 401; `Save-GiteaAsset` follows redirects itself, re-sending the token only +while the host does not change. + +What it does not cover: a real Gitea instance, and whether VS Code loads the packages +once installed. Both were checked by hand. + +## Known limitations + +- **No auto-update.** VS Code only offers updates for marketplace extensions, so a new + release does not notify you. Re-run `install.ps1`. +- **`--force` on every install**, because VS Code otherwise declines to reinstall a + version it already has, which would silently do nothing whenever you republish + without bumping the version. +- **A private extension gallery** — pointing VS Code's `product.json` at your own + gallery so updates work normally — is possible but gets overwritten by VS Code + updates. Not worth it for three extensions. +- **`dev-link.ps1` and `install.ps1` conflict.** A junctioned source and an installed + `.vsix` are two copies of the same extension id; VS Code loads one arbitrarily. + `dev-link.ps1 -Unlink` before installing. +- **Windows-only paths.** The scripts assume `%APPDATA%\Code\User` and + `%USERPROFILE%\.vscode\extensions`. `ci-release.sh` is the portable half. diff --git a/config.json b/config.json new file mode 100644 index 0000000..878fd59 --- /dev/null +++ b/config.json @@ -0,0 +1,26 @@ +{ + "//": "Fill in your Gitea server. GITEA_URL / GITEA_OWNER / GITEA_REPO override these.", + "gitea": { + "url": "https://gitea.example.com", + "owner": "max", + "repo": "vscode-extensions" + }, + "//extensions": "path is relative to this repo; repo is the Gitea repo to clone if path is missing.", + "extensions": [ + { + "id": "colored-references", + "path": "../colored-references-src/colored-references", + "repo": "colored-references" + }, + { + "id": "vertical-tabs", + "path": "../vertical-tabs", + "repo": "vertical-tabs" + }, + { + "id": "dotnet-hot-reload", + "path": "../dotnet-hot-reload", + "repo": "dotnet-hot-reload" + } + ] +} diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..c989fb7 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,98 @@ +<# +.SYNOPSIS + Packages the extensions into dist/*.vsix. + +.DESCRIPTION + For each extension in config.json: install dependencies if they are missing, then + run vsce, which compiles through the vscode:prepublish script. The .vsix files land + in dist/, which publish.ps1 uploads and install.ps1 can install from directly. + +.PARAMETER Only + Build just these extension ids. + +.PARAMETER Clone + Clone any extension whose configured path does not exist yet, from its Gitea repo. + +.PARAMETER Pull + git pull each extension before building. + +.EXAMPLE + .\scripts\build.ps1 + .\scripts\build.ps1 -Only vertical-tabs + .\scripts\build.ps1 -Clone -Pull +#> +[CmdletBinding()] +param( + [string[]] $Only, + [switch] $Clone, + [switch] $Pull +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'common.ps1') + +$config = Get-SetupConfig +$dist = Get-DistDir +$vsce = Get-VsceCommand + +$extensions = $config.extensions +if ($Only) { + $extensions = $extensions | Where-Object { $Only -contains $_.id } + $missing = $Only | Where-Object { $config.extensions.id -notcontains $_ } + if ($missing) { throw "not in config.json: $($missing -join ', ')" } +} + +$built = @() +foreach ($extension in $extensions) { + $path = Get-ExtensionPath $extension + Write-Host '' + Write-Host "=== $($extension.id)" -ForegroundColor Cyan + + if (-not (Test-Path $path)) { + if (-not $Clone) { + throw ("$path does not exist. Pass -Clone to clone it from " + + "$($config.gitea.url)/$($config.gitea.owner)/$($extension.repo).") + } + $url = "$($config.gitea.url)/$($config.gitea.owner)/$($extension.repo).git" + Write-Host "cloning $url" -ForegroundColor DarkGray + & git clone $url $path + if ($LASTEXITCODE -ne 0) { throw "git clone failed for $($extension.id)" } + } elseif ($Pull) { + Write-Host 'git pull' -ForegroundColor DarkGray + & git -C $path pull --ff-only + if ($LASTEXITCODE -ne 0) { throw "git pull failed for $($extension.id)" } + } + + $manifestPath = Join-Path $path 'package.json' + if (-not (Test-Path $manifestPath)) { throw "no package.json in $path" } + $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json + + Push-Location $path + try { + if (-not (Test-Path (Join-Path $path 'node_modules'))) { + Write-Host 'installing dependencies...' -ForegroundColor DarkGray + if (Test-Path (Join-Path $path 'package-lock.json')) { + & npm ci --silent --no-audit --no-fund + } else { + & npm install --silent --no-audit --no-fund + } + if ($LASTEXITCODE -ne 0) { throw "dependency install failed for $($extension.id)" } + } + + # vsce runs vscode:prepublish, so this compiles as well as packages. + # --allow-missing-repository keeps it from refusing a package.json with no + # repository field; it is harmless once that field is filled in. + $out = Join-Path $dist "$($manifest.name)-$($manifest.version).vsix" + & node $vsce package --out $out --allow-missing-repository + if ($LASTEXITCODE -ne 0) { throw "vsce package failed for $($extension.id)" } + + $size = [math]::Round((Get-Item $out).Length / 1KB, 1) + Write-Host " -> $(Split-Path -Leaf $out) ($size KB)" -ForegroundColor Green + $built += $out + } finally { Pop-Location } +} + +Write-Host '' +Write-Host "$($built.Count) package(s) in $dist" -ForegroundColor Cyan +$built diff --git a/scripts/ci-release.sh b/scripts/ci-release.sh new file mode 100644 index 0000000..bbe224d --- /dev/null +++ b/scripts/ci-release.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# Packages every extension in config.json and uploads the .vsix files to a Gitea release. +# +# This is the CI half of publish.ps1: the same steps, in bash, for a Linux runner with no +# PowerShell. Driven by .gitea/workflows/release.yml, but it runs anywhere with bash, git, +# node and curl. JSON goes through node rather than jq, because node has to be there +# anyway to build the extensions and jq often is not. +# +# Environment: +# GITEA_SERVER base URL, e.g. https://gitea.example.com (CI: GITHUB_SERVER_URL) +# GITEA_OWNER owner of the release repo (CI: GITHUB_REPOSITORY_OWNER) +# GITEA_REPO name of the release repo +# GITEA_TOKEN token with write:repository +# TAG release tag, default "latest" +# SKIP_CLONE non-empty: fail instead of cloning a missing extension +# SKIP_PACKAGE non-empty: upload whatever is already in dist/, build nothing + +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +config="$root/config.json" +dist="$root/dist" +tag="${TAG:-latest}" + +for tool in git node curl; do + command -v "$tool" >/dev/null || { echo "missing $tool" >&2; exit 1; } +done +for name in GITEA_SERVER GITEA_OWNER GITEA_REPO GITEA_TOKEN; do + [ -n "${!name:-}" ] || { echo "missing $name" >&2; exit 1; } +done + +server="${GITEA_SERVER%/}" +api="$server/api/v1/repos/$GITEA_OWNER/$GITEA_REPO" +auth=(-H "Authorization: token $GITEA_TOKEN") + +# Evaluates a JavaScript expression against JSON on stdin, one line per array element. +# Unparseable input yields nothing, so a 404 body reads as "no release". +jget() { + node -e ' + let raw = ""; + process.stdin.on("data", chunk => { raw += chunk; }).on("end", () => { + let json; + try { json = JSON.parse(raw); } catch (error) { return; } + const value = Function("j", "return (" + process.argv[1] + ")")(json); + if (value === undefined || value === null) { return; } + if (Array.isArray(value)) { value.forEach(item => console.log(item)); } + else { console.log(value); } + }); + ' "$1" +} + +mkdir -p "$dist" + +# --- package --------------------------------------------------------------------- + +if [ -n "${SKIP_PACKAGE:-}" ]; then + echo "SKIP_PACKAGE set, using what is already in dist/" +else + rm -f "$dist"/*.vsix + + node -e ' + const config = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); + for (const extension of config.extensions) { + console.log([extension.id, extension.path, extension.repo].join("\t")); + } + ' "$config" > "$dist/.extensions.tsv" + + while IFS=$'\t' read -r id relative repo; do + [ -n "$id" ] || continue + path="$root/$relative" + echo "=== $id" + + if [ ! -d "$path" ]; then + [ -z "${SKIP_CLONE:-}" ] || { echo " $path missing and SKIP_CLONE set" >&2; exit 1; } + # oauth2: is how Gitea takes a token over https, so private repos clone. + authed="$(echo "$server" | sed -E "s#^(https?://)#\1oauth2:$GITEA_TOKEN@#")" + git clone --depth 1 "$authed/$GITEA_OWNER/$repo.git" "$path" + fi + + pushd "$path" >/dev/null + if [ -f package-lock.json ]; then + npm ci --no-audit --no-fund + else + npm install --no-audit --no-fund + fi + name="$(node -p "require('./package.json').name")" + version="$(node -p "require('./package.json').version")" + npx --yes @vscode/vsce package \ + --out "$dist/$name-$version.vsix" --allow-missing-repository + popd >/dev/null + done < "$dist/.extensions.tsv" + + rm -f "$dist/.extensions.tsv" +fi + +ls -l "$dist"/*.vsix + +# --- release --------------------------------------------------------------------- + +release="$(curl -sS "${auth[@]}" "$api/releases/tags/$tag" || true)" +id="$(printf '%s' "$release" | jget 'j.id')" + +if [ -z "$id" ]; then + echo "creating release $tag" + body="$(TAG="$tag" node -e ' + process.stdout.write(JSON.stringify({ + tag_name: process.env.TAG, + name: "VS Code extensions (" + process.env.TAG + ")", + body: "Built by CI. Install with scripts/install.ps1.", + draft: false, + prerelease: false, + })); + ')" + created="$(curl -sS -f -X POST "${auth[@]}" \ + -H 'Content-Type: application/json' -d "$body" "$api/releases")" + id="$(printf '%s' "$created" | jget 'j.id')" + [ -n "$id" ] || { echo "could not create the release: $created" >&2; exit 1; } + release='{"assets":[]}' +fi +echo "release id $id" + +# Uploads run from inside dist/ so curl gets a bare filename. An absolute path would +# do on Linux, but curl built for Windows cannot open an MSYS-style /d/... path, and +# a relative name is unambiguous everywhere. +pushd "$dist" >/dev/null +for base in *.vsix; do + # Replace rather than duplicate, so republishing the same tag stays clean. + old="$(printf '%s' "$release" | ASSET_NAME="$base" \ + jget '(j.assets||[]).filter(a => a.name === process.env.ASSET_NAME).map(a => a.id)')" + for asset in $old; do + echo " replacing $base" + curl -sS -f -X DELETE "${auth[@]}" "$api/releases/$id/assets/$asset" >/dev/null + done + + echo " uploading $base" + curl -sS -f -X POST "${auth[@]}" \ + -F "attachment=@$base;type=application/octet-stream" \ + "$api/releases/$id/assets?name=$base" >/dev/null +done +popd >/dev/null + +echo "done: $server/$GITEA_OWNER/$GITEA_REPO/releases/tag/$tag" diff --git a/scripts/common.ps1 b/scripts/common.ps1 new file mode 100644 index 0000000..df0b6c4 --- /dev/null +++ b/scripts/common.ps1 @@ -0,0 +1,301 @@ +# Shared helpers: configuration, the Gitea release API, and locating vsce. +# Dot-source this; it defines functions and does nothing on its own. + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# Windows PowerShell 5.1 still defaults to SSL3/TLS1.0, which no current Gitea accepts. +[Net.ServicePointManager]::SecurityProtocol = + [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 + +$script:RepoRoot = Split-Path -Parent $PSScriptRoot + +function Get-RepoRoot { $script:RepoRoot } + +function Get-SetupConfig { + <# + The repo's config.json, with GITEA_URL / GITEA_OWNER / GITEA_REPO taking + precedence so a machine can point somewhere else without editing the file. + + -RequireGitea additionally insists the server has been filled in. Only the + scripts that talk to Gitea ask for that: building, linking and settings all + work on a checkout nobody has configured yet. + #> + param([switch] $RequireGitea) + + $path = Join-Path $script:RepoRoot 'config.json' + if (-not (Test-Path $path)) { + throw "config.json not found at $path" + } + $config = Get-Content $path -Raw | ConvertFrom-Json + + foreach ($pair in @( + @{ Env = 'GITEA_URL'; Key = 'url' }, + @{ Env = 'GITEA_OWNER'; Key = 'owner' }, + @{ Env = 'GITEA_REPO'; Key = 'repo' } + )) { + $value = [Environment]::GetEnvironmentVariable($pair.Env) + if ($value) { $config.gitea.($pair.Key) = $value } + } + + $config.gitea.url = $config.gitea.url.TrimEnd('/') + if ($RequireGitea -and $config.gitea.url -like '*example.com*') { + throw ('config.json still has the placeholder Gitea URL. Set it there, or set ' + + 'the GITEA_URL, GITEA_OWNER and GITEA_REPO environment variables.') + } + $config +} + +function Get-ExtensionPath { + <# Absolute path to an extension's source, from its repo-relative config entry. #> + param([Parameter(Mandatory)] $Extension) + [System.IO.Path]::GetFullPath((Join-Path $script:RepoRoot $Extension.path)) +} + +function Get-DistDir { + $dist = Join-Path $script:RepoRoot 'dist' + if (-not (Test-Path $dist)) { New-Item -ItemType Directory -Path $dist | Out-Null } + $dist +} + +function Get-GiteaToken { + <# + A token with write access to the release repo, from (in order) the argument, + GITEA_TOKEN, or %USERPROFILE%\.gitea-token. Never read from inside the repo, + so a token cannot be committed by accident. + + Create one at /user/settings/applications with scope write:repository. + #> + param([string] $Token) + + if ($Token) { return $Token } + if ($env:GITEA_TOKEN) { return $env:GITEA_TOKEN } + + $file = Join-Path $env:USERPROFILE '.gitea-token' + if (Test-Path $file) { + $value = (Get-Content $file -Raw).Trim() + if ($value) { return $value } + } + + throw ("No Gitea token. Set the GITEA_TOKEN environment variable, write one to " + + "$file, or pass -Token. Create it at /user/settings/applications " + + "with write:repository scope.") +} + +function Invoke-Gitea { + <# A JSON call against the Gitea API. Paths are relative to /api/v1. #> + param( + [Parameter(Mandatory)] [string] $Path, + [Parameter(Mandatory)] [string] $Token, + [string] $Method = 'GET', + $Body, + [switch] $AllowNotFound + ) + $config = Get-SetupConfig -RequireGitea + $uri = "$($config.gitea.url)/api/v1$Path" + + $arguments = @{ + Uri = $uri + Method = $Method + Headers = @{ Authorization = "token $Token"; Accept = 'application/json' } + UseBasicParsing = $true + } + if ($null -ne $Body) { + $arguments.Body = ($Body | ConvertTo-Json -Depth 6) + $arguments.ContentType = 'application/json' + } + + try { + Invoke-RestMethod @arguments + } catch { + $status = 0 + if ($_.Exception.Response) { $status = [int] $_.Exception.Response.StatusCode } + if ($AllowNotFound -and $status -eq 404) { return $null } + $detail = "Gitea $Method $Path failed" + if ($status) { $detail += " with HTTP $status" } + throw "${detail}: $($_.Exception.Message)" + } +} + +function Send-GiteaAsset { + <# + Uploads one file as a release asset. + + Gitea only accepts multipart/form-data here, and Invoke-RestMethod cannot build + that on 5.1 (-Form is PowerShell 6+), so this goes through HttpClient. That also + streams the file rather than reading it into a string, which matters because a + .vsix is a zip and any text encoding step would corrupt it. + #> + param( + [Parameter(Mandatory)] [int] $ReleaseId, + [Parameter(Mandatory)] [string] $FilePath, + [Parameter(Mandatory)] [string] $Token + ) + Add-Type -AssemblyName System.Net.Http + + $config = Get-SetupConfig -RequireGitea + $name = [System.IO.Path]::GetFileName($FilePath) + $uri = ("$($config.gitea.url)/api/v1/repos/$($config.gitea.owner)/" + + "$($config.gitea.repo)/releases/$ReleaseId/assets" + + "?name=$([Uri]::EscapeDataString($name))") + + $client = $null; $content = $null; $stream = $null + try { + $client = New-Object System.Net.Http.HttpClient + $client.Timeout = [TimeSpan]::FromMinutes(10) + $client.DefaultRequestHeaders.Authorization = + New-Object System.Net.Http.Headers.AuthenticationHeaderValue('token', $Token) + + $content = New-Object System.Net.Http.MultipartFormDataContent + $stream = [System.IO.File]::OpenRead($FilePath) + $file = New-Object System.Net.Http.StreamContent($stream) + $file.Headers.ContentType = + New-Object System.Net.Http.Headers.MediaTypeHeaderValue('application/octet-stream') + + # Set Content-Disposition rather than letting Add(content, name, fileName) do it. + # On .NET Framework that overload emits an unquoted `name=attachment` plus an + # RFC 5987 `filename*`; Gitea's Go parser copes, but this sends exactly what curl + # -F sends and leaves nothing to the server's leniency. The quotes have to be + # written in by hand -- the setters store the value verbatim. + $disposition = + New-Object System.Net.Http.Headers.ContentDispositionHeaderValue('form-data') + $disposition.Name = '"attachment"' + $disposition.FileName = '"' + $name + '"' + $file.Headers.ContentDisposition = $disposition + $content.Add($file) + + $response = $client.PostAsync($uri, $content).GetAwaiter().GetResult() + $text = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() + if (-not $response.IsSuccessStatusCode) { + throw "uploading $name failed with HTTP $([int] $response.StatusCode): $text" + } + $text | ConvertFrom-Json + } finally { + if ($stream) { $stream.Dispose() } + if ($content) { $content.Dispose() } + if ($client) { $client.Dispose() } + } +} + +function Get-GiteaRelease { + <# The release for a tag, or nothing if there is none. #> + param( + [Parameter(Mandatory)] [string] $Tag, + [Parameter(Mandatory)] [string] $Token + ) + $config = Get-SetupConfig -RequireGitea + $tagPath = [Uri]::EscapeDataString($Tag) + Invoke-Gitea -Token $Token -AllowNotFound ` + -Path "/repos/$($config.gitea.owner)/$($config.gitea.repo)/releases/tags/$tagPath" +} + +function Save-GiteaAsset { + <# + Downloads a release asset. + + Redirects are followed here by hand instead of using Invoke-WebRequest, because + a private repo needs the token and Gitea answers browser_download_url with a + redirect to the attachment -- and every automatic redirect follower drops the + Authorization header on the way, which comes back as a 401 that looks like a + bad token. + + The header is re-sent only when the redirect stays on the same host, so a + redirect off the instance cannot walk away with the token. + #> + param( + [Parameter(Mandatory)] [string] $Url, + [Parameter(Mandatory)] [string] $Destination, + [string] $Token, + [int] $MaxHops = 5 + ) + Add-Type -AssemblyName System.Net.Http + + $origin = ([Uri] $Url).Host + $handler = New-Object System.Net.Http.HttpClientHandler + $handler.AllowAutoRedirect = $false + $client = New-Object System.Net.Http.HttpClient($handler) + $client.Timeout = [TimeSpan]::FromMinutes(10) + + try { + $current = [Uri] $Url + for ($hop = 0; $hop -le $MaxHops; $hop++) { + $request = New-Object System.Net.Http.HttpRequestMessage('GET', $current) + if ($Token -and $current.Host -eq $origin) { + $request.Headers.Authorization = + New-Object System.Net.Http.Headers.AuthenticationHeaderValue('token', $Token) + } + + $response = $client.SendAsync( + $request, + [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead + ).GetAwaiter().GetResult() + + try { + $status = [int] $response.StatusCode + if ($status -ge 300 -and $status -lt 400 -and $response.Headers.Location) { + $next = $response.Headers.Location + if (-not $next.IsAbsoluteUri) { $next = New-Object Uri($current, $next) } + $current = $next + continue + } + if (-not $response.IsSuccessStatusCode) { + throw "downloading $current failed with HTTP $status" + } + + $input_ = $response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() + $output = [System.IO.File]::Create($Destination) + try { $input_.CopyTo($output) } finally { $output.Dispose(); $input_.Dispose() } + + if ((Get-Item $Destination).Length -eq 0) { + throw "downloaded $current but the file is empty" + } + return + } finally { $response.Dispose() } + } + throw "more than $MaxHops redirects starting at $Url" + } finally { + $client.Dispose() + $handler.Dispose() + } +} + +function Get-VsceCommand { + <# + Path to the vsce entry script, installing it on first use. + + It lives in vsce/ with its own pinned dependencies rather than being run through + npx, because current vsce needs Node 20+ and on Node 18 it dies with + "ReferenceError: File is not defined" from a transitive undici. vsce/package.json + pins both. On Node 20 or newer none of this matters and plain + "npx @vscode/vsce package" would do. + #> + $vsceRoot = Join-Path $script:RepoRoot 'vsce' + $entry = Join-Path $vsceRoot 'node_modules/@vscode/vsce/vsce' + if (-not (Test-Path $entry)) { + Write-Host "installing vsce into $vsceRoot (first run only)..." -ForegroundColor DarkGray + Push-Location $vsceRoot + try { + & npm install --silent --no-audit --no-fund + if ($LASTEXITCODE -ne 0) { throw 'npm install failed in vsce/' } + } finally { Pop-Location } + } + if (-not (Test-Path $entry)) { throw "vsce still not present at $entry" } + $entry +} + +function Get-CodeCommand { + <# The VS Code CLI. Not always on PATH, so fall back to the usual install locations. #> + $onPath = Get-Command code -ErrorAction SilentlyContinue + if ($onPath) { return $onPath.Source } + + $candidates = @( + (Join-Path $env:LOCALAPPDATA 'Programs\Microsoft VS Code\bin\code.cmd'), + 'C:\Program Files\Microsoft VS Code\bin\code.cmd', + (Join-Path $env:LOCALAPPDATA 'Programs\Microsoft VS Code Insiders\bin\code-insiders.cmd') + ) + foreach ($candidate in $candidates) { + if (Test-Path $candidate) { return $candidate } + } + throw ("The 'code' CLI was not found. In VS Code run " + + "'Shell Command: Install code command in PATH', or add its bin/ to PATH.") +} diff --git a/scripts/dev-link.ps1 b/scripts/dev-link.ps1 new file mode 100644 index 0000000..c42a436 --- /dev/null +++ b/scripts/dev-link.ps1 @@ -0,0 +1,111 @@ +<# +.SYNOPSIS + Links the extension sources into VS Code's extensions folder for development. + +.DESCRIPTION + Packaging is the wrong loop while you are actually changing code. VS Code loads any + folder under %USERPROFILE%\.vscode\extensions that has a package.json, so linking the + source there reduces the edit cycle to "npm run compile" plus a window reload. + + The link is a directory junction rather than a symlink because junctions need no + elevation and no Developer Mode on Windows, and VS Code cannot tell the difference. + + Run -Unlink before install.ps1: a linked source and an installed .vsix are two copies + of the same extension id, and VS Code will load one of them arbitrarily. + +.PARAMETER Only + Restrict to these extension ids. + +.PARAMETER Unlink + Remove the links. + +.PARAMETER Compile + Run npm run compile in each extension first. + +.EXAMPLE + .\scripts\dev-link.ps1 -Compile + .\scripts\dev-link.ps1 -Unlink +#> +[CmdletBinding()] +param( + [string[]] $Only, + [switch] $Unlink, + [switch] $Compile +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'common.ps1') + +$config = Get-SetupConfig +$extensionsDir = Join-Path $env:USERPROFILE '.vscode\extensions' +if (-not (Test-Path $extensionsDir)) { + New-Item -ItemType Directory -Path $extensionsDir -Force | Out-Null +} + +$extensions = $config.extensions +if ($Only) { + $extensions = $extensions | Where-Object { $Only -contains $_.id } + $missing = $Only | Where-Object { $config.extensions.id -notcontains $_ } + if ($missing) { throw "not in config.json: $($missing -join ', ')" } +} + +foreach ($extension in $extensions) { + $source = Get-ExtensionPath $extension + $manifestPath = Join-Path $source 'package.json' + if (-not (Test-Path $manifestPath)) { + Write-Warning "no package.json in $source, skipping $($extension.id)" + continue + } + $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json + $linkName = "$($manifest.publisher).$($manifest.name)-$($manifest.version)" + $link = Join-Path $extensionsDir $linkName + + if ($Unlink) { + if (Test-Path $link) { + # Remove-Item on a junction deletes the link, not the target -- but only + # without -Recurse, which would walk into the source and delete the files. + [System.IO.Directory]::Delete($link) + Write-Host "unlinked $linkName" -ForegroundColor Green + } else { + Write-Host "$linkName was not linked" -ForegroundColor DarkGray + } + continue + } + + if ($Compile) { + Push-Location $source + try { + Write-Host "compiling $($extension.id)..." -ForegroundColor DarkGray + if (-not (Test-Path (Join-Path $source 'node_modules'))) { + & npm install --silent --no-audit --no-fund + if ($LASTEXITCODE -ne 0) { throw "npm install failed for $($extension.id)" } + } + & npm run compile --silent + if ($LASTEXITCODE -ne 0) { throw "compile failed for $($extension.id)" } + } finally { Pop-Location } + } + + $main = $manifest.main -replace '^\./', '' + if (-not (Test-Path (Join-Path $source $main))) { + Write-Warning ("$($extension.id) has no built output at $main -- it will fail to " + + "activate. Pass -Compile.") + } + + if (Test-Path $link) { + $item = Get-Item $link -Force + $target = $item.Target + if ($target -and @($target)[0] -eq $source) { + Write-Host "$linkName already linked" -ForegroundColor DarkGray + continue + } + throw ("$link already exists and does not point at $source. Remove it by hand " + + "(it may be a real installed copy).") + } + + New-Item -ItemType Junction -Path $link -Target $source | Out-Null + Write-Host "linked $linkName -> $source" -ForegroundColor Green +} + +Write-Host '' +Write-Host 'Reload the window (Developer: Reload Window) to pick this up.' -ForegroundColor Cyan diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..d9e3032 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,135 @@ +<# +.SYNOPSIS + Installs the extensions into VS Code, from a Gitea release or from dist/. + +.DESCRIPTION + By default this downloads the .vsix assets from the -Tag release and installs them, + so a fresh machine needs nothing but VS Code and this repo -- no Node, no toolchain. + Pass -Local to install what build.ps1 produced instead. + + Every install passes --force. Without it VS Code declines to reinstall a version it + already has, which would silently do nothing every time you republish the same + version number. + +.PARAMETER Tag + Release tag to install from. Defaults to "latest". + +.PARAMETER Local + Install from dist/ rather than downloading. + +.PARAMETER Only + Restrict to these extension ids. + +.PARAMETER Uninstall + Remove the extensions instead of installing them. + +.EXAMPLE + .\scripts\install.ps1 + .\scripts\install.ps1 -Local + .\scripts\install.ps1 -Tag v0.1.0 + .\scripts\install.ps1 -Uninstall +#> +[CmdletBinding()] +param( + [string] $Tag = 'latest', + [string] $Token, + [string[]] $Only, + [switch] $Local, + [switch] $Uninstall +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'common.ps1') + +$config = Get-SetupConfig +$code = Get-CodeCommand + +function Select-Configured { + param([string[]] $Ids) + $chosen = $config.extensions + if ($Ids) { + $chosen = $chosen | Where-Object { $Ids -contains $_.id } + $missing = $Ids | Where-Object { $config.extensions.id -notcontains $_ } + if ($missing) { throw "not in config.json: $($missing -join ', ')" } + } + $chosen +} + +if ($Uninstall) { + foreach ($extension in Select-Configured $Only) { + # The manifest's publisher is "local", so the installed id is local.. + $id = "local.$($extension.id)" + Write-Host "uninstalling $id" -ForegroundColor DarkGray + & $code --uninstall-extension $id + } + Write-Host 'Reload the window for this to take effect.' -ForegroundColor Cyan + return +} + +$wanted = Select-Configured $Only +$packages = @() + +if ($Local) { + $dist = Get-DistDir + foreach ($extension in $wanted) { + $found = @(Get-ChildItem -Path $dist -Filter "$($extension.id)-*.vsix" | + Sort-Object Name -Descending) + if ($found.Count -eq 0) { + throw "no package for $($extension.id) in $dist. Run build.ps1 first." + } + $packages += $found[0].FullName + } +} else { + $Token = Get-GiteaToken -Token $Token + $release = Get-GiteaRelease -Tag $Tag -Token $Token + if (-not $release) { + throw ("no release tagged '$Tag' in " + + "$($config.gitea.owner)/$($config.gitea.repo). Run publish.ps1 first.") + } + + $assets = @() + if ($release.PSObject.Properties.Name -contains 'assets' -and $release.assets) { + $assets = @($release.assets) + } + if ($assets.Count -eq 0) { throw "release '$Tag' has no assets" } + + # Downloads go to a temp folder, not dist/, so an install never disturbs a build. + $staging = Join-Path ([System.IO.Path]::GetTempPath()) "vsix-$Tag-$PID" + New-Item -ItemType Directory -Path $staging -Force | Out-Null + + foreach ($extension in $wanted) { + $match = @($assets | + Where-Object { $_.name -like "$($extension.id)-*.vsix" } | + Sort-Object name -Descending) + if ($match.Count -eq 0) { + Write-Warning "release '$Tag' has no asset for $($extension.id), skipping" + continue + } + $asset = $match[0] + $target = Join-Path $staging $asset.name + Write-Host "downloading $($asset.name)" -ForegroundColor DarkGray + Save-GiteaAsset -Url $asset.browser_download_url -Destination $target -Token $Token + $packages += $target + } +} + +if ($packages.Count -eq 0) { throw 'nothing to install' } + +$failed = @() +foreach ($package in $packages) { + Write-Host "installing $(Split-Path -Leaf $package)" -NoNewline + & $code --install-extension $package --force + if ($LASTEXITCODE -eq 0) { + Write-Host ' ok' -ForegroundColor Green + } else { + Write-Host ' FAILED' -ForegroundColor Red + $failed += $package + } +} + +Write-Host '' +if ($failed.Count -gt 0) { + throw "$($failed.Count) of $($packages.Count) failed to install: $($failed -join ', ')" +} +Write-Host "installed $($packages.Count) extension(s). Reload the window." -ForegroundColor Cyan diff --git a/scripts/publish.ps1 b/scripts/publish.ps1 new file mode 100644 index 0000000..100810e --- /dev/null +++ b/scripts/publish.ps1 @@ -0,0 +1,112 @@ +<# +.SYNOPSIS + Uploads dist/*.vsix to a release on Gitea. + +.DESCRIPTION + Creates the release for -Tag if it does not exist, then uploads each .vsix as an + asset. An asset with the same name is deleted first, so re-publishing the same tag + replaces the packages instead of piling up duplicates -- which is what makes the + default rolling "latest" tag usable as the thing install.ps1 reads. + + Needs a token with write:repository scope. See Get-GiteaToken in common.ps1 for + where it is read from; it is never read from inside this repo. + +.PARAMETER Tag + The release tag. Defaults to "latest", a rolling release holding current builds. + Use a version tag for a snapshot you want to keep. + +.PARAMETER Build + Run build.ps1 first. + +.PARAMETER Notes + Release body. Defaults to a list of the packages and their versions. + +.EXAMPLE + .\scripts\publish.ps1 -Build + .\scripts\publish.ps1 -Tag v0.1.0 -Notes 'First cut of all three.' +#> +[CmdletBinding()] +param( + [string] $Tag = 'latest', + [string] $Token, + [string] $Notes, + [string] $Target, + [switch] $Build, + [switch] $Prerelease +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'common.ps1') + +if ($Build) { & (Join-Path $PSScriptRoot 'build.ps1') | Out-Null } + +$config = Get-SetupConfig +$Token = Get-GiteaToken -Token $Token +$dist = Get-DistDir +$repoPath = "/repos/$($config.gitea.owner)/$($config.gitea.repo)" + +$packages = @(Get-ChildItem -Path $dist -Filter '*.vsix' | Sort-Object Name) +if ($packages.Count -eq 0) { + throw "no .vsix files in $dist. Run build.ps1, or pass -Build." +} + +Write-Host "publishing $($packages.Count) package(s) to " -NoNewline +Write-Host "$($config.gitea.url)/$($config.gitea.owner)/$($config.gitea.repo)@$Tag" -ForegroundColor Cyan + +$release = Get-GiteaRelease -Tag $Tag -Token $Token +if ($release) { + Write-Host "reusing release $($release.id)" -ForegroundColor DarkGray +} else { + if (-not $Notes) { + $lines = $packages | ForEach-Object { + $name = $_.BaseName + "- $name" + } + $Notes = @( + 'VS Code extensions, packaged as VSIX.', + '', + ($lines -join "`n"), + '', + 'Install them with scripts/install.ps1, or by hand:', + '', + ' code --install-extension .vsix --force' + ) -join "`n" + } + + $body = @{ + tag_name = $Tag + name = "VS Code extensions ($Tag)" + body = $Notes + draft = $false + prerelease = [bool] $Prerelease + } + # Omitted means Gitea creates the tag on the default branch. + if ($Target) { $body.target_commitish = $Target } + + $release = Invoke-Gitea -Token $Token -Method 'POST' -Path "$repoPath/releases" -Body $body + Write-Host "created release $($release.id) for tag $Tag" -ForegroundColor DarkGray +} + +$existing = @() +if ($release.PSObject.Properties.Name -contains 'assets' -and $release.assets) { + $existing = @($release.assets) +} + +foreach ($package in $packages) { + $clash = $existing | Where-Object { $_.name -eq $package.Name } + foreach ($old in $clash) { + Write-Host " replacing $($package.Name)" -ForegroundColor DarkGray + Invoke-Gitea -Token $Token -Method 'DELETE' ` + -Path "$repoPath/releases/$($release.id)/assets/$($old.id)" | Out-Null + } + + $size = [math]::Round($package.Length / 1KB, 1) + Write-Host " uploading $($package.Name) ($size KB)" -NoNewline + $asset = Send-GiteaAsset -ReleaseId $release.id -FilePath $package.FullName -Token $Token + Write-Host " ok" -ForegroundColor Green + Write-Verbose " $($asset.browser_download_url)" +} + +Write-Host '' +Write-Host "done: $($config.gitea.url)/$($config.gitea.owner)/$($config.gitea.repo)/releases/tag/$Tag" -ForegroundColor Cyan diff --git a/scripts/settings.ps1 b/scripts/settings.ps1 new file mode 100644 index 0000000..eee0ad8 --- /dev/null +++ b/scripts/settings.ps1 @@ -0,0 +1,192 @@ +<# +.SYNOPSIS + Moves VS Code user settings between this repo and the machine. + +.DESCRIPTION + -Push copies the machine's settings into User/ so you can commit them. + -Pull copies User/ onto the machine, backing up whatever was there. + -Diff says what differs without touching anything. + + This copies rather than symlinking on purpose. A symlink at + %APPDATA%\Code\User\settings.json is fragile: an editor that saves atomically + (write a temp file, rename it over the target) replaces the link with a regular + file, and the sync silently stops without anything looking wrong. Copying is + explicit -- you can see in git what changed and when. + + -Push also records your marketplace extensions in User/extensions.txt, and -Pull + installs any that are missing. Ids starting "local." are skipped: those are the + extensions in this repo, and install.ps1 gets them from the Gitea release. + +.EXAMPLE + .\scripts\settings.ps1 -Diff + .\scripts\settings.ps1 -Push + .\scripts\settings.ps1 -Pull +#> +[CmdletBinding(DefaultParameterSetName = 'Diff')] +param( + [Parameter(ParameterSetName = 'Push', Mandatory)] [switch] $Push, + [Parameter(ParameterSetName = 'Pull', Mandatory)] [switch] $Pull, + [Parameter(ParameterSetName = 'Diff')] [switch] $Diff, + [switch] $NoExtensions +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'common.ps1') + +$repoUser = Join-Path (Get-RepoRoot) 'User' +$codeUser = Join-Path $env:APPDATA 'Code\User' + +if (-not (Test-Path $codeUser)) { + throw ("VS Code's user folder was not found at $codeUser. If you use Insiders or a " + + "portable install, point APPDATA at it or copy by hand.") +} + +# Only the things worth version-controlling. Everything else in User/ is state: +# globalStorage, workspaceStorage, History, sync, logs. +$items = @( + @{ Name = 'settings.json'; Kind = 'File' }, + @{ Name = 'keybindings.json'; Kind = 'File' }, + @{ Name = 'tasks.json'; Kind = 'File' }, + @{ Name = 'snippets'; Kind = 'Directory' } +) + +function Get-Fingerprint { + param([string] $Path, [string] $Kind) + if (-not (Test-Path $Path)) { return $null } + if ($Kind -eq 'File') { return (Get-FileHash $Path -Algorithm SHA256).Hash } + + $parts = Get-ChildItem -Path $Path -Recurse -File | Sort-Object FullName | ForEach-Object { + "$($_.FullName.Substring($Path.Length)):$((Get-FileHash $_.FullName -Algorithm SHA256).Hash)" + } + if (-not $parts) { return $null } + $joined = $parts -join '|' + $bytes = [System.Text.Encoding]::UTF8.GetBytes($joined) + $sha = [System.Security.Cryptography.SHA256]::Create() + try { + ([BitConverter]::ToString($sha.ComputeHash($bytes))) -replace '-', '' + } finally { $sha.Dispose() } +} + +function Copy-Item2 { + param([string] $From, [string] $To, [string] $Kind) + $parent = Split-Path -Parent $To + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } + + if ($Kind -eq 'File') { + Copy-Item -Path $From -Destination $To -Force + } else { + if (Test-Path $To) { Remove-Item -Path $To -Recurse -Force } + Copy-Item -Path $From -Destination $To -Recurse -Force + } +} + +function Backup-Existing { + param([string] $Path, [string] $Kind) + if (-not (Test-Path $Path)) { return } + $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $backup = "$Path.backup-$stamp" + if ($Kind -eq 'File') { + Copy-Item -Path $Path -Destination $backup -Force + } else { + Copy-Item -Path $Path -Destination $backup -Recurse -Force + } + Write-Host " backed up to $(Split-Path -Leaf $backup)" -ForegroundColor DarkGray +} + +$mode = $PSCmdlet.ParameterSetName +Write-Host "repo: $repoUser" +Write-Host "code: $codeUser" +Write-Host '' + +foreach ($item in $items) { + $inRepo = Join-Path $repoUser $item.Name + $onDisk = Join-Path $codeUser $item.Name + $repoPrint = Get-Fingerprint -Path $inRepo -Kind $item.Kind + $diskPrint = Get-Fingerprint -Path $onDisk -Kind $item.Kind + + if (-not $repoPrint -and -not $diskPrint) { + Write-Host " $($item.Name): absent both sides" -ForegroundColor DarkGray + continue + } + if ($repoPrint -eq $diskPrint) { + Write-Host " $($item.Name): identical" -ForegroundColor DarkGray + continue + } + + switch ($mode) { + 'Push' { + if (-not $diskPrint) { + Write-Host " $($item.Name): not on this machine, leaving the repo copy alone" ` + -ForegroundColor Yellow + continue + } + Copy-Item2 -From $onDisk -To $inRepo -Kind $item.Kind + Write-Host " $($item.Name): machine -> repo" -ForegroundColor Green + } + 'Pull' { + if (-not $repoPrint) { + Write-Host " $($item.Name): not in the repo, leaving the machine alone" ` + -ForegroundColor Yellow + continue + } + Backup-Existing -Path $onDisk -Kind $item.Kind + Copy-Item2 -From $inRepo -To $onDisk -Kind $item.Kind + Write-Host " $($item.Name): repo -> machine" -ForegroundColor Green + } + default { + $where = if (-not $repoPrint) { 'only on this machine' } + elseif (-not $diskPrint) { 'only in the repo' } + else { 'differs' } + Write-Host " $($item.Name): $where" -ForegroundColor Yellow + } + } +} + +if ($NoExtensions) { return } + +Write-Host '' +$listPath = Join-Path $repoUser 'extensions.txt' +$code = Get-CodeCommand + +if ($mode -eq 'Push') { + $installed = @(& $code --list-extensions) | + Where-Object { $_ -and $_ -notlike 'local.*' } | + Sort-Object + if (-not (Test-Path $repoUser)) { New-Item -ItemType Directory -Path $repoUser | Out-Null } + Set-Content -Path $listPath -Value $installed -Encoding UTF8 + Write-Host " extensions.txt: recorded $($installed.Count) marketplace extension(s)" ` + -ForegroundColor Green + return +} + +if (-not (Test-Path $listPath)) { + Write-Host ' extensions.txt: not in the repo yet, run -Push on your main machine' ` + -ForegroundColor Yellow + return +} + +$wanted = @(Get-Content $listPath | Where-Object { $_ -and -not $_.StartsWith('#') }) +$present = @(& $code --list-extensions) +$missing = @($wanted | Where-Object { $present -notcontains $_ }) + +if ($mode -eq 'Diff') { + if ($missing.Count -eq 0) { + Write-Host " extensions.txt: all $($wanted.Count) present" -ForegroundColor DarkGray + } else { + Write-Host " extensions.txt: $($missing.Count) missing: $($missing -join ', ')" ` + -ForegroundColor Yellow + } + return +} + +if ($missing.Count -eq 0) { + Write-Host " extensions.txt: all $($wanted.Count) already installed" -ForegroundColor DarkGray + return +} +foreach ($id in $missing) { + Write-Host " installing $id" -ForegroundColor DarkGray + & $code --install-extension $id +} +Write-Host '' +Write-Host 'Reload the window to pick up new settings and extensions.' -ForegroundColor Cyan diff --git a/test/fake-gitea.js b/test/fake-gitea.js new file mode 100644 index 0000000..4e924ff --- /dev/null +++ b/test/fake-gitea.js @@ -0,0 +1,215 @@ +// A stub of the parts of the Gitea API that publish.ps1 and install.ps1 use. +// +// The point is to prove the real scripts work: that the token header is sent, that the +// multipart upload delivers the .vsix byte-for-byte (a zip, so any encoding step would +// corrupt it), that re-publishing replaces assets instead of duplicating them, and that +// the download survives a redirect with the auth header intact. + +const http = require('http'); +const crypto = require('crypto'); + +const PORT = Number(process.env.PORT || 5602); +const TOKEN = 'TESTTOKEN'; + +let nextId = 100; +const releases = []; // { id, tag_name, name, body, assets: [] } +const blobs = new Map(); // uuid -> Buffer +const log = []; + +function sha256(buffer) { + return crypto.createHash('sha256').update(buffer).digest('hex').toUpperCase(); +} + +function authorized(request) { + return request.headers.authorization === `token ${TOKEN}`; +} + +function send(response, status, payload) { + const body = payload === undefined ? '' : JSON.stringify(payload); + response.writeHead(status, { 'Content-Type': 'application/json' }); + response.end(body); +} + +function readBody(request) { + return new Promise((resolve, reject) => { + const chunks = []; + request.on('data', chunk => chunks.push(chunk)); + request.on('end', () => resolve(Buffer.concat(chunks))); + request.on('error', reject); + }); +} + +/** The one file out of a multipart/form-data body, as raw bytes. */ +function parseMultipart(buffer, contentType) { + const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType || ''); + if (!match) { return null; } + const boundary = Buffer.from(`--${match[1] || match[2]}`); + + const parts = []; + let at = buffer.indexOf(boundary); + while (at >= 0) { + const from = at + boundary.length; + const next = buffer.indexOf(boundary, from); + if (next < 0) { break; } + parts.push(buffer.slice(from, next)); + at = next; + } + + for (const part of parts) { + const split = part.indexOf('\r\n\r\n'); + if (split < 0) { continue; } + const headers = part.slice(0, split).toString('latin1'); + // Trailing CRLF belongs to the delimiter, not the content. + let content = part.slice(split + 4); + if (content.slice(-2).toString('latin1') === '\r\n') { + content = content.slice(0, -2); + } + // Values may be unquoted: .NET Framework's MultipartFormDataContent writes + // name=attachment, and Go's mime/multipart (what Gitea uses) accepts both. + const name = /\bname=(?:"([^"]*)"|([^;\s]+))/i.exec(headers); + const filename = /\bfilename=(?:"([^"]*)"|([^;\s]+))/i.exec(headers); + const fieldName = name ? (name[1] !== undefined ? name[1] : name[2]) : null; + if (fieldName === 'attachment') { + return { + content, + filename: filename ? (filename[1] !== undefined ? filename[1] : filename[2]) : null, + headers: headers.trim(), + }; + } + } + return null; +} + +const server = http.createServer(async (request, response) => { + const url = new URL(request.url, 'http://localhost'); + const path = url.pathname; + log.push(`${request.method} ${request.url}`); + + // Serving an attachment: what browser_download_url points at. + let hit = /^\/attachments\/([^/]+)$/.exec(path); + if (hit && request.method === 'GET') { + // Redirect once, so the client has to carry the auth header across it. + response.writeHead(302, { Location: `/files/${hit[1]}` }); + return response.end(); + } + hit = /^\/files\/([^/]+)$/.exec(path); + if (hit && request.method === 'GET') { + if (!authorized(request)) { + log.push(' !! download had no token'); + return send(response, 401, { message: 'token required on download' }); + } + const blob = blobs.get(hit[1]); + if (!blob) { return send(response, 404, { message: 'no such attachment' }); } + response.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Length': blob.length, + }); + return response.end(blob); + } + + // Test-only: lets the harness assert on what the server actually received. + if (path === '/__state') { + return send(response, 200, { + log, + releases: releases.map(release => ({ + id: release.id, + tag_name: release.tag_name, + name: release.name, + assets: release.assets.map(asset => ({ + id: asset.id, + name: asset.name, + size: asset.size, + sha256: asset.sha256, + uploadFilename: asset.uploadFilename, + })), + })), + }); + } + + if (!authorized(request)) { + log.push(' !! missing or wrong token'); + return send(response, 401, { message: 'token required' }); + } + + const api = /^\/api\/v1\/repos\/([^/]+)\/([^/]+)\/releases(.*)$/.exec(path); + if (!api) { return send(response, 404, { message: `unhandled ${path}` }); } + const rest = api[3]; + + // GET /releases/tags/:tag + hit = /^\/tags\/(.+)$/.exec(rest); + if (hit && request.method === 'GET') { + const tag = decodeURIComponent(hit[1]); + const release = releases.find(candidate => candidate.tag_name === tag); + if (!release) { return send(response, 404, { message: 'release not found' }); } + return send(response, 200, release); + } + + // POST /releases + if (rest === '' && request.method === 'POST') { + const body = JSON.parse((await readBody(request)).toString('utf8') || '{}'); + if (!body.tag_name) { return send(response, 422, { message: 'tag_name required' }); } + const release = { + id: nextId++, + tag_name: body.tag_name, + name: body.name || body.tag_name, + body: body.body || '', + draft: !!body.draft, + prerelease: !!body.prerelease, + target_commitish: body.target_commitish || 'main', + assets: [], + }; + releases.push(release); + return send(response, 201, release); + } + + // POST /releases/:id/assets?name=... + hit = /^\/(\d+)\/assets$/.exec(rest); + if (hit && request.method === 'POST') { + const release = releases.find(candidate => candidate.id === Number(hit[1])); + if (!release) { return send(response, 404, { message: 'no such release' }); } + + const raw = await readBody(request); + const part = parseMultipart(raw, request.headers['content-type']); + if (!part) { + const preview = raw.slice(0, 400).toString('latin1').replace(/\r\n/g, '\\r\\n'); + log.push(` !! no attachment field. content-type=${request.headers['content-type']}`); + log.push(` !! body starts: ${preview}`); + return send(response, 400, { message: 'expected multipart with an attachment field' }); + } + log.push(` disposition: ${part.headers.replace(/\r\n/g, ' | ')}`); + + const name = url.searchParams.get('name') || part.filename || 'unnamed'; + const uuid = crypto.randomBytes(8).toString('hex'); + blobs.set(uuid, part.content); + const asset = { + id: nextId++, + name, + size: part.content.length, + sha256: sha256(part.content), + uploadFilename: part.filename, + browser_download_url: `http://127.0.0.1:${PORT}/attachments/${uuid}`, + }; + release.assets.push(asset); + return send(response, 201, asset); + } + + // DELETE /releases/:id/assets/:assetId + hit = /^\/(\d+)\/assets\/(\d+)$/.exec(rest); + if (hit && request.method === 'DELETE') { + const release = releases.find(candidate => candidate.id === Number(hit[1])); + if (!release) { return send(response, 404, { message: 'no such release' }); } + const before = release.assets.length; + release.assets = release.assets.filter(asset => asset.id !== Number(hit[2])); + if (release.assets.length === before) { + return send(response, 404, { message: 'no such asset' }); + } + response.writeHead(204); + return response.end(); + } + + return send(response, 404, { message: `unhandled ${request.method} ${path}` }); +}); + +server.listen(PORT, '127.0.0.1', () => { + console.log(`fake gitea on http://127.0.0.1:${PORT} (token ${TOKEN})`); +}); diff --git a/test/run.ps1 b/test/run.ps1 new file mode 100644 index 0000000..dffb87c --- /dev/null +++ b/test/run.ps1 @@ -0,0 +1,204 @@ +<# +.SYNOPSIS + Exercises publish.ps1, install.ps1 and ci-release.sh against a stub Gitea. + +.DESCRIPTION + Runs the real scripts, pointed at test/fake-gitea.js instead of a server, with a + stub "code" on PATH so nothing is installed into the editor. What it checks: + + - the token reaches the API, and the download after the redirect + - the .vsix arrives byte-for-byte (it is a zip, so any text encoding step shows up) + - republishing a tag replaces assets instead of duplicating them + - install passes --force, without which VS Code silently skips a same-version + reinstall + - build.ps1 and install.ps1 -Local work with no Gitea configured + - ci-release.sh agrees with publish.ps1 + + Needs dist/*.vsix, so it runs build.ps1 first unless -SkipBuild. + +.EXAMPLE + .\test\run.ps1 + .\test\run.ps1 -SkipBuild +#> +[CmdletBinding()] +param( + [int] $Port = 5602, + [switch] $SkipBuild +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$root = Split-Path -Parent $PSScriptRoot +$scripts = Join-Path $root 'scripts' +$dist = Join-Path $root 'dist' +$base = "http://127.0.0.1:$Port" + +$failures = New-Object System.Collections.ArrayList +function Assert-That { + param([string] $What, [bool] $Condition, [string] $Detail = '') + if ($Condition) { + Write-Host " PASS $What" -ForegroundColor Green + } else { + Write-Host " FAIL $What" -ForegroundColor Red + if ($Detail) { Write-Host " $Detail" -ForegroundColor DarkGray } + [void] $failures.Add($What) + } +} + +function Get-State { Invoke-RestMethod -Uri "$base/__state" -UseBasicParsing } + +# A stub code CLI, so an install never touches the real editor. It has to be found by +# Get-CodeCommand, which looks at PATH first. +$stubDir = Join-Path ([System.IO.Path]::GetTempPath()) "vscode-setup-test-$PID" +New-Item -ItemType Directory -Path $stubDir -Force | Out-Null +$callLog = Join-Path $stubDir 'calls.log' +Set-Content -Path (Join-Path $stubDir 'code.cmd') -Encoding ASCII -Value @( + '@echo off', + "echo %* >> `"$callLog`"", + 'exit /b 0' +) + +$server = $null +$savedPath = $env:PATH +try { + if (-not $SkipBuild) { + Write-Host 'building...' -ForegroundColor Cyan + & (Join-Path $scripts 'build.ps1') | Out-Null + } + $packages = @(Get-ChildItem -Path $dist -Filter '*.vsix') + if ($packages.Count -eq 0) { throw "no .vsix in $dist" } + + Write-Host "starting the stub on $base" -ForegroundColor Cyan + # Set before spawning: the child inherits the environment as it is at that moment. + $env:PORT = "$Port" + $server = Start-Process -FilePath 'node' ` + -ArgumentList (Join-Path $PSScriptRoot 'fake-gitea.js') ` + -WindowStyle Hidden -PassThru + + $up = $false + foreach ($attempt in 1..20) { + try { Get-State | Out-Null; $up = $true; break } catch { Start-Sleep -Milliseconds 250 } + } + if (-not $up) { throw "the stub did not come up on $base" } + + $env:GITEA_URL = $base + $env:GITEA_OWNER = 'max' + $env:GITEA_REPO = 'vscode-extensions' + $env:GITEA_TOKEN = 'TESTTOKEN' + $env:PATH = "$stubDir;$savedPath" + + Write-Host '' + Write-Host 'publish.ps1' -ForegroundColor Cyan + & (Join-Path $scripts 'publish.ps1') -Tag 'test' | Out-Null + $state = Get-State + $release = $state.releases | Where-Object { $_.tag_name -eq 'test' } + Assert-That 'the release is created' ($null -ne $release) + Assert-That "all $($packages.Count) packages are uploaded" ` + ($release.assets.Count -eq $packages.Count) "got $($release.assets.Count)" + + $intact = $true + foreach ($asset in $release.assets) { + $local = Join-Path $dist $asset.name + if ((Get-FileHash $local -Algorithm SHA256).Hash -ne $asset.sha256) { $intact = $false } + } + Assert-That 'the uploaded bytes match dist/ exactly' $intact + + $quoted = @($state.log | Where-Object { $_ -like '*disposition*' }) + Assert-That 'the multipart field is name="attachment"' ` + (($quoted.Count -gt 0) -and ($quoted[-1] -like '*name="attachment"*')) ` + ($(if ($quoted.Count) { $quoted[-1] } else { 'no disposition logged' })) + + Write-Host '' + Write-Host 'publish.ps1 again' -ForegroundColor Cyan + & (Join-Path $scripts 'publish.ps1') -Tag 'test' | Out-Null + $release = (Get-State).releases | Where-Object { $_.tag_name -eq 'test' } + Assert-That 'republishing replaces rather than duplicates' ` + ($release.assets.Count -eq $packages.Count) "got $($release.assets.Count)" + + Write-Host '' + Write-Host 'install.ps1' -ForegroundColor Cyan + Remove-Item $callLog -ErrorAction SilentlyContinue + & (Join-Path $scripts 'install.ps1') -Tag 'test' | Out-Null + $calls = @(Get-Content $callLog -ErrorAction SilentlyContinue) + Assert-That "code was called once per extension" ($calls.Count -eq $packages.Count) ` + "got $($calls.Count)" + Assert-That 'every install passes --force' ` + (($calls.Count -gt 0) -and -not ($calls | Where-Object { $_ -notmatch '--force' })) + + $downloadedOk = $calls.Count -gt 0 + foreach ($call in $calls) { + $path = ($call -replace '^--install-extension\s+', '' -replace '\s+--force\s*$', '').Trim() + $local = Join-Path $dist (Split-Path -Leaf $path) + if (-not (Test-Path $path) -or + (Get-FileHash $path -Algorithm SHA256).Hash -ne + (Get-FileHash $local -Algorithm SHA256).Hash) { $downloadedOk = $false } + } + Assert-That 'the downloaded files match dist/ (token survived the redirect)' $downloadedOk + + Write-Host '' + Write-Host 'no Gitea configured' -ForegroundColor Cyan + $env:GITEA_URL = ''; $env:GITEA_OWNER = ''; $env:GITEA_REPO = '' + Remove-Item $callLog -ErrorAction SilentlyContinue + $localWorked = $true + try { & (Join-Path $scripts 'install.ps1') -Local | Out-Null } catch { $localWorked = $false } + Assert-That 'install.ps1 -Local needs no Gitea config' $localWorked + $env:GITEA_URL = $base; $env:GITEA_OWNER = 'max'; $env:GITEA_REPO = 'vscode-extensions' + + # Git's bash, not the one on PATH: on Windows that is usually + # System32\bash.exe, the WSL launcher, which sees D:\ as /mnt/d and so cannot open + # the script by the path we have. + $bash = @( + 'C:\Program Files\Git\bin\bash.exe', + 'C:\Program Files (x86)\Git\bin\bash.exe', + (Join-Path $env:LOCALAPPDATA 'Programs\Git\bin\bash.exe') + ) | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if (-not $bash) { + $onPath = Get-Command bash -ErrorAction SilentlyContinue + if ($onPath -and $onPath.Source -notlike "$env:WINDIR*") { $bash = $onPath.Source } + } + + if ($bash) { + Write-Host '' + Write-Host 'ci-release.sh' -ForegroundColor Cyan + $env:GITEA_SERVER = $base + $env:TAG = 'test-ci' + $env:SKIP_PACKAGE = '1' + # Forward slashes too: bash reads the backslashes in a Windows path as escapes, + # so D:\a\b arrives as D:ab. + $shPath = (Join-Path $scripts 'ci-release.sh') -replace '\\', '/' + & $bash $shPath 2>&1 | Out-Null + $ci = (Get-State).releases | Where-Object { $_.tag_name -eq 'test-ci' } + Assert-That 'ci-release.sh publishes the same set' ` + (($null -ne $ci) -and ($ci.assets.Count -eq $packages.Count)) + $ciIntact = $null -ne $ci + if ($ci) { + foreach ($asset in $ci.assets) { + $local = Join-Path $dist $asset.name + if ((Get-FileHash $local -Algorithm SHA256).Hash -ne $asset.sha256) { + $ciIntact = $false + } + } + } + Assert-That 'ci-release.sh uploads intact bytes' $ciIntact + Remove-Item Env:\SKIP_PACKAGE, Env:\TAG, Env:\GITEA_SERVER -ErrorAction SilentlyContinue + } else { + Write-Host ' SKIP ci-release.sh (no bash on PATH)' -ForegroundColor Yellow + } +} finally { + $env:PATH = $savedPath + Remove-Item Env:\GITEA_URL, Env:\GITEA_OWNER, Env:\GITEA_REPO, Env:\GITEA_TOKEN, Env:\PORT ` + -ErrorAction SilentlyContinue + if ($server -and -not $server.HasExited) { Stop-Process -Id $server.Id -Force } + Remove-Item $stubDir -Recurse -Force -ErrorAction SilentlyContinue +} + +Write-Host '' +if ($failures.Count -eq 0) { + Write-Host 'all checks passed' -ForegroundColor Green + exit 0 +} +Write-Host "$($failures.Count) check(s) failed:" -ForegroundColor Red +$failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } +exit 1 diff --git a/vsce/package-lock.json b/vsce/package-lock.json new file mode 100644 index 0000000..a8f24ca --- /dev/null +++ b/vsce/package-lock.json @@ -0,0 +1,2260 @@ +{ + "name": "vsce-host", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vsce-host", + "version": "1.0.0", + "devDependencies": { + "@vscode/vsce": "2.32.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", + "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.1.tgz", + "integrity": "sha512-2QygG2F76ZpMP2eMztiJvAiFMu71M9rDeU7vO/QKg5Css7MgM4frUOslFjhVjRhbGaCNPtz/S8M6y46/fFKVuQ==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-process": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/core-process/-/core-process-1.0.0.tgz", + "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", + "dev": true, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", + "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", + "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", + "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.2.tgz", + "integrity": "sha512-NXL2/pCJctLxgw8bvrwwgge743kEq8LBT+O1pmV0vyUwetzFPH9auP6jhkU/cgZCPPtWoewAe3ncaGCgPo07fA==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-process": "^1.0.0", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.5", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", + "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", + "dev": true, + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.21.0.tgz", + "integrity": "sha512-80OcuXDErmcEDAIH9pBtSqBsed2sPT/IWmbG3xHLoPMl5zc8TINd6SlJAbVSmN5huGa3xGAg5qR7VnpaIEK0Zw==", + "dev": true, + "dependencies": { + "@azure/msal-common": "16.14.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.0.tgz", + "integrity": "sha512-A4rb55hI86Q9tBl/+jBj7TMz7iX2RFgQs/nExFzcAtoI/BFRVdaH5SL/MivrYD7qvweMpN8AgVvVMHV8UBYxew==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.6.0.tgz", + "integrity": "sha512-uFY9NxrWHw8PwZx7gAX6PDn+9vdfS05+levc/kwkx77IkjfaldnQbbcQzzDIZ5Hq5Zdr6/z92oAIoRWKp6MnOA==", + "dev": true, + "dependencies": { + "@azure/msal-common": "16.13.0", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.13.0.tgz", + "integrity": "sha512-rOAy0KUcyBbdwVJ+f3uPpthXatFLLZN+/KWAsTLzk1aB23Xl9DRmmXYwSvBFOZyXj4jUQQ5FKxxRkhAFW1fOow==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", + "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", + "dev": true, + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@vscode/vsce": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", + "integrity": "sha512-3EFJfsgrSftIqt3EtdRcAygy/OJ3hstyI1cDmIgkU9CFZW5C+3djr6mfosndCUqcVYuyjmxOK1xmFp/Bq7+NIg==", + "dev": true, + "dependencies": { + "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.1.0.tgz", + "integrity": "sha512-9AQrqazrBgTgRSuwleLVXUrIUphY02/SFCh2TKYoLV/xifJAdblhdmEmw5gUrYSPQ3sRwNs9iyCMD14sATEE6g==", + "dev": true, + "hasInstallScript": true, + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "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 + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "optional": true + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "optional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "optional": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "optional": true + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "optional": true + }, + "node_modules/node-abi": { + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", + "dev": true, + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "optional": true + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "optional": true + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3" + } + } + } +} diff --git a/vsce/package.json b/vsce/package.json new file mode 100644 index 0000000..771a059 --- /dev/null +++ b/vsce/package.json @@ -0,0 +1,12 @@ +{ + "name": "vsce-host", + "private": true, + "version": "1.0.0", + "description": "Pins vsce to a version that runs on Node 18. See ../README.md.", + "devDependencies": { + "@vscode/vsce": "2.32.0" + }, + "overrides": { + "undici": "6.21.3" + } +}