From f4e742f57128b4d2f99f9489db43c721b8c85a8e Mon Sep 17 00:00:00 2001 From: ajaykontham Date: Mon, 29 Jun 2026 22:51:28 -0400 Subject: [PATCH] Added cert utility and documentation --- CHANGELOG.md | 41 +- CONTRIBUTING.md | 116 + README.md | 455 +- package-lock.json | 5932 ++++++++++++++++- package.json | 182 +- resources/accessibility.test.js | 80 + resources/codicon.css | 708 ++ resources/codicon.ttf | Bin 0 -> 125828 bytes resources/commit.css | 251 +- resources/commit.js | 303 +- resources/commit.test.js | 136 + resources/commitView.js | 112 + resources/commitView.test.js | 169 + resources/documentation.css | 675 ++ resources/documentation.js | 433 ++ resources/graph.css | 122 +- resources/graph.js | 217 +- resources/icon-sourcecontrol.svg | 3 + resources/icon.png | Bin 0 -> 2585 bytes resources/icon.svg | 8 +- resources/manifest.test.js | 210 +- resources/phase10.test.js | 82 + resources/seti.css | 217 + resources/seti.woff | Bin 0 -> 37284 bytes resources/setiIcons.js | 45 + scripts/verify-vsix.mjs | 85 + src/commands/branch.ts | 101 +- src/commands/clone.ts | 3 +- src/commands/compare.ts | 5 +- src/commands/fileContext.ts | 58 +- src/commands/history.ts | 1 + src/commands/lfs.ts | 27 +- src/commands/maintenance.ts | 12 +- src/commands/notes.ts | 12 +- src/commands/patch.ts | 9 +- src/commands/reflog.ts | 12 +- src/commands/remote.ts | 12 +- src/commands/replace.ts | 50 +- src/commands/scm.ts | 209 +- src/commands/shared.ts | 61 +- src/commands/staging.ts | 13 +- src/commands/stash.ts | 23 +- src/commands/subtree.ts | 14 +- src/commands/tag.ts | 45 +- src/commands/transport.ts | 27 +- src/commands/worktree.ts | 21 +- src/decorations/BlameController.ts | 6 +- src/extension.ts | 51 +- src/git/GitContentProvider.ts | 18 +- src/git/GitExecutor.test.ts | 76 - src/git/GitExecutor.ts | 28 +- src/git/QuickDiffProvider.ts | 14 +- src/git/Repository.ts | 53 +- src/git/RepositoryManager.ts | 239 +- src/services/AutoFetchService.ts | 5 - src/test/extension.integration.test.ts | 45 + src/util/commandPreview.ts | 85 + src/util/html.test.ts | 27 - src/util/html.ts | 8 - src/views/CompareProvider.ts | 19 +- src/views/ConflictsProvider.ts | 11 +- src/views/ReflogProvider.ts | 10 +- src/views/RepositoriesProvider.ts | 52 +- src/views/ScmProvider.ts | 195 + src/views/StagingProvider.ts | 21 +- src/views/SynchronizeProvider.ts | 15 +- src/views/WorktreesProvider.ts | 15 +- src/views/treeAccessibility.ts | 13 + src/webviews/CommitPickerView.ts | 22 +- src/webviews/CreateTagDialog.ts | 26 +- src/webviews/HistoryView.ts | 45 +- src/webviews/commit/CommitViewProvider.ts | 129 +- src/webviews/commitPickerHtml.ts | 55 +- src/webviews/configHtml.ts | 72 +- .../documentation/DocumentationProvider.ts | 218 + .../documentationContent.test.ts | 68 + .../documentation/documentationContent.ts | 639 ++ src/webviews/editTextHtml.ts | 28 +- src/webviews/graph/GraphPanel.ts | 46 +- src/webviews/historyHtml.ts | 91 +- src/webviews/rebaseTodoHtml.ts | 24 +- src/webviews/refPickerHtml.ts | 48 +- tsconfig.integration.json | 13 + tsconfig.test.json | 2 + 84 files changed, 12703 insertions(+), 1126 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 resources/accessibility.test.js create mode 100644 resources/codicon.css create mode 100644 resources/codicon.ttf create mode 100644 resources/commit.test.js create mode 100644 resources/commitView.js create mode 100644 resources/commitView.test.js create mode 100644 resources/documentation.css create mode 100644 resources/documentation.js create mode 100644 resources/icon-sourcecontrol.svg create mode 100644 resources/icon.png create mode 100644 resources/phase10.test.js create mode 100644 resources/seti.css create mode 100644 resources/seti.woff create mode 100644 resources/setiIcons.js create mode 100644 scripts/verify-vsix.mjs delete mode 100644 src/git/GitExecutor.test.ts create mode 100644 src/test/extension.integration.test.ts create mode 100644 src/util/commandPreview.ts delete mode 100644 src/util/html.test.ts delete mode 100644 src/util/html.ts create mode 100644 src/views/ScmProvider.ts create mode 100644 src/views/treeAccessibility.ts create mode 100644 src/webviews/documentation/DocumentationProvider.ts create mode 100644 src/webviews/documentation/documentationContent.test.ts create mode 100644 src/webviews/documentation/documentationContent.ts create mode 100644 tsconfig.integration.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bcb3da..22b7fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog -## [Unreleased] +## [v0.0.5] - 2026-06-01 + +### Added +- A **Documentation** webview at the bottom of the VsGit sidebar, plus a + full-screen **VsGit: Open Documentation** panel. +- A searchable component guide and Git glossary with definitions, purpose, and + practical usage guidance. +- A manifest-driven catalog covering all contributed operations and + distinguishing Command Palette entries from context-only actions. +- Extension Host integration tests for activation, complete command + registration, repository refresh, and the full Documentation panel. +- Native Node coverage gates (80% lines, 80% branches, 70% functions), published + in CI alongside the verified VSIX artifact. +- Contributor guidance and a repeatable Marketplace release checklist. ### Fixed - **History graph rendering**: commit lanes now draw as continuous lines. The @@ -14,6 +27,25 @@ ### Changed - New, distinctive activity-bar icon (a commit-graph DAG) so the container no longer reuses the built-in Source Control glyph. +- **Commit view**: the advanced commit options (Amend / Sign off / GPG) are now + hidden by default behind an "Advanced" disclosure, keeping the panel focused on + the message and changes. The disclosure state is remembered per webview. A + small indicator dot stays on the collapsed toggle whenever amend/sign-off/GPG + is checked, so an active option is never silently hidden. Checking Amend now + prefills the previous commit's message (via the existing + `Repository.headCommitMessage()`) when the message box is empty, restoring the + prior draft if Amend is unchecked again untouched. The view's pure helpers + (status labels, file-tree grouping, escaping) were extracted into a + unit-tested module (`resources/commitView.js`). +- All tree providers now expose complete screen-reader labels. Custom webview + rows, folders, menus, pickers, and rebase controls support keyboard operation, + visible focus, live announcements, and forced-colour mode. +- Repository discovery now runs workspace probes concurrently, coalesces + overlapping scans, records scan duration, and loads submodule metadata only + when requested. +- CI and tag publishing now enforce type checks, unit tests, coverage, + Extension Host integration, dependency audit, and inspection of the exact + packaged VSIX. ### Security - The askpass and editor IPC servers now require a per-session token (passed to @@ -25,15 +57,22 @@ values beginning with `-` (option injection) and the `ext::`/`fd::` remote transports, applied across the webview-reachable git operations and the diff content provider. +- Declared untrusted and virtual workspaces unsupported because VsGit requires a + local checkout and executes repository Git configuration and hooks. +- Updated development tooling and pinned audited transitive test dependencies; + `npm audit --audit-level=high` reports no vulnerabilities. ### Internal - Extracted `parseWorktreeList` and the graph-log line parser into dedicated, unit-tested parser modules. Test suite grows from 36 to 60 cases. +- Added accessibility, Phase 10 contract, packaging, and integration coverage; + the Node suite now contains 184 passing tests. ### Docs - Rewrote the README with a detailed feature reference, settings table, architecture and security sections, and illustrated `docs/` diagrams of the Git Graph panel and the activity-bar trees. +- Added `CONTRIBUTING.md` and `docs/MARKETPLACE_CHECKLIST.md`. ## [0.1.0] - 2026-06-01 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e08f106 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to VsGit + +VsGit is a VS Code extension that delegates Git behavior to the installed +`git` executable. Changes must preserve that boundary: commands are passed as +argument arrays, repository output is parsed from machine-readable formats, and +webviews send intent rather than running Git directly. + +## Prerequisites + +- Node.js 22 +- npm +- VS Code 1.85 or newer +- Git 2.20 or newer +- `unzip` for final VSIX inspection + +Git LFS is optional unless the change exercises LFS operations. + +## Set up the repository + +```bash +npm ci +npm run check-types +npm test +npm run build +``` + +For live extension development, run `npm run watch`, then launch the Extension +Development Host with `F5`. + +## Repository structure + +- `src/git/` — Git execution, repository state, guards, and parsers. +- `src/commands/` — command registration grouped by workflow. +- `src/views/` — native VS Code tree and Source Control providers. +- `src/webviews/` — webview hosts and inline HTML generators. +- `resources/` — webview clients, styles, fonts, pure helpers, and static tests. +- `src/test/` — tests that run inside a real VS Code Extension Host. + +All Git child processes must go through `GitExecutor`. Do not construct shell +command strings. Validate refs and remote URLs received from webviews or other +untrusted surfaces with the existing argument guards. + +## Required verification + +Run the checks that match the change, then run the complete local gate: + +```bash +npm run verify +``` + +The gate includes type-checking, unit/contract tests, native Node coverage +thresholds, and a production build. Current minimums are: + +- 80% lines +- 80% branches +- 70% functions + +Changes to activation, commands, contributions, or VS Code API integration must +also pass: + +```bash +npm run test:integration +``` + +This launches a clean Extension Development Host and verifies activation, +command registration, repository refresh, and the documentation panel. + +Before release-related changes: + +```bash +npm run package:verify +``` + +That command creates `artifacts/vsgit-vscode.vsix` and checks its identity, +version, required runtime files, size, and absence of source/test artifacts. + +## Testing expectations + +- Parser changes need focused input/output unit tests. +- Repository methods need argv and option-injection coverage. +- Manifest, menu, documentation, and resource contracts belong in + `resources/*.test.js`. +- VS Code lifecycle and command-registration behavior belongs in + `src/test/*.integration.test.ts`. +- A bug fix should include a test that fails without the fix. + +Do not lower coverage thresholds to land a change. Add meaningful coverage or +document why code cannot be exercised and request review. + +## Accessibility requirements + +Every UI change must remain usable with keyboard-only navigation and across VS +Code light, dark, and high-contrast themes. + +- Use native buttons, inputs, tables, and labels where possible. +- Custom interactive elements require a role, accessible name, focusability, + Enter/Space behavior, and visible focus. +- Announce asynchronous state changes through a polite or assertive live region. +- Tree items must provide complete `accessibilityInformation` labels when visual + icons, abbreviations, or secondary descriptions carry meaning. +- Preserve `prefers-reduced-motion` and forced-colour behavior. + +## Pull requests + +Keep changes focused and preserve unrelated worktree edits. A pull request +should explain: + +1. The user-visible problem and chosen behavior. +2. Security or destructive-operation implications. +3. Tests added or updated. +4. Commands used for verification. +5. Screenshots or a short recording for material UI changes. + +Update `README.md`, `CHANGELOG.md`, and +`docs/IMPLEMENTATION_PLAN.md` whenever implementation status or public behavior +changes. diff --git a/README.md b/README.md index 0c66113..3869918 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,52 @@ -# Git (VsGit) for VS Code +# VsGit — a full Git client for VS Code -A full-featured, Git client for VS Code. VsGit spawns the `git` -binary directly — no libgit2, no reimplementation of git in JavaScript — and -surfaces **170+ commands** across every Git workflow through dedicated views, -webviews, and an interactive commit graph. +VsGit is a complete, power-user Git client built into VS Code. It drives the real +`git` binary directly — no libgit2, no JavaScript reimplementation of git — and +surfaces **173 commands** across every Git workflow through dedicated tree +views, rich webviews, an interactive commit graph, and native Source Control +integration. + +Because every operation is a genuine `git` invocation with an argv array, VsGit +behaves *exactly* like your shell: the same config, hooks, credential helpers, +aliases, and `.gitignore` rules apply. Nothing is approximated. ![VsGit Git Graph](docs/git-graph.png) > The images in this README are static **illustrations** of the interface (the > graph is rendered exactly this way from live repository data); they are not -> live photographs of a running editor. +> live screenshots of a running editor. --- ## Table of contents - [Why VsGit](#why-vsgit) -- [Highlights](#highlights) +- [Feature highlights](#feature-highlights) +- [What's included and what's next](#whats-included-and-whats-next) - [Screenshots](#screenshots) -- [Views & features](#views--features) +- [Views & panels](#views--panels) + - [Activity-bar container](#activity-bar-container) + - [Repositories view](#repositories-view) + - [Commit webview](#commit-webview) + - [Git Graph](#git-graph) + - [History view](#history-view) + - [Compare view](#compare-view) + - [Synchronize view](#synchronize-view) + - [Conflict resolution](#conflict-resolution) + - [Reflog view](#reflog-view) + - [Worktrees view](#worktrees-view) + - [Documentation library](#documentation-library) +- [Native Source Control integration](#native-source-control-integration) - [Advanced operations](#advanced-operations) +- [File & language icons](#file--language-icons) - [Keyboard shortcuts](#keyboard-shortcuts) - [Getting started](#getting-started) -- [Settings](#settings) +- [Settings reference](#settings-reference) - [Architecture](#architecture) - [Security model](#security-model) -- [Development](#development) +- [Development & testing](#development--testing) - [Requirements](#requirements) +- [FAQ & troubleshooting](#faq--troubleshooting) - [License](#license) --- @@ -34,110 +54,246 @@ webviews, and an interactive commit graph. ## Why VsGit VS Code ships with a capable Source Control panel, but power users coming from -Eclipse's EGit, `gitk`, `git-cola`, or standalone graph tools often want more: -a real commit graph, worktrees, interactive rebase, LFS, notes, bisect, subtree, -Gerrit, and per-commit operations without dropping to a terminal. +Eclipse's EGit, `gitk`, `git-cola`, GitKraken, or standalone graph tools often +want more than commit/push/pull: a real commit graph, worktrees, interactive +rebase, LFS, notes, bisect, subtree, Gerrit, submodules, and per-commit +operations — all without dropping to a terminal. + +VsGit fills that gap by driving the real `git` CLI: -VsGit fills that gap by driving the real `git` CLI. Because every operation is a -genuine `git` invocation, behaviour matches your shell exactly — the same -config, hooks, credential helpers, and aliases apply. +- **Authentic behaviour.** Every operation is a real `git` command, so results + match your shell precisely — including hooks, credential helpers, and aliases. +- **Breadth.** 173 commands spanning the everyday flow and the long tail + (rebase, LFS, notes, bisect, subtree, archive, patch, Gerrit, maintenance). +- **Multi-root aware.** Every view understands multi-folder workspaces and tracks + a single "active repository" so the panels stay coherent. +- **Safe by construction.** Git is never spawned through a shell; refs and remote + URLs from untrusted surfaces are guarded against option injection; the + credential/editor IPC channels are authenticated with a per-session token. -## Highlights +## Feature highlights - 🌳 **Interactive commit graph** — an SVG-rendered DAG with colour-coded branch lanes, inline ref pills, an expand-in-place commit-details row, flow tracing, - fuzzy find, and a full right-click action menu. -- 📜 **History view** — paginated log with the same robust graph renderer, - branch/author/message/date filtering, and a Compare-Branches mode. -- 🗂️ **Rich sidebar** — Repositories, Staging, Synchronize, Conflicts, Reflog, - Worktrees, and Compare trees, all multi-root aware. -- ✍️ **Commit webview** — stage by hunk, GPG-sign, DCO sign-off, amend. + fuzzy find, toggleable metadata columns, and a full right-click action menu. +- 📜 **History view** — a paginated log rendered with the *same* verified graph + layout, with branch/author/message/date filtering and a Compare-Branches mode. +- ✍️ **Commit webview** — a Source-Control-style panel with a split **Commit / + Commit & Push / Commit & Sync** button, amend / sign-off / GPG options behind a + "more" menu, collapsible Staged/Changes groups, hunk-level staging, and a + tree-or-list file view. +- 🗂️ **Rich sidebar** — Repositories, Commit, Staging, Synchronize, Conflicts, + Reflog, Worktrees, and Compare, all multi-root aware. +- 🔁 **Native Source Control integration** — VsGit publishes real SCM resource + groups (staged / working tree / merge) so VS Code's built-in Source Control + panel gets VsGit's menus, quick-diff gutters, and commit input. +- 🎨 **Real VS Code icons** — the UI uses official VS Code **codicons** + throughout, and file rows show the same **Seti file-type icons** you see in the + Explorer (no hand-drawn SVGs). - 🔧 **Everything else** — interactive rebase, LFS, notes, bisect, subtree, - archive, patch, Gerrit, submodules, and a built-in git-config editor. -- 🔒 **Safe by construction** — git is always spawned with an argv array (never a - shell), refs are guarded against option injection, and the credential/editor - IPC channels are authenticated with a per-session token. + archive, patch, Gerrit, submodules, maintenance, blame, tags, and a graphical + git-config editor. +- 📚 **Built-in documentation library** — a searchable bottom sidebar view and + full editor screen explaining every VsGit component, key Git terminology, and + all contributed operations with their purpose and entry point. +- 🔒 **Hardened** — argv-only spawning, option-injection guards, and authenticated + IPC for credential prompts and rebase/commit editing. + +## What's included and what's next + +### Added and available now + +- A **Documentation** view at the bottom of the VsGit sidebar. +- A full-screen **VsGit: Open Documentation** command, also available from the + Documentation view title and its **Open Full Library** button. +- Detailed component guides covering Repositories, Commit, Staging, Graph, + History, Compare, Synchronize, Conflicts, Reflog, Worktrees, native Source + Control integration, configuration, blame, and background services. +- A searchable Git glossary with definitions, purpose, practical usage, and + cautions for destructive or history-rewriting concepts. +- A manifest-driven catalog of all **173 contributed operations**. It clearly + separates the **72 Command Palette operations** from **101 contextual actions** + that require a selected file, ref, commit, resource group, or view item. +- The existing Git client surface: multi-root repositories, staging and commit, + branch/tag/remote workflows, history and graph, synchronization, conflict + resolution, worktrees, LFS, Gerrit, notes, patch/archive, submodules, + subtrees, bisect, maintenance, config editing, and authenticated Git IPC. +- Phase 10 integration and polish: keyboard and screen-reader semantics across + tree/webview surfaces, forced-colour support, concurrent/coalesced repository + discovery, lazy submodule loading, Extension Host tests, enforceable coverage, + audited development dependencies, CI release gates, and inspected VSIX output. + +### Pending future enhancements + +- Multi-platform Extension Host runs beyond the current macOS development and + Ubuntu CI coverage. +- Larger synthetic-repository benchmarks and additional on-demand loading for + rarely used metadata. +- Localization of the documentation library and command descriptions. +- Current live workflow recordings for the Marketplace listing. +- Signed release provenance and automated post-publish Marketplace smoke tests. + +The detailed engineering status remains in +[docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md). Pending items above +are plans, not claims of implemented behavior. ## Screenshots -### Git Graph +### Git Graph panel ![Git Graph panel](docs/git-graph.png) Colour-coded lanes, `HEAD → main` / remote / tag ref pills, a selected-row commit-details panel (metadata on the left, changed files on the right), and -Eclipse Git-style columns: **Graph · Description · Author · Authored Date · Committer · -Committed Date · Commit**. Metadata columns are toggleable from the graph toolbar; -Authored Date and Committer are hidden by default. +Eclipse-Git-style columns: **Graph · Description · Author · Authored Date · +Committer · Committed Date · Commit**. Metadata columns are toggleable from the +graph toolbar; Authored Date and Committer are hidden by default. ### Activity bar & trees ![Sidebar views](docs/sidebar.png) -The VsGit container uses a git-merge style logo and hosts the Repositories -and Staging trees, among others. +The VsGit activity-bar container hosts Repositories, Commit, advanced Git views, +and the Documentation library. Repository-aware views follow the active +repository selection. --- -## Views & features +## Views & panels + +### Activity-bar container + +A dedicated **VsGit** container in the activity bar groups all of the extension's +views. The core views (Repositories, Commit, Git Repositories) are always +visible; Documentation is always available at the bottom. The advanced views +(Staging, Reflog, Synchronize, Worktrees, Conflicts, Compare) appear when +`vsgit.showAdvancedViews` is enabled. ### Repositories view + - Multi-root workspace support with per-repo ahead/behind indicators. -- Full tree of branches, remotes, tags, stashes, submodules, and worktrees. -- Inline checkout, push, pull, fetch, merge, and rebase from tree nodes. +- A full tree of branches, remotes, tags, stashes, submodules, and worktrees. +- Inline checkout, push, pull, fetch, merge, and rebase directly from tree nodes. - **Reset HEAD** submenu with all five git modes — soft, mixed, hard, keep, and - merge — each picking the target ref and confirming before a hard reset. -- **Switch To** quick picker (`⌘⇧G B` / `Ctrl+Shift+G B`) across all branches and tags. + merge — each picking the target ref and confirming before a destructive reset. +- **Switch To** quick picker (`⌘⇧G B` / `Ctrl+Shift+G B`) across all branches + and tags. - Sequencer controls (Continue / Skip / Abort) appear automatically during an in-progress rebase, merge, cherry-pick, or revert. +- Selecting a repository here makes it the **active repository**; the Commit, + Staging, Synchronize, Reflog, History, and Graph surfaces all follow it. + +### Commit webview + +A Source-Control-style commit panel that replaces the transient input box: + +- **Branch header** showing the current branch with a branch glyph. +- **Split Commit button** with a dropdown of commit actions, mirroring VS Code's + Source Control panel: + - **Commit** — commit staged changes (the primary action persists your last + choice). + - **Commit & Push** — commit, then push to the upstream. + - **Commit & Sync** — commit, then pull and push. + - **Commit (Amend)** — amend the previous commit (message prefilled). + - **Commit (Signed Off)** — add a `Signed-off-by` trailer (DCO). +- A **"more" (`…`) menu** revealing **Amend**, **Sign off**, and **GPG** toggles; + an indicator dot stays on the toggle whenever one is active, so an enabled + option is never silently hidden. +- **Collapsible groups** — *Staged Changes*, *Changes*, and *Conflicts* sections + each collapse/expand (state persisted), with stage-all / unstage-all actions. +- **Tree or flat list** file view (toggle persisted across sessions), with the + real Explorer file icon, a per-file status code, and inline + stage / unstage / discard actions. +- **Hunk-level staging** — stage and unstage individual hunks (forward/reverse + patch apply against the index). +- `Ctrl/Cmd+Enter` commits, matching the native SCM input. + +### Git Graph -### Staging view & Commit webview -- Staged / Changes / Conflicts groups. -- Changed files render as a collapsible **tree or flat list** (toggle persisted - across sessions), with a descriptive status label per file. -- Hunk-level stage and unstage (forward/reverse patch apply to the index). -- **Reset Changes** on any unstaged file to revert it. -- Commit with **GPG sign** (`-S`) and **DCO sign-off** options, or **amend** the - last commit (message prefilled). +- **SVG-rendered commit graph**: one overlay path system spanning every row, so + branch edges never break apart between rows. +- **Inline ref labels**, an **expand-at-selection** commit-details row, and + `Ctrl/Cmd-click` to **compare any two commits**. +- **Flow tracing** — highlight the ancestors and/or descendants of the selected + commit; the Trace toolbar button cycles **off → ancestors → both** and shows + its current mode as a label. +- **Find** (`Ctrl/Cmd+F`) across commit message, author, hash, and ref names. +- **Toggleable metadata columns** (Id / Author / Authored Date / Committer / + Committed Date) from the toolbar. +- **Toolbar**: a Repo and Branches picker, a *Show Remote Branches* toggle, the + Trace control, per-repo Pull / Push / Fetch (with ahead/behind badges), + Commit / Branch / Merge / Stash, Find, Columns, Tracking, and Refresh — plus an + in-progress operation banner. +- **Right-click** any commit or ref pill for the full action menu (checkout, + branch, tag, merge, rebase, cherry-pick, revert, drop, reset, compare, copy + SHA; branch/tag/stash management on ref pills). ### History view -- Paginated commit log (page size configurable) rendered with the shared, + +- A paginated commit log (page size configurable) rendered with the shared, unit-tested graph layout — branch lanes stay connected across rows. -- Filter by branch, author, or message; restrict by date range. +- Filter by **branch**, **author**, or **message**; restrict by **date range**. - Per-commit context menu: checkout (detached), create branch/tag, cherry-pick, - revert, reset (soft / mixed / hard / keep / merge), compare with HEAD or - another commit, copy SHA, and show full details. + revert, reset (soft / mixed / hard / keep / merge), compare with HEAD or another + commit, copy SHA, and show full details. - **Compare Branches** mode for a symmetric `A...B` diff. -- Commits are loaded in `--topo-order` so a child always precedes its parents, - which is what the lane layout needs to draw a correct graph. - -### Git Graph -- SVG-rendered commit graph: one overlay path system spanning every row, so - edges never break apart between rows. -- Inline ref labels, an expand-at-selection commit-details row, and - `Ctrl/Cmd-click` to compare any two commits. -- **Flow tracing**: highlight ancestors / descendants of the selected commit. -- **Find** (`Ctrl/Cmd+F`) across message, author, hash, and ref names. -- Toolbar: per-repo Pull / Push / Fetch / Commit / Branch / Merge / Stash with - ahead/behind badges, plus an in-progress operation banner. -- Right-click any commit or ref pill for the full action menu (checkout, branch, - tag, merge, rebase, cherry-pick, revert, drop, reset, compare, copy SHA; - branch/tag/stash management on ref pills). +- Commits load in `--topo-order` so a child always precedes its parents — required + for the lane layout to draw a correct graph. ### Compare view -- Side-by-side branch/ref comparison tree listing commits unique to each side + +- Side-by-side branch/ref comparison tree listing the commits unique to each side and all changed files; click a file to open a diff. ### Synchronize view + - Incoming (behind) and outgoing (ahead) commits vs the configured upstream; right-click to cherry-pick or checkout any incoming commit. ### Conflict resolution -- Conflicts view lists every conflicted file with **Use Ours / Use Theirs / - Open Merge Editor / Mark Resolved**, backed by VS Code's built-in 3-way merge. + +- The Conflicts view lists every conflicted file with **Use Ours / Use Theirs / + Open Merge Editor / Mark Resolved**, backed by VS Code's built-in 3-way merge + editor. ### Reflog view -- Browse `git reflog` and checkout or reset to any entry. + +- Browse `git reflog` and checkout or reset to any entry — your safety net for + recovering lost commits. + +### Worktrees view + +- List, create, open, lock, unlock, remove, and prune linked worktrees. + +### Documentation library + +The **Documentation** view stays at the bottom of the VsGit sidebar and works +without an active repository. It provides four searchable sections: + +- **Overview** — a safe daily workflow plus what was added and what remains + planned. +- **Components** — what every VsGit view or service is, its purpose, and how to + use it. +- **Git glossary** — key Git terminology with definitions, practical purpose, + usage guidance, and recovery/destructive-operation cautions. +- **Operations** — all commands from the live extension manifest, grouped by + workflow. Palette commands can be launched directly; context-only actions + identify where their required selection is available. + +Choose **Open Full Library** inside the sidebar, use the view-title book action, +or run **VsGit: Open Documentation** for the wider editor screen. Press `/` to +focus search and `Escape` to clear it. + +--- + +## Native Source Control integration + +VsGit doesn't just live in its own container — it also publishes real +`vscode.SourceControl` resource groups for **staged**, **working-tree**, and +**merge** changes. That means the built-in Source Control panel shows VsGit's +inline menus, supports quick-diff gutters, and routes commit-message input +through VsGit. A `vsgit:` content provider feeds VS Code's diff editor with the +correct blobs for any ref or index state. --- @@ -151,18 +307,40 @@ and Staging trees, among others. | **Git Notes** | Add, edit, remove, show per-commit notes | | **Bisect** | Start, mark good/bad, reset, show log | | **Subtree** | Add, pull, push, split | +| **Submodules** | Add, update, sync, deinit | | **Archive** | Create a zip/tar from any ref | | **Patch** | Create from staged changes or commits, and apply patch files | | **Gerrit** | Push for review, install the `Change-Id` commit-msg hook | -| **Submodules** | Add, update, sync, deinit | | **Maintenance** | `git gc`, prune, fsck, and repo maintenance helpers | | **Blame** | Toggleable inline blame annotations (`⌘⇧G A`) | | **Tags** | Webview **Create Tag** dialog — name, message, and annotate / sign / force / push options in one form | | **GitHub** | Fetch Pull Requests — pulls `refs/pull/*/head` as local refs | +| **Stash** | Save, apply, pop, drop, and inspect stashes | +| **Remotes** | Add, remove, rename, edit URL, prune | +| **Git Config editor** | A graphical editor (`⌘⇧G ,`) for local / global / system git config | Interactive rebase and commit-message editing are routed back into VS Code via a -small editor shim wired to `GIT_SEQUENCE_EDITOR` / `GIT_EDITOR`, so `git -rebase -i` opens a native editor instead of a terminal vi session. +small editor shim wired to `GIT_SEQUENCE_EDITOR` / `GIT_EDITOR`, so `git rebase +-i` opens a native editor instead of a terminal `vi` session. + +--- + +## File & language icons + +VsGit uses **official VS Code codicons** for every UI glyph (toolbar buttons, ref +pills, tree chevrons, actions) — there are no hand-drawn or third-party SVG icons +in the interface. + +For changed-file rows in the Commit and Graph panels, VsGit shows the same +**Seti file-type icons** that VS Code's default File Icon Theme renders in the +Explorer (colourful, language-specific icons for JS/TS/JSON/Python/etc.). + +> **Why bundle the icons?** Webviews can't read the user's active File Icon Theme +> ([microsoft/vscode#183893](https://github.com/microsoft/vscode/issues/183893)). +> VsGit therefore bundles the Seti icon font and a filename→icon mapping +> generated from VS Code's own `theme-seti` source — the same approach GitLens +> uses. The icons match stock VS Code; they won't follow a *custom* third-party +> icon theme. --- @@ -180,8 +358,10 @@ rebase -i` opens a native editor instead of a terminal vi session. | `⌘⇧G K` | `Ctrl+Shift+G K` | Cherry-Pick Commit | | `⌘⇧G ,` | `Ctrl+Shift+G ,` | Open Git Config Panel | -All 170+ commands are also available from the Command Palette under the -**Git (VsGit)** category. +The Command Palette exposes **72** commands under the **VsGit** category. The +other **101** contributed operations are intentionally context-only because they +require a selected file, ref, commit, resource group, or view item. The +Documentation library covers all **173** and identifies each entry point. --- @@ -201,30 +381,48 @@ code --install-extension vsgit.vsix Or run it live in the **Extension Development Host**: open the folder in VS Code, run `npm run watch`, then press `F5`. -Once installed, click the VsGit icon in the activity bar, or run **Git (VsGit): -Show Git Graph** from the Command Palette. +Once installed, click the **VsGit** icon in the activity bar, or run +**VsGit: Show Git Graph** from the Command Palette. Open **Documentation** at the +bottom of the VsGit sidebar for the built-in reference. --- -## Settings +## Settings reference + +All 29 settings live under the `vsgit.*` namespace. -All settings live under the `vsgit.*` namespace. +### General | Setting | Default | Description | |---|---|---| -| `vsgit.showAdvancedViews` | `false` | Show advanced sidebar sections such as Staging, Reflog, Synchronize, Worktrees, Conflicts, and Compare. | +| `vsgit.showAdvancedViews` | `false` | Show advanced sidebar sections (Staging, Reflog, Synchronize, Worktrees, Conflicts, Compare). | | `vsgit.git.path` | `""` | Custom path to the `git` executable; empty uses `$PATH`. | | `vsgit.autoRefresh` | `true` | Refresh views automatically when the repo changes. | +| `vsgit.confirmDestructiveActions` | `true` | Confirm hard reset, clean, force-push, etc. | +| `vsgit.showCommandPreview` | `false` | Preview mutating git commands before execution; read-only refresh/diff commands run without prompting. | +| `vsgit.defaultPullMode` | `merge` | Pull strategy: `merge` or `rebase`. | + +### Fetch & sync + +| Setting | Default | Description | +|---|---|---| | `vsgit.autoFetch.enabled` | `false` | Periodically fetch from all remotes. | | `vsgit.autoFetch.intervalMinutes` | `3` | Minutes between automatic fetches. | | `vsgit.autoFetch.notify` | `true` | Notify when auto-fetch discovers new incoming commits. | | `vsgit.fetch.pruneOnFetch` | `true` | Prune deleted remote-tracking branches on fetch. | -| `vsgit.defaultPullMode` | `merge` | Pull strategy: `merge` or `rebase`. | -| `vsgit.confirmDestructiveActions` | `true` | Confirm hard reset, clean, force-push, etc. | -| `vsgit.showCommandPreview` | `false` | Preview the exact git command before running it. | + +### Commit & blame + +| Setting | Default | Description | +|---|---|---| | `vsgit.commit.gpgSign` | `false` | Sign commits with GPG by default (`-S`). | | `vsgit.commit.signOff` | `false` | Add a `Signed-off-by` trailer by default (DCO). | | `vsgit.blame.enabledByDefault` | `false` | Show inline blame when opening files. | + +### History & graph + +| Setting | Default | Description | +|---|---|---| | `vsgit.history.maxCommits` | `500` | Max commits to load in the History view. | | `vsgit.graph.pageSize` | `200` | Commits loaded per page in the History view. | | `vsgit.graph.maxCommits` | `500` | Max commits to load in the Git Graph. | @@ -261,14 +459,22 @@ src/ parsers/ pure, unit-tested output parsers (log, graphLog, status, refs, diff, blame, config, reflog, rebaseTodo, worktree) - views/ tree data providers (Repositories, Staging, …) - webviews/ webview panels (Graph, History, Commit, Create Tag, - pickers) + commands/ one module per workflow (branch, stash, tag, lfs, + notes, bisect, subtree, rebase, gerrit, …) + views/ tree data providers + native Source Control bridge + webviews/ webview panels (Graph, History, Commit, Documentation, + Create Tag, pickers) services/ auto-fetch, file-system watcher, status bar util/ IPC servers (askpass / editor) + helpers (html escape) resources/ - graphLayout.js shared, unit-tested commit-graph layout (UMD) graph.js / graph.css Git Graph panel client + documentation.js/.css searchable documentation library client + graphLayout.js shared, unit-tested commit-graph layout (UMD) + commit.js / commit.css Commit webview client + commitView.js Commit view pure helpers (UMD, unit-tested) + setiIcons.js shared filename→Seti-icon resolver (UMD) + seti.css / seti.woff bundled Seti file-icon font (Explorer icons) + codicon.css / codicon.ttf bundled VS Code codicon font (UI glyphs) askpass.js GIT_ASKPASS shim sequence-editor.js GIT_SEQUENCE_EDITOR / GIT_EDITOR shim icon.svg activity-bar logo @@ -281,10 +487,16 @@ Key design points: - **Machine-readable output.** Operations request NUL-/porcelain-formatted output and parse it in small, pure functions under `git/parsers/`, each with tests. - **One graph layout.** Both the Git Graph panel and the History view import the - same `resources/graphLayout.js` (a UMD module that also loads in Node), so - there is a single, verified implementation of lane layout and edge geometry. + same `resources/graphLayout.js` (a UMD module that also loads in Node), so there + is a single, verified implementation of lane layout and edge geometry. +- **Shared webview helpers.** Pure logic (status labels, file-tree grouping, + HTML-escaping, Seti icon resolution) lives in UMD modules so it can be unit + tested in Node and reused across the Commit and Graph webviews. - **Live refresh.** A file-system watcher plus `RepositoryManager.onDidChange` keep every view in sync after internal or external git changes. +- **Measured, coalesced discovery.** Workspace roots are discovered concurrently; + overlapping scans share one in-flight operation, scan duration is recorded, + and submodule enumeration is deferred until its tree is opened. ## Security model @@ -297,12 +509,18 @@ Key design points: arbitrary commands. - **Authenticated IPC.** Credential prompts (`GIT_ASKPASS`) and rebase/commit editing run over a unix socket / named pipe whose name is enumerable by other - local processes. Each session generates a random token, passed to the shim - only via its environment; the server rejects any connection that doesn't echo - it — preventing local credential phishing or edit injection. Credential - prompts are masked unless they explicitly ask for a username. - -## Development + local processes. Each session generates a random token, passed to the shim only + via its environment; the server rejects any connection that doesn't echo it — + preventing local credential phishing or edit injection. Credential prompts are + masked unless they explicitly ask for a username, and the IPC read buffers are + bounded. +- **Strict webview CSP.** Each webview runs under a Content-Security-Policy that + only allows the extension's own nonce'd scripts and bundled styles/fonts. +- **Workspace trust boundary.** The manifest disables VsGit in untrusted and + virtual workspaces because Git configuration and repository hooks execute from + a local checkout. + +## Development & testing ```bash npm install @@ -310,26 +528,40 @@ npm run watch # esbuild in watch mode; F5 launches the dev host npm run check-types # tsc --noEmit npm run build # production bundle into dist/ npm test # compile + run the unit-test suite +npm run test:coverage # enforce 80% lines/branches and 70% functions +npm run test:integration # launch a real VS Code Extension Host +npm run package:verify # build and inspect artifacts/vsgit-vscode.vsix ``` ### Testing -Tests run on Node's built-in test runner (`node --test`) — no VS Code instance -required. They cover the pure logic that's most worth pinning down: +Unit and contract tests run on Node's built-in test runner (`node --test`). They +cover the pure logic that's most worth pinning down: - every output parser under `src/git/parsers/` (log, graph-log, status, refs, diff, blame, config, reflog, rebase-todo, worktree), -- the shared commit-graph layout (`resources/graphLayout.test.js`) and the - Git Graph webview client (`resources/manifest.test.js`), +- the shared commit-graph layout (`resources/graphLayout.test.js`) and the Git + Graph / Commit webview clients (`resources/manifest.test.js`, + `resources/commit.test.js`, `resources/commitView.test.js`), +- the Seti filename→icon resolver, - the `GitExecutor` argv assembly and the `Repository` command builders, - the argument guards, the HTML-escape helper, and the IPC token comparison. +- accessibility contracts for every tree provider and webview surface, +- Phase 10 CI, coverage, performance, documentation, and packaging contracts. ```bash -npm test # 142 tests +npm test # 184 tests ``` -CI (GitHub Actions) runs type-check, build, the test suite, and packages the -VSIX on every push and pull request. +The Extension Host suite activates the real extension, verifies every +contributed command, refreshes repository discovery, and opens Documentation. +Native coverage currently exceeds the enforced 80% line, 80% branch, and 70% +function thresholds. + +CI runs type-checking, unit/contract tests, coverage, a Linux Extension Host, +dependency audit, production build, and VSIX inspection on every push and pull +request. See [CONTRIBUTING.md](CONTRIBUTING.md) for development standards and +[the Marketplace checklist](docs/MARKETPLACE_CHECKLIST.md) for the release gate. --- @@ -338,6 +570,27 @@ VSIX on every push and pull request. - VS Code **1.85+** - `git` **2.20+** on `$PATH` (or set `vsgit.git.path`) - `git-lfs` for the LFS commands (optional) +- A trusted local workspace (virtual and Restricted Mode workspaces are not + supported because VsGit executes the repository's Git configuration/hooks) + +## FAQ & troubleshooting + +**VsGit can't find my repository.** VsGit discovers repos from your open +workspace folders. Make sure the folder containing `.git` is open, or run +**VsGit: Refresh**. + +**Git isn't found / wrong version.** Set `vsgit.git.path` to the absolute path of +your `git` binary, then reload the window. + +**The advanced views are missing.** Enable `vsgit.showAdvancedViews` to reveal +Staging, Reflog, Synchronize, Worktrees, Conflicts, and Compare. + +**File icons look generic.** VsGit shows the default Seti icons; it cannot read a +custom File Icon Theme inside a webview (a VS Code platform limitation). The icons +will match a stock VS Code install. + +**Credential prompts.** VsGit uses your existing git credential helper via +`GIT_ASKPASS`. If a remote needs auth, you'll get a native VS Code prompt. ## License diff --git a/package-lock.json b/package-lock.json index 862f1f1..0c16d2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,248 @@ { - "name": "git-vscode", - "version": "0.1.0", + "name": "vsgit-vscode", + "version": "0.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "git-vscode", - "version": "0.1.0", + "name": "vsgit-vscode", + "version": "0.0.5", "license": "MIT", "devDependencies": { "@types/node": "^20.11.0", "@types/vscode": "^1.85.0", - "esbuild": "^0.20.0", + "@vscode/codicons": "^0.0.45", + "@vscode/test-cli": "^0.0.11", + "@vscode/test-electron": "^3.0.0", + "@vscode/vsce": "^3.9.2", + "esbuild": "^0.28.1", "typescript": "^5.4.0" }, "engines": { "vscode": "^1.85.0" } }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", + "dev": true, + "license": "MIT", + "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": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz", + "integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==", + "dev": true, + "license": "MIT", + "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": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@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.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.15.0.tgz", + "integrity": "sha512-2NYT6v+eeQn8kmNddr9LnbXSvXbVELpmFMmfFvtRxD7I/5+5GlkMlncApeuRFj+mY6C9syOwQip1a0Y+TIbyiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.10.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.10.0.tgz", + "integrity": "sha512-iYtjpanlv6963Jprs0MvzIap07V+QhultjQctfbEDQCflsDAEeO3R7XnVA5gk30fhoBFLdgJT7VqO0TGsEsN9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.3.0.tgz", + "integrity": "sha512-fXtJX811pX8y8QlrQqBSH6+plvWyKZDI0IxkheAcyAw9OtcpXyFivmTC7eGUqutLWaDlKXuQ3yOESD4zAmkjHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.10.0", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -32,13 +253,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -49,13 +270,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -66,13 +287,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -83,13 +304,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -100,13 +321,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -117,13 +338,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -134,13 +355,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -151,13 +372,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -168,13 +389,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -185,13 +406,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -202,13 +423,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -219,13 +440,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -236,13 +457,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -253,13 +474,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -270,13 +491,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -287,13 +508,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -304,13 +525,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -321,13 +559,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -338,13 +593,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -355,13 +627,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -372,13 +644,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -389,13 +661,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -405,86 +677,5446 @@ "os": [ "win32" ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { "node": ">=12" } }, - "node_modules/@types/node": { - "version": "20.19.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", - "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/vscode": { - "version": "1.120.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz", - "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=14.17" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.7.1" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.120.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz", + "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.6.tgz", + "integrity": "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@vscode/codicons": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45.tgz", + "integrity": "sha512-1KAZ7XCMagp5Gdrlr4bbbcAqgcIL623iO1wW6rfcSVGAVUQvR0WP7bQx1SbJ11gmV3fdQTSEFIJQ/5C+HuVasw==", + "dev": true, + "license": "CC-BY-4.0" + }, + "node_modules/@vscode/test-cli": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", + "integrity": "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.2", + "c8": "^9.1.0", + "chokidar": "^3.5.3", + "enhanced-resolve": "^5.15.0", + "glob": "^10.3.10", + "minimatch": "^9.0.3", + "mocha": "^11.1.0", + "supports-color": "^9.4.0", + "yargs": "^17.7.2" + }, + "bin": { + "vscode-test": "out/bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/test-electron": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.0.0.tgz", + "integrity": "sha512-TY5mC7aAjxSLDXsyjhrG8cJHgc/HLdiE5lvtW7hABYQrY24Qwozzr5UoO3HiuAM4Hzz4b7K/eZlwrCILj94CcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "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, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "license": "Python-2.0" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/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, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "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" + } + ], + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "BSD-3-Clause" + }, + "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, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "BSD-2-Clause", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "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, + "license": "ISC", + "optional": true + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "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, + "license": "MIT", + "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" + } + ], + "license": "BSD-2-Clause" + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", + "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/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, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "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" + } + ], + "license": "MIT", + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "ISC", + "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, + "license": "ISC" + }, + "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, + "license": "ISC", + "optional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "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-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "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, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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, + "license": "MIT", + "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/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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, + "license": "MIT", + "optional": true + }, + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "optional": true + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "BSD-2-Clause", + "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, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.5.tgz", + "integrity": "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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, + "license": "MIT", + "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/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "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/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/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, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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" + } + ], + "license": "MIT", + "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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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/tar-stream/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, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/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, + "license": "ISC", + "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/test-exclude/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "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, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "ISC" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 802494c..af1f572 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,10 @@ "name": "vsgit-vscode", "displayName": "VsGit", "description": "VsGit-style Git client for VS Code: Repositories, Staging, History, Graph, Compare, Worktrees, LFS, Gerrit, and more.", - "version": "0.0.2", + "version": "0.0.5", "publisher": "framedparadox", "license": "MIT", + "icon": "resources/icon.png", "repository": { "type": "git", "url": "https://github.com/framedparadox/vsgit-vscode.git" @@ -39,6 +40,16 @@ "activationEvents": [ "onStartupFinished" ], + "capabilities": { + "untrustedWorkspaces": { + "supported": false, + "description": "VsGit executes the repository's Git configuration and hooks. Trust the workspace before enabling Git operations." + }, + "virtualWorkspaces": { + "supported": false, + "description": "VsGit requires a local filesystem checkout and an installed Git executable." + } + }, "main": "./dist/extension.js", "contributes": { "viewsContainers": { @@ -106,6 +117,13 @@ "name": "Compare", "icon": "$(git-compare)", "when": "vsgit.advancedViews" + }, + { + "id": "vsgit.documentation", + "name": "Documentation", + "type": "webview", + "icon": "$(book)", + "visibility": "visible" } ] }, @@ -828,6 +846,12 @@ "category": "VsGit", "icon": "$(git-branch)" }, + { + "command": "vsgit.documentation.open", + "title": "Open Documentation", + "category": "VsGit", + "icon": "$(book)" + }, { "command": "vsgit.autoFetch.fetchNow", "title": "Fetch All Remotes Now", @@ -1076,6 +1100,10 @@ } ], "submenus": [ + { + "id": "vsgit.repo", + "label": "VsGit" + }, { "id": "vsgit.team", "label": "Team" @@ -1173,6 +1201,13 @@ "group": "navigation@9" } ], + "scm/sourceControl": [ + { + "submenu": "vsgit.repo", + "when": "scmProvider == git", + "group": "9_vsgit@1" + } + ], "explorer/context": [ { "submenu": "vsgit.team.compareWith", @@ -1319,6 +1354,100 @@ "group": "2@2" } ], + "vsgit.repo": [ + { + "submenu": "vsgit.repo.pushPull", + "group": "1@1" + }, + { + "submenu": "vsgit.repo.history", + "group": "1@2" + }, + { + "command": "vsgit.merge", + "group": "1b@1" + }, + { + "command": "vsgit.rebase", + "group": "1b@2" + }, + { + "submenu": "vsgit.repo.reset", + "group": "1b@3" + }, + { + "command": "vsgit.tag.create", + "group": "1b@4" + }, + { + "submenu": "vsgit.repo.switchTo", + "group": "2@1" + }, + { + "submenu": "vsgit.repo.commit", + "group": "2@2" + }, + { + "submenu": "vsgit.repo.sequencer", + "group": "2@3" + }, + { + "submenu": "vsgit.repo.remote", + "group": "3@1" + }, + { + "submenu": "vsgit.repo.submodule", + "group": "3@2" + }, + { + "submenu": "vsgit.repo.patch", + "group": "3@3" + }, + { + "submenu": "vsgit.repo.advanced.rebase", + "group": "4@1" + }, + { + "submenu": "vsgit.repo.advanced.bisect", + "group": "4@2" + }, + { + "submenu": "vsgit.repo.advanced.notes", + "group": "4@3" + }, + { + "submenu": "vsgit.repo.advanced.subtree", + "group": "4@4" + }, + { + "submenu": "vsgit.repo.advanced.lfs", + "group": "4@5" + }, + { + "submenu": "vsgit.repo.advanced.gerrit", + "group": "4@6" + }, + { + "command": "vsgit.clean", + "group": "5@1" + }, + { + "command": "vsgit.archive.create", + "group": "5@2" + }, + { + "submenu": "vsgit.repo.maintenance", + "group": "8@1" + }, + { + "submenu": "vsgit.repo.prune", + "group": "8@2" + }, + { + "submenu": "vsgit.repo.config", + "group": "9@1" + } + ], "vsgit.repo.pushPull": [ { "command": "vsgit.fetch", @@ -1702,6 +1831,11 @@ "command": "vsgit.compare.clear", "when": "view == vsgit.compare", "group": "navigation@3" + }, + { + "command": "vsgit.documentation.open", + "when": "view == vsgit.documentation", + "group": "navigation@1" } ], "view/item/context": [ @@ -2244,12 +2378,12 @@ "scm/resourceState/context": [ { "command": "vsgit.scm.stage", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup != index", "group": "inline@1" }, { "command": "vsgit.scm.unstage", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup == index", "group": "inline@2" }, { @@ -2279,7 +2413,7 @@ }, { "command": "vsgit.scm.discard", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup != index", "group": "9_destructive@1" }, { @@ -2296,18 +2430,23 @@ "scm/resourceGroup/context": [ { "command": "vsgit.scm.stageAll", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup != index", "group": "1_modify@1" }, { "command": "vsgit.scm.unstageAll", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup == index", "group": "1_modify@2" }, { "command": "vsgit.scm.discardAll", - "when": "scmProvider == vsgit", + "when": "scmProvider == vsgit && scmResourceGroup != index", "group": "9_destructive@1" + }, + { + "submenu": "vsgit.repo", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "9_vsgit@1" } ], "editor/title": [ @@ -2994,16 +3133,16 @@ }, "default": [ "#0085d9", - "#d9008f", + "#ff7700", "#00d90a", "#d98500", - "#a300d9", - "#ff0000", + "#ffd000", + "#0a6976", + "#6f24d6", "#00d9cc", - "#e138e8", "#85d900", "#dc5b23", - "#6f24d6", + "#BB8EF8", "#ffcc00" ], "description": "Branch line colours (HEX or rgb) cycled through in the Git Graph." @@ -3082,12 +3221,27 @@ "compile": "tsc -p ./", "check-types": "tsc --noEmit", "test": "node --test \"out-test/**/*.test.js\" \"resources/**/*.test.js\"", - "pretest": "tsc -p ./tsconfig.test.json" + "pretest": "tsc -p ./tsconfig.test.json", + "test:coverage": "node --test --experimental-test-coverage --test-coverage-exclude=\"**/*.test.js\" --test-coverage-lines=80 --test-coverage-branches=80 --test-coverage-functions=70 \"out-test/**/*.test.js\" \"resources/**/*.test.js\"", + "pretest:coverage": "tsc -p ./tsconfig.test.json", + "compile:integration": "tsc -p ./tsconfig.integration.json", + "test:integration": "npm run build && npm run compile:integration && vscode-test", + "package:vsix": "npm run build && node -e \"require('fs').mkdirSync('artifacts',{recursive:true})\" && vsce package --no-dependencies --out artifacts/vsgit-vscode.vsix", + "package:verify": "npm run package:vsix && node scripts/verify-vsix.mjs artifacts/vsgit-vscode.vsix", + "verify": "npm run check-types && npm test && npm run test:coverage && npm run build" }, "devDependencies": { "@types/node": "^20.11.0", "@types/vscode": "^1.85.0", - "esbuild": "^0.20.0", + "@vscode/codicons": "^0.0.45", + "@vscode/test-cli": "^0.0.11", + "@vscode/test-electron": "^3.0.0", + "@vscode/vsce": "^3.9.2", + "esbuild": "^0.28.1", "typescript": "^5.4.0" + }, + "overrides": { + "diff": "8.0.3", + "serialize-javascript": "7.0.5" } } diff --git a/resources/accessibility.test.js b/resources/accessibility.test.js new file mode 100644 index 0000000..55d180f --- /dev/null +++ b/resources/accessibility.test.js @@ -0,0 +1,80 @@ +'use strict'; + +const { test } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); + +const root = path.resolve(__dirname, '..'); +const read = (relative) => fs.readFileSync(path.join(root, relative), 'utf8'); + +test('every TreeDataProvider supplies explicit screen-reader labels', () => { + const viewDir = path.join(root, 'src', 'views'); + const providers = fs.readdirSync(viewDir) + .filter((name) => name.endsWith('Provider.ts')) + .map((name) => [`src/views/${name}`, read(`src/views/${name}`)]) + .filter(([, source]) => source.includes('TreeDataProvider<')); + + assert.ok(providers.length >= 7, 'expected the sidebar tree providers'); + for (const [file, source] of providers) { + assert.ok( + source.includes('accessibleTreeItem('), + `${file} assigns accessibilityInformation through accessibleTreeItem`, + ); + } + assert.ok( + read('src/views/treeAccessibility.ts').includes('item.accessibilityInformation = { label }'), + 'tree accessibility helper assigns the complete label', + ); +}); + +test('every webview surface has live announcements, keyboard focus, and high-contrast support', () => { + const surfaces = [ + ['History', read('src/webviews/historyHtml.ts')], + ['Config', read('src/webviews/configHtml.ts')], + ['Interactive Rebase', read('src/webviews/rebaseTodoHtml.ts')], + ['Commit Message Editor', read('src/webviews/editTextHtml.ts')], + ['Commit Picker', read('src/webviews/commitPickerHtml.ts')], + ['Ref Picker', read('src/webviews/refPickerHtml.ts')], + ['Create Tag', read('src/webviews/CreateTagDialog.ts')], + [ + 'Git Graph', + read('src/webviews/graph/GraphPanel.ts') + read('resources/graph.css'), + ], + [ + 'Commit', + read('src/webviews/commit/CommitViewProvider.ts') + read('resources/commit.css'), + ], + [ + 'Documentation', + read('src/webviews/documentation/DocumentationProvider.ts') + + read('resources/documentation.css'), + ], + ]; + + for (const [name, source] of surfaces) { + assert.ok(source.includes('aria-live='), `${name} has a live region`); + assert.ok(source.includes(':focus-visible'), `${name} has visible keyboard focus`); + assert.ok( + source.includes('forced-colors: active'), + `${name} supports forced-colour/high-contrast mode`, + ); + } +}); + +test('custom clickable rows and disclosures expose keyboard semantics', () => { + const commit = read('resources/commit.js'); + const graph = read('resources/graph.js'); + const history = read('src/webviews/historyHtml.ts'); + const refs = read('src/webviews/refPickerHtml.ts'); + const config = read('src/webviews/configHtml.ts'); + const rebase = read('src/webviews/rebaseTodoHtml.ts'); + + assert.ok(commit.includes('makeKeyboardClickable('), 'Commit rows use a keyboard helper'); + assert.ok(graph.includes("tr.addEventListener('keydown'"), 'Graph commits handle keyboard actions'); + assert.ok(graph.includes("role=\"option\" tabindex=\"-1\""), 'Graph dropdown options are keyboard focusable'); + assert.ok(history.includes("item.setAttribute('role', 'option')"), 'History commits expose option semantics'); + assert.ok(refs.includes("leaf.setAttribute('role', 'treeitem')"), 'Ref picker leaves expose tree semantics'); + assert.ok(config.includes('' + + '' + + '' + '' + '
' + '
' + @@ -1193,22 +1301,14 @@ const CDV_STATUS_LABELS = { // File extension (lowercase, no dot) shown on the left of each row, or '•' when // the file has no extension. -function cdvFileExt(p) { - const base = String(p || '').split('/').pop() || ''; - const dot = base.lastIndexOf('.'); - if (dot <= 0 || dot === base.length - 1) return '•'; - return base.slice(dot + 1).toLowerCase(); -} - function makeFileRow(f, onOpen, label) { const fileRow = document.createElement('div'); fileRow.className = 'file-row'; const code = (f.status || 'M').charAt(0).toUpperCase(); - // LEFT: file extension chip. + // LEFT: Seti file-type icon (the icon VS Code shows in the Explorer). const ext = document.createElement('span'); - ext.className = 'file-ext ' + code; - ext.textContent = cdvFileExt(f.path); + ext.className = 'file-ext seti-icon ' + self.SetiIcons.setiIconClass(f.path); const p = document.createElement('span'); p.className = 'file-path'; @@ -1223,7 +1323,9 @@ function makeFileRow(f, onOpen, label) { fileRow.appendChild(ext); fileRow.appendChild(p); fileRow.appendChild(change); - if (onOpen) fileRow.addEventListener('click', () => onOpen(f)); + if (onOpen) { + makeKeyboardClickable(fileRow, `${f.path}, ${CDV_STATUS_LABELS[code] || 'Modified'}, open diff`, () => onOpen(f)); + } return fileRow; } @@ -1269,13 +1371,15 @@ function renderTreeLevel(node, parent, onOpen, depth) { fname.textContent = name; folderRow.appendChild(chev); folderRow.appendChild(fname); + folderRow.setAttribute('aria-expanded', 'true'); const childrenWrap = document.createElement('div'); childrenWrap.className = 'tree-children'; renderTreeLevel(dir, childrenWrap, onOpen, depth + 1); - folderRow.addEventListener('click', (e) => { + makeKeyboardClickable(folderRow, `${name}, folder`, (e) => { e.stopPropagation(); const collapsed = childrenWrap.classList.toggle('collapsed'); chev.classList.toggle('expanded', !collapsed); + folderRow.setAttribute('aria-expanded', String(!collapsed)); }); parent.appendChild(folderRow); parent.appendChild(childrenWrap); @@ -1305,11 +1409,12 @@ function buildMenu(items) { const el = document.createElement('div'); el.className = 'context-menu-item'; el.textContent = it.label; - el.addEventListener('click', (e) => { + makeKeyboardClickable(el, it.label, (e) => { e.stopPropagation(); menu.classList.remove('visible'); it.action(); }); + el.setAttribute('role', 'menuitem'); menu.appendChild(el); } }); @@ -1322,6 +1427,7 @@ function placeMenu(menu, x, y) { const r = menu.getBoundingClientRect(); if (r.right > window.innerWidth) menu.style.left = Math.max(0, window.innerWidth - r.width - 4) + 'px'; if (r.bottom > window.innerHeight) menu.style.top = Math.max(0, window.innerHeight - r.height - 4) + 'px'; + menu.querySelector('[role="menuitem"]')?.focus(); } function showCommitMenu(x, y, commit) { const sha = commit.sha; @@ -1574,6 +1680,7 @@ window.addEventListener('message', (event) => { document.getElementById('loading').style.display = 'none'; document.getElementById('empty-state').style.display = 'block'; document.getElementById('graph-body').innerHTML = ''; + announce('No Git repository is active.'); break; case 'graphData': { graphData = msg.data; @@ -1609,8 +1716,12 @@ window.addEventListener('message', (event) => { } } applyTrace(); - document.getElementById('commit-count').textContent = - graphData.commits.filter((c) => c.kind !== 'uncommitted').length + ' commits'; + const commitCount = graphData.commits.filter((c) => c.kind !== 'uncommitted').length; + document.getElementById('commit-count').textContent = commitCount + ' commits'; + if (commitCount !== lastAnnouncedCommitCount) { + lastAnnouncedCommitCount = commitCount; + announce(`${commitCount} commits loaded.`); + } break; } case 'files': diff --git a/resources/icon-sourcecontrol.svg b/resources/icon-sourcecontrol.svg new file mode 100644 index 0000000..5de3a8b --- /dev/null +++ b/resources/icon-sourcecontrol.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9cca04f0a3b55ddb3bcd1e6b3c691fe8ce17531a GIT binary patch literal 2585 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 - - + + + + + \ No newline at end of file diff --git a/resources/manifest.test.js b/resources/manifest.test.js index e54d7f9..9bb487c 100644 --- a/resources/manifest.test.js +++ b/resources/manifest.test.js @@ -105,6 +105,157 @@ test('Git Graph column settings are contributed, sent, and rendered', () => { assert.ok(graphJs.includes('tb-columns'), 'graph.js wires the Columns toolbar button'); }); +test('native SCM provider is registered and menu actions target resource groups', () => { + const pkg = JSON.parse(read('package.json')); + const extension = read('src/extension.ts'); + const scmProvider = read('src/views/ScmProvider.ts'); + const scmCommands = read('src/commands/scm.ts'); + const resourceStateMenus = pkg.contributes?.menus?.['scm/resourceState/context'] ?? []; + const resourceGroupMenus = pkg.contributes?.menus?.['scm/resourceGroup/context'] ?? []; + + assert.ok(extension.includes('new VsgitScmProvider(manager)'), 'SCM provider is activated'); + assert.ok(scmProvider.includes('vscode.scm.createSourceControl('), 'SCM provider creates source controls'); + assert.ok(scmProvider.includes('"index", "Staged Changes"'), 'SCM provider exposes staged group'); + assert.ok(scmProvider.includes('"workingTree", "Changes"'), 'SCM provider exposes working tree group'); + assert.ok(scmProvider.includes('"merge", "Merge Changes"'), 'SCM provider exposes merge group'); + assert.ok(scmProvider.includes('vsgitChange: change'), 'SCM resources carry parsed status metadata'); + assert.ok(scmProvider.includes('quickDiffProvider'), 'native SCM uses VsGit quick diff'); + assert.ok(scmProvider.includes('new VsgitQuickDiffProvider(this.manager, repo.root)'), 'SCM provider reuses quick diff provider'); + assert.ok(scmProvider.includes('acceptInputCommand'), 'native SCM input box can commit'); + assert.ok(!extension.includes('vscode.scm.createSourceControl("vsgit", "VsGit")'), 'old empty SCM provider was removed'); + assert.ok(scmCommands.includes('resourceGroup('), 'SCM commands inspect resource group metadata'); + assert.ok(scmCommands.includes('resourceChange('), 'SCM commands inspect status metadata'); + assert.ok(scmCommands.includes('discardPathSets('), 'SCM discard separates tracked and untracked paths'); + assert.ok(scmCommands.includes('repo.discard(discard.tracked, discard.untracked)'), 'SCM discard cleans untracked paths'); + assert.ok(scmCommands.includes('VSGIT_EMPTY_REF'), 'SCM diff can render deleted working-tree files'); + assert.ok(scmCommands.includes('Index ↔ Working Tree'), 'SCM working-tree diffs compare index to working tree'); + assert.ok(scmCommands.includes('manager.findByUri'), 'SCM commands resolve repos by URI containment'); + + assert.strictEqual( + resourceStateMenus.find((item) => item.command === 'vsgit.scm.stage')?.when, + 'scmProvider == vsgit && scmResourceGroup != index', + ); + assert.strictEqual( + resourceStateMenus.find((item) => item.command === 'vsgit.scm.unstage')?.when, + 'scmProvider == vsgit && scmResourceGroup == index', + ); + assert.strictEqual( + resourceStateMenus.find((item) => item.command === 'vsgit.scm.discard')?.when, + 'scmProvider == vsgit && scmResourceGroup != index', + ); + assert.strictEqual( + resourceGroupMenus.find((item) => item.command === 'vsgit.scm.stageAll')?.when, + 'scmProvider == vsgit && scmResourceGroup != index', + ); + assert.strictEqual( + resourceGroupMenus.find((item) => item.command === 'vsgit.scm.unstageAll')?.when, + 'scmProvider == vsgit && scmResourceGroup == index', + ); + assert.strictEqual( + resourceGroupMenus.find((item) => item.command === 'vsgit.scm.discardAll')?.when, + 'scmProvider == vsgit && scmResourceGroup != index', + ); +}); + +test('VsGit submenu is attached to the native Git SCM menus', () => { + const pkg = JSON.parse(read('package.json')); + const menus = pkg.contributes?.menus ?? {}; + const submenus = pkg.contributes?.submenus ?? []; + const shared = read('src/commands/shared.ts'); + + // Parent submenu exists and is labelled "VsGit". + const repoSubmenu = submenus.find((s) => s.id === 'vsgit.repo'); + assert.ok(repoSubmenu, 'vsgit.repo submenu is declared'); + assert.strictEqual(repoSubmenu.label, 'VsGit', 'native submenu is labelled VsGit'); + + // The submenu has a non-empty body of repo actions. + const repoItems = menus['vsgit.repo'] ?? []; + assert.ok(repoItems.length > 0, 'vsgit.repo submenu has entries'); + assert.ok( + repoItems.some((item) => item.submenu === 'vsgit.repo.pushPull'), + 'vsgit.repo submenu surfaces push/pull actions', + ); + + // Attached to the native repository node (right-click the repo header). + const sourceControlMenu = menus['scm/sourceControl'] ?? []; + const repoNodeEntry = sourceControlMenu.find((item) => item.submenu === 'vsgit.repo'); + assert.ok(repoNodeEntry, 'vsgit.repo is attached to scm/sourceControl'); + assert.strictEqual( + repoNodeEntry.when, + 'scmProvider == git', + 'native repo submenu targets the built-in git provider', + ); + + // Attached to the native "Changes" group ellipsis (working tree only). + const resourceGroupMenus = menus['scm/resourceGroup/context'] ?? []; + const changesEntry = resourceGroupMenus.find((item) => item.submenu === 'vsgit.repo'); + assert.ok(changesEntry, 'vsgit.repo is attached to scm/resourceGroup/context'); + assert.strictEqual( + changesEntry.when, + 'scmProvider == git && scmResourceGroup == workingTree', + 'native group submenu targets the working-tree (Changes) group', + ); + + // resolveRepo can map a native SourceControl rootUri to a repository. + assert.ok(shared.includes('nativeScmRootUri'), 'resolveRepo extracts a native rootUri'); + assert.ok( + shared.includes('manager.get(rootUri.fsPath) ?? manager.findByUri(rootUri)'), + 'resolveRepo maps the native rootUri to a repository', + ); +}); + +test('configured git path and command preview are wired through the shared executor', () => { + const executor = read('src/git/GitExecutor.ts'); + const manager = read('src/git/RepositoryManager.ts'); + const extension = read('src/extension.ts'); + const contentProvider = read('src/git/GitContentProvider.ts'); + const commandPreview = read('src/util/commandPreview.ts'); + + assert.ok(executor.includes('private gitPath: string = "git"'), 'GitExecutor stores the configured path'); + assert.ok(executor.includes('spawn(this.gitPath, args'), 'GitExecutor spawns the configured path'); + assert.ok(executor.includes('this.preview(args, options.cwd, this.gitPath)'), 'preview sees the executable path'); + assert.ok(manager.includes('new GitExecutor(configuredGitPath(), shouldRunGitCommand)'), 'manager creates shared configured executor'); + assert.ok(manager.includes('updateGitPathFromConfiguration()'), 'manager can update git path after configuration changes'); + assert.ok(extension.includes('e.affectsConfiguration("vsgit.git.path")'), 'extension listens for git path changes'); + assert.ok(extension.includes('manager.updateGitPathFromConfiguration()'), 'extension applies git path changes'); + assert.ok(extension.includes('new GitContentProvider(manager.getGitExecutor())'), 'diff content provider uses shared executor'); + assert.ok(contentProvider.includes('constructor(private readonly git: GitExecutor)'), 'content provider receives executor'); + assert.ok(contentProvider.includes('export const VSGIT_EMPTY_REF'), 'content provider has explicit empty-side ref'); + assert.ok(contentProvider.includes('vscode.Uri.from({'), 'content provider encodes virtual URI paths structurally'); + assert.ok(commandPreview.includes('get("showCommandPreview", false)'), 'preview setting is read'); + assert.ok(commandPreview.includes('[gitPath || "git", ...args]'), 'preview renders configured executable'); + assert.ok(commandPreview.includes('isReadOnlyGitCommand(args)'), 'background read-only commands are not previewed'); +}); + +test('repository routing uses active repo and containment-aware URI lookup', () => { + const manager = read('src/git/RepositoryManager.ts'); + const staging = read('src/views/StagingProvider.ts'); + const sync = read('src/views/SynchronizeProvider.ts'); + const reflog = read('src/views/ReflogProvider.ts'); + const history = read('src/webviews/HistoryView.ts'); + const graph = read('src/webviews/graph/GraphPanel.ts'); + const quickDiff = read('src/git/QuickDiffProvider.ts'); + const blame = read('src/decorations/BlameController.ts'); + const compare = read('src/commands/compare.ts'); + + assert.ok(manager.includes('getActive(): Repository | undefined'), 'manager exposes active repo'); + assert.ok(manager.includes('setActive(root: string): void'), 'manager can set active repo'); + assert.ok(manager.includes('findByUri(uri: vscode.Uri): Repository | undefined'), 'manager resolves repo by URI'); + assert.ok(manager.includes('sort((a, b) => b.root.length - a.root.length)'), 'nested repos prefer deepest root'); + assert.ok(manager.includes('relativePath(repo: Repository, uri: vscode.Uri): string'), 'manager owns git-relative path conversion'); + assert.ok(manager.includes('"--absolute-git-dir"'), 'manager resolves linked-worktree git dirs'); + assert.ok(manager.includes('"--git-common-dir"'), 'manager resolves common git dirs for refs'); + assert.ok(manager.includes('watchGitPath(root, watchPath)'), 'manager watches resolved git paths'); + assert.ok(staging.includes('return this.manager.getActive();'), 'staging view uses active repo'); + assert.ok(sync.includes('this.repo = this.manager.getActive();'), 'synchronize view uses active repo'); + assert.ok(reflog.includes('this.repo = this.manager.getActive();'), 'reflog view uses active repo'); + assert.ok(history.includes('repo ?? this.manager.getActive()'), 'history fallback uses active repo'); + assert.ok(graph.includes('initialRepo ?? manager.getActive()'), 'graph fallback uses active repo'); + assert.ok(quickDiff.includes('this.manager.findByUri(uri)'), 'quick diff uses containment lookup'); + assert.ok(blame.includes('this.manager.findByUri(editor.document.uri)'), 'blame uses containment lookup'); + assert.ok(compare.includes('manager.findByUri(uri)'), 'compare uses containment lookup'); +}); + test('Git Graph trace defaults to explicit toolbar control', () => { const graphJs = read('resources/graph.js'); @@ -167,8 +318,16 @@ test('Git Graph top bar buttons expose labels and table fills available width', for (const id of buttonIds) { const button = graphPanel.match(new RegExp(`]+id="${id}"[^>]+>`)); assert.ok(button, `${id} exists`); - assert.ok(button[0].includes('data-label='), `${id} has data-label`); assert.ok(button[0].includes('aria-label='), `${id} has aria-label`); + if (id === 'tb-trace') { + // Trace shows a visible text label rather than the hover-only data-label. + assert.ok( + graphPanel.includes('Trace'), + 'tb-trace has a visible text label', + ); + } else { + assert.ok(button[0].includes('data-label='), `${id} has data-label`); + } } assert.ok(graphCss.includes('.tb-btn.icon-only[data-label]::after'), 'toolbar labels use CSS tooltip'); @@ -195,3 +354,52 @@ test('Git Graph trace keeps the graph overlay bright', () => { 'active traced graph nodes are visually emphasized', ); }); + +test('documentation library is the final sidebar view and has a full-panel command', () => { + const pkg = JSON.parse(read('package.json')); + const views = pkg.contributes?.views?.vsgit ?? []; + const documentation = views.find((view) => view.id === 'vsgit.documentation'); + const extension = read('src/extension.ts'); + const provider = read('src/webviews/documentation/DocumentationProvider.ts'); + const client = read('resources/documentation.js'); + const readme = read('README.md'); + const hiddenCommands = new Set( + (pkg.contributes?.menus?.commandPalette ?? []) + .filter((entry) => entry.when === 'false') + .map((entry) => entry.command), + ); + const operationCount = pkg.contributes.commands.length; + const paletteCount = operationCount - hiddenCommands.size; + + assert.ok(documentation, 'documentation view is contributed'); + assert.strictEqual(views.at(-1)?.id, 'vsgit.documentation', 'documentation is the bottom view'); + assert.strictEqual(documentation.type, 'webview', 'documentation is a webview screen'); + assert.ok( + pkg.contributes.commands.some((command) => command.command === 'vsgit.documentation.open'), + 'full documentation panel command is contributed', + ); + assert.ok( + extension.includes('DocumentationProvider.viewType'), + 'documentation webview provider is registered', + ); + assert.ok( + extension.includes('vscode.commands.registerCommand("vsgit.documentation.open"'), + 'full documentation panel command is registered', + ); + assert.ok(provider.includes('this.allowedCommands.has(message.command)'), 'run-command bridge validates command ids'); + assert.ok(client.includes('data.operationCategories'), 'client renders the manifest-driven operation catalog'); + assert.ok(client.includes('data.glossary'), 'client renders the Git glossary'); + assert.ok(client.includes('data.components'), 'client renders extension components'); + assert.ok( + readme.includes(`all **${operationCount} contributed operations**`), + 'README operation count matches the manifest', + ); + assert.ok( + readme.includes(`**${paletteCount} Command Palette operations**`), + 'README palette count matches the manifest', + ); + assert.ok( + readme.includes(`**${hiddenCommands.size} contextual actions**`), + 'README contextual-action count matches the manifest', + ); +}); diff --git a/resources/phase10.test.js b/resources/phase10.test.js new file mode 100644 index 0000000..008e218 --- /dev/null +++ b/resources/phase10.test.js @@ -0,0 +1,82 @@ +'use strict'; + +const { test } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); + +const root = path.resolve(__dirname, '..'); +const read = (relative) => fs.readFileSync(path.join(root, relative), 'utf8'); +const pkg = JSON.parse(read('package.json')); + +test('Phase 10 verification scripts enforce coverage, integration, audit, and packaging', () => { + assert.ok(pkg.scripts['test:coverage'].includes('--test-coverage-lines=80')); + assert.ok(pkg.scripts['test:coverage'].includes('--test-coverage-branches=80')); + assert.ok(pkg.scripts['test:coverage'].includes('--test-coverage-functions=70')); + assert.ok(pkg.scripts['test:integration'].includes('vscode-test')); + assert.ok(pkg.scripts['package:verify'].includes('verify-vsix.mjs')); + + const integrationConfig = read('.vscode-test.mjs'); + const integrationTest = read('src/test/extension.integration.test.ts'); + assert.ok(integrationConfig.includes('out-integration/**/*.test.js')); + assert.ok(integrationTest.includes('await extension.activate()')); + assert.ok(integrationTest.includes('vsgit.repositories.refresh')); + assert.ok(integrationTest.includes('vsgit.documentation.open')); +}); + +test('CI and publish workflows run the complete quality gate', () => { + const ci = read('.github/workflows/ci.yml'); + const publish = read('.github/workflows/publish.yml'); + for (const required of [ + 'npm run test:coverage', + 'npm run test:integration', + 'npm audit --audit-level=high', + 'npm run package:verify', + ]) { + assert.ok(ci.includes(required), `CI includes ${required}`); + } + for (const required of [ + 'npm run test:coverage', + 'npm run test:integration', + 'npm audit --audit-level=high', + 'verify-vsix.mjs', + ]) { + assert.ok(publish.includes(required), `publish includes ${required}`); + } +}); + +test('repository discovery is concurrent, coalesced, and measured', () => { + const manager = read('src/git/RepositoryManager.ts'); + assert.ok(manager.includes('private scanInFlight: Promise | undefined')); + assert.ok(manager.includes('folders.map((folder) => this.discoverRoot')); + assert.ok(manager.includes('await Promise.all(newRoots.map((root) => this.watch(root)))')); + assert.ok(manager.includes('getPerformanceSnapshot()')); + assert.ok(manager.includes('performance.now() - startedAt')); + const repository = read('src/git/Repository.ts'); + assert.ok(repository.includes('async ensureSubmodules()')); + assert.ok(repository.includes('if (this.submodulesLoaded)')); +}); + +test('contributor and release documentation covers Phase 10 gates', () => { + const contributing = read('CONTRIBUTING.md'); + const marketplace = read('docs/MARKETPLACE_CHECKLIST.md'); + for (const command of [ + 'npm run verify', + 'npm run test:integration', + 'npm run package:verify', + ]) { + assert.ok(contributing.includes(command), `CONTRIBUTING includes ${command}`); + } + assert.ok(contributing.includes('Accessibility requirements')); + assert.ok(marketplace.includes('npm run test:coverage')); + assert.ok(marketplace.includes('VSCE_PAT')); + assert.ok(marketplace.includes('Rollback')); +}); + +test('package and lockfile versions remain aligned', () => { + const lock = JSON.parse(read('package-lock.json')); + assert.strictEqual(lock.version, pkg.version); + assert.strictEqual(lock.packages[''].version, pkg.version); + assert.strictEqual(pkg.capabilities.untrustedWorkspaces.supported, false); + assert.strictEqual(pkg.capabilities.virtualWorkspaces.supported, false); +}); diff --git a/resources/seti.css b/resources/seti.css new file mode 100644 index 0000000..fdee4ef --- /dev/null +++ b/resources/seti.css @@ -0,0 +1,217 @@ +/* Seti file icons — generated from VS Code's theme-seti + (extensions/theme-seti/icons/vs-seti-icon-theme.json). The Seti icon font is + the icon set behind VS Code's default "Seti" File Icon Theme shown in the + Explorer. Webviews cannot read the active File Icon Theme (vscode#183893), so + we bundle the font + mapping directly, the same approach GitLens uses. */ +@font-face { + font-family: "seti"; + src: url("./seti.woff") format("woff"); + font-weight: normal; + font-style: normal; +} +.seti-icon::before { + font-family: "seti" !important; + font-style: normal; + font-weight: normal; + font-display: block; + speak: never; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +.seti-R::before { content: "\E001"; color: #519aba; } +.seti-argdown::before { content: "\E003"; color: #519aba; } +.seti-asm::before { content: "\E004"; color: #cc3e44; } +.seti-audio::before { content: "\E005"; color: #a074c4; } +.seti-babel::before { content: "\E006"; color: #cbcb41; } +.seti-bazel::before { content: "\E007"; color: #8dc149; } +.seti-bazel_1::before { content: "\E007"; color: #4d5a5e; } +.seti-bicep::before { content: "\E008"; color: #519aba; } +.seti-bower::before { content: "\E009"; color: #e37933; } +.seti-bsl::before { content: "\E00A"; color: #cc3e44; } +.seti-c::before { content: "\E00C"; color: #519aba; } +.seti-c-sharp::before { content: "\E00B"; color: #519aba; } +.seti-c_1::before { content: "\E00C"; color: #a074c4; } +.seti-c_2::before { content: "\E00C"; color: #cbcb41; } +.seti-cake::before { content: "\E00D"; color: #cc3e44; } +.seti-cake_php::before { content: "\E00E"; color: #cc3e44; } +.seti-clock::before { content: "\E012"; color: #519aba; } +.seti-clock_1::before { content: "\E012"; color: #6d8086; } +.seti-clojure::before { content: "\E013"; color: #8dc149; } +.seti-clojure_1::before { content: "\E013"; color: #519aba; } +.seti-code-climate::before { content: "\E014"; color: #8dc149; } +.seti-code-search::before { content: "\E015"; color: #a074c4; } +.seti-coffee::before { content: "\E016"; color: #cbcb41; } +.seti-coldfusion::before { content: "\E018"; color: #519aba; } +.seti-config::before { content: "\E019"; color: #6d8086; } +.seti-cpp::before { content: "\E01A"; color: #519aba; } +.seti-cpp_1::before { content: "\E01A"; color: #a074c4; } +.seti-cpp_2::before { content: "\E01A"; color: #cbcb41; } +.seti-crystal::before { content: "\E01B"; color: #d4d7d6; } +.seti-crystal_embedded::before { content: "\E01C"; color: #d4d7d6; } +.seti-css::before { content: "\E01D"; color: #519aba; } +.seti-csv::before { content: "\E01E"; color: #8dc149; } +.seti-cu::before { content: "\E01F"; color: #8dc149; } +.seti-cu_1::before { content: "\E01F"; color: #a074c4; } +.seti-d::before { content: "\E020"; color: #cc3e44; } +.seti-dart::before { content: "\E021"; color: #519aba; } +.seti-db::before { content: "\E022"; color: #f55385; } +.seti-db_1::before { content: "\E022"; color: #519aba; } +.seti-default::before { content: "\E023"; color: #d4d7d6; } +.seti-docker::before { content: "\E025"; color: #519aba; } +.seti-docker_1::before { content: "\E025"; color: #4d5a5e; } +.seti-docker_2::before { content: "\E025"; color: #8dc149; } +.seti-docker_3::before { content: "\E025"; color: #f55385; } +.seti-ejs::before { content: "\E027"; color: #cbcb41; } +.seti-elixir::before { content: "\E028"; color: #a074c4; } +.seti-elixir_script::before { content: "\E029"; color: #a074c4; } +.seti-elm::before { content: "\E02A"; color: #519aba; } +.seti-eslint::before { content: "\E02C"; color: #a074c4; } +.seti-eslint_1::before { content: "\E02C"; color: #4d5a5e; } +.seti-ethereum::before { content: "\E02D"; color: #519aba; } +.seti-f-sharp::before { content: "\E02E"; color: #519aba; } +.seti-favicon::before { content: "\E02F"; color: #cbcb41; } +.seti-firebase::before { content: "\E030"; color: #e37933; } +.seti-firefox::before { content: "\E031"; color: #e37933; } +.seti-font::before { content: "\E033"; color: #cc3e44; } +.seti-git::before { content: "\E034"; color: #41535b; } +.seti-github::before { content: "\E037"; color: #d4d7d6; } +.seti-gitlab::before { content: "\E038"; color: #e37933; } +.seti-go::before { content: "\E039"; color: #519aba; } +.seti-go2::before { content: "\E03A"; color: #519aba; } +.seti-godot::before { content: "\E03B"; color: #519aba; } +.seti-godot_1::before { content: "\E03B"; color: #cc3e44; } +.seti-godot_2::before { content: "\E03B"; color: #cbcb41; } +.seti-godot_3::before { content: "\E03B"; color: #a074c4; } +.seti-gradle::before { content: "\E03C"; color: #519aba; } +.seti-grails::before { content: "\E03D"; color: #8dc149; } +.seti-graphql::before { content: "\E03E"; color: #f55385; } +.seti-grunt::before { content: "\E03F"; color: #e37933; } +.seti-gulp::before { content: "\E040"; color: #cc3e44; } +.seti-hacklang::before { content: "\E041"; color: #e37933; } +.seti-haml::before { content: "\E042"; color: #cc3e44; } +.seti-happenings::before { content: "\E043"; color: #519aba; } +.seti-haskell::before { content: "\E044"; color: #a074c4; } +.seti-haxe::before { content: "\E045"; color: #e37933; } +.seti-haxe_1::before { content: "\E045"; color: #cbcb41; } +.seti-haxe_2::before { content: "\E045"; color: #519aba; } +.seti-haxe_3::before { content: "\E045"; color: #a074c4; } +.seti-heroku::before { content: "\E046"; color: #a074c4; } +.seti-hex::before { content: "\E047"; color: #cc3e44; } +.seti-html::before { content: "\E048"; color: #519aba; } +.seti-html_1::before { content: "\E048"; color: #8dc149; } +.seti-html_2::before { content: "\E048"; color: #cbcb41; } +.seti-html_3::before { content: "\E048"; color: #e37933; } +.seti-html_erb::before { content: "\E049"; color: #cc3e44; } +.seti-ignored::before { content: "\E04A"; color: #41535b; } +.seti-illustrator::before { content: "\E04B"; color: #cbcb41; } +.seti-image::before { content: "\E04C"; color: #a074c4; } +.seti-info::before { content: "\E04D"; color: #519aba; } +.seti-ionic::before { content: "\E04E"; color: #519aba; } +.seti-jade::before { content: "\E04F"; color: #cc3e44; } +.seti-java::before { content: "\E050"; color: #cc3e44; } +.seti-java_1::before { content: "\E050"; color: #519aba; } +.seti-javascript::before { content: "\E051"; color: #cbcb41; } +.seti-javascript_1::before { content: "\E051"; color: #e37933; } +.seti-javascript_2::before { content: "\E051"; color: #519aba; } +.seti-jenkins::before { content: "\E052"; color: #cc3e44; } +.seti-jinja::before { content: "\E053"; color: #cc3e44; } +.seti-json::before { content: "\E055"; color: #cbcb41; } +.seti-json_1::before { content: "\E055"; color: #8dc149; } +.seti-julia::before { content: "\E056"; color: #a074c4; } +.seti-karma::before { content: "\E057"; color: #8dc149; } +.seti-kotlin::before { content: "\E058"; color: #e37933; } +.seti-less::before { content: "\E059"; color: #519aba; } +.seti-license::before { content: "\E05A"; color: #cbcb41; } +.seti-license_1::before { content: "\E05A"; color: #e37933; } +.seti-license_2::before { content: "\E05A"; color: #cc3e44; } +.seti-liquid::before { content: "\E05B"; color: #8dc149; } +.seti-livescript::before { content: "\E05C"; color: #519aba; } +.seti-lock::before { content: "\E05D"; color: #8dc149; } +.seti-lua::before { content: "\E05E"; color: #519aba; } +.seti-makefile::before { content: "\E05F"; color: #e37933; } +.seti-makefile_1::before { content: "\E05F"; color: #a074c4; } +.seti-makefile_2::before { content: "\E05F"; color: #6d8086; } +.seti-makefile_3::before { content: "\E05F"; color: #519aba; } +.seti-markdown::before { content: "\E060"; color: #519aba; } +.seti-maven::before { content: "\E061"; color: #cc3e44; } +.seti-mdo::before { content: "\E062"; color: #cc3e44; } +.seti-mustache::before { content: "\E063"; color: #e37933; } +.seti-nim::before { content: "\E065"; color: #cbcb41; } +.seti-notebook::before { content: "\E066"; color: #519aba; } +.seti-npm::before { content: "\E067"; color: #41535b; } +.seti-npm_1::before { content: "\E067"; color: #cc3e44; } +.seti-npm_ignored::before { content: "\E068"; color: #41535b; } +.seti-nunjucks::before { content: "\E069"; color: #8dc149; } +.seti-ocaml::before { content: "\E06A"; color: #e37933; } +.seti-odata::before { content: "\E06B"; color: #e37933; } +.seti-pddl::before { content: "\E06C"; color: #a074c4; } +.seti-pdf::before { content: "\E06D"; color: #cc3e44; } +.seti-perl::before { content: "\E06E"; color: #519aba; } +.seti-photoshop::before { content: "\E06F"; color: #519aba; } +.seti-php::before { content: "\E070"; color: #a074c4; } +.seti-pipeline::before { content: "\E071"; color: #e37933; } +.seti-plan::before { content: "\E072"; color: #8dc149; } +.seti-platformio::before { content: "\E073"; color: #e37933; } +.seti-powershell::before { content: "\E074"; color: #519aba; } +.seti-prisma::before { content: "\E075"; color: #519aba; } +.seti-prolog::before { content: "\E077"; color: #e37933; } +.seti-pug::before { content: "\E078"; color: #cc3e44; } +.seti-puppet::before { content: "\E079"; color: #cbcb41; } +.seti-purescript::before { content: "\E07A"; color: #d4d7d6; } +.seti-python::before { content: "\E07B"; color: #519aba; } +.seti-react::before { content: "\E07D"; color: #519aba; } +.seti-react_1::before { content: "\E07D"; color: #e37933; } +.seti-reasonml::before { content: "\E07E"; color: #cc3e44; } +.seti-rescript::before { content: "\E07F"; color: #cc3e44; } +.seti-rescript_1::before { content: "\E07F"; color: #f55385; } +.seti-rollup::before { content: "\E080"; color: #cc3e44; } +.seti-ruby::before { content: "\E081"; color: #cc3e44; } +.seti-rust::before { content: "\E082"; color: #6d8086; } +.seti-salesforce::before { content: "\E083"; color: #519aba; } +.seti-sass::before { content: "\E084"; color: #f55385; } +.seti-sbt::before { content: "\E085"; color: #519aba; } +.seti-scala::before { content: "\E086"; color: #cc3e44; } +.seti-shell::before { content: "\E089"; color: #8dc149; } +.seti-slim::before { content: "\E08A"; color: #e37933; } +.seti-smarty::before { content: "\E08B"; color: #cbcb41; } +.seti-spring::before { content: "\E08C"; color: #8dc149; } +.seti-stylelint::before { content: "\E08D"; color: #d4d7d6; } +.seti-stylelint_1::before { content: "\E08D"; color: #4d5a5e; } +.seti-stylus::before { content: "\E08E"; color: #8dc149; } +.seti-sublime::before { content: "\E08F"; color: #e37933; } +.seti-svelte::before { content: "\E090"; color: #cc3e44; } +.seti-svg::before { content: "\E091"; color: #a074c4; } +.seti-svg_1::before { content: "\E091"; color: #519aba; } +.seti-swift::before { content: "\E092"; color: #e37933; } +.seti-terraform::before { content: "\E093"; color: #a074c4; } +.seti-tex::before { content: "\E094"; color: #519aba; } +.seti-tex_1::before { content: "\E094"; color: #cbcb41; } +.seti-tex_2::before { content: "\E094"; color: #e37933; } +.seti-tex_3::before { content: "\E094"; color: #d4d7d6; } +.seti-todo::before { content: "\E096"; color: #cccccc; } +.seti-tsconfig::before { content: "\E097"; color: #519aba; } +.seti-twig::before { content: "\E098"; color: #8dc149; } +.seti-typescript::before { content: "\E099"; color: #519aba; } +.seti-typescript_1::before { content: "\E099"; color: #e37933; } +.seti-vala::before { content: "\E09A"; color: #6d8086; } +.seti-video::before { content: "\E09B"; color: #f55385; } +.seti-vite::before { content: "\E09C"; color: #cbcb41; } +.seti-vue::before { content: "\E09D"; color: #8dc149; } +.seti-wasm::before { content: "\E09E"; color: #a074c4; } +.seti-wat::before { content: "\E09F"; color: #a074c4; } +.seti-webpack::before { content: "\E0A0"; color: #519aba; } +.seti-wgt::before { content: "\E0A1"; color: #519aba; } +.seti-windows::before { content: "\E0A2"; color: #519aba; } +.seti-word::before { content: "\E0A3"; color: #519aba; } +.seti-xls::before { content: "\E0A4"; color: #8dc149; } +.seti-xml::before { content: "\E0A5"; color: #e37933; } +.seti-yarn::before { content: "\E0A6"; color: #519aba; } +.seti-yml::before { content: "\E0A7"; color: #a074c4; } +.seti-zig::before { content: "\E0A8"; color: #e37933; } +.seti-zip::before { content: "\E0A9"; color: #cc3e44; } +.seti-zip_1::before { content: "\E0A9"; color: #6d8086; } diff --git a/resources/seti.woff b/resources/seti.woff new file mode 100644 index 0000000000000000000000000000000000000000..6a8a3da7cf05b57901a2d75bfc7acf7067438f07 GIT binary patch literal 37284 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%0DSql$v{z$czc3}#|B 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 entry.startsWith(prefix)) || + entry.endsWith(".test.js") || + entry.endsWith(".test.ts") + ) { + throw new Error(`Development-only file leaked into VSIX: ${entry}`); + } +} + +const packagedManifest = JSON.parse( + unzip(["-p", vsixPath, "extension/package.json"]), +); +if (packagedManifest.name !== rootManifest.name) { + throw new Error( + `Packaged name ${packagedManifest.name} does not match ${rootManifest.name}`, + ); +} +if (packagedManifest.publisher !== rootManifest.publisher) { + throw new Error( + `Packaged publisher ${packagedManifest.publisher} does not match ${rootManifest.publisher}`, + ); +} +if (packagedManifest.version !== rootManifest.version) { + throw new Error( + `Packaged version ${packagedManifest.version} does not match ${rootManifest.version}`, + ); +} + +const sizeMiB = statSync(vsixPath).size / (1024 * 1024); +if (sizeMiB > 10) { + throw new Error(`VSIX is unexpectedly large: ${sizeMiB.toFixed(2)} MiB`); +} + +console.log( + `Verified ${rootManifest.publisher}.${rootManifest.name}@${rootManifest.version}: ` + + `${entries.length} files, ${sizeMiB.toFixed(2)} MiB`, +); diff --git a/src/commands/branch.ts b/src/commands/branch.ts index 8145b06..84639fb 100644 --- a/src/commands/branch.ts +++ b/src/commands/branch.ts @@ -1,10 +1,8 @@ import * as vscode from "vscode"; -import { GitExecutor } from "../git/GitExecutor"; import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { Repository } from "../git/Repository"; import { resolveRepo, withProgress, errMsg } from "./shared"; -import { safeRef } from "../git/argGuard"; import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** @@ -15,8 +13,6 @@ export function registerBranchCommands( context: vscode.ExtensionContext, manager: RepositoryManager, ): void { - const git = new GitExecutor(); - context.subscriptions.push( vscode.commands.registerCommand( "vsgit.branch.checkout", @@ -26,9 +22,7 @@ export function registerBranchCommands( return; } try { - await git.run(["checkout", safeRef(target.name, "branch")], { - cwd: target.repo.root, - }); + await target.repo.checkoutRef(target.name); await manager.refreshAll(); vscode.window.setStatusBarMessage( `Checked out ${target.name}`, @@ -62,13 +56,8 @@ export function registerBranchCommands( if (!checkout) { return; } - const safeName = safeRef(name.trim(), "branch"); - const args = - checkout === "Create and checkout" - ? ["checkout", "-b", safeName] - : ["branch", safeName]; try { - await git.run(args, { cwd: repo.root }); + await repo.createBranchAt(name.trim(), "HEAD", checkout === "Create and checkout"); await manager.refreshAll(); } catch (err) { vscode.window.showErrorMessage( @@ -85,20 +74,21 @@ export function registerBranchCommands( if (!target) { return; } - const confirm = await vscode.window.showWarningMessage( - `Delete branch ${target.name}?`, - { modal: true }, - "Delete", - "Force Delete", - ); - if (!confirm) { + const force = await vscode.window.showQuickPick(["Delete", "Force Delete"], { + placeHolder: `Delete branch ${target.name}`, + }); + if (!force) { + return; + } + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_BRANCH, + message: `${force} branch ${target.name}?`, + }); + if (!confirmed) { return; } - const flag = confirm === "Force Delete" ? "-D" : "-d"; try { - await git.run(["branch", flag, safeRef(target.name, "branch")], { - cwd: target.repo.root, - }); + await target.repo.deleteBranch(target.name, force === "Force Delete"); await manager.refreshAll(); } catch (err) { vscode.window.showErrorMessage( @@ -166,8 +156,6 @@ export function registerBranchCommands( ); } -type ResetMode = "soft" | "mixed" | "hard" | "keep" | "merge"; - /** Reset the current branch HEAD to a chosen ref. */ async function doBranchReset( manager: RepositoryManager, @@ -181,33 +169,47 @@ async function doBranchReset( { label: "Soft", description: "Move HEAD only; keep index and working tree" }, { label: "Mixed", description: "Move HEAD and reset index; keep working tree" }, { label: "Hard", description: "Move HEAD, reset index and working tree" }, - { label: "Keep", description: "Reset index; abort if a touched file has local changes" }, - { label: "Merge", description: "Reset index; keep local changes to untouched files" }, ], { placeHolder: "Reset mode" }, ); if (!mode) return; - await resetToRef(manager, repo, mode.label.toLowerCase() as ResetMode); + const refs = [ + "HEAD~1", + ...repo.localBranches.map((b) => b.shortName), + ...repo.remoteBranches.map((b) => b.shortName), + ...repo.tags.map((t) => t.shortName), + ]; + const ref = await vscode.window.showQuickPick(refs, { + placeHolder: "Reset to ref / SHA", + }); + if (!ref) return; + + if (mode.label === "Hard") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.HARD_RESET, + message: `Hard reset to ${ref}? All uncommitted changes will be lost.`, + }); + if (!confirmed) return; + } + + await withProgress( + manager, + `Reset --${mode.label.toLowerCase()} to ${ref}`, + () => repo.reset(ref, mode.label.toLowerCase() as ResetMode), + ); } -/** Reset the current branch HEAD using a fixed mode, prompting only for the ref. */ -async function doResetWithMode( +type ResetMode = "soft" | "mixed" | "hard" | "keep" | "merge"; + +async function resetWithMode( manager: RepositoryManager, node: unknown, mode: ResetMode, ): Promise { const repo = await resolveRepo(manager, node as VsgitNode); if (!repo) return; - await resetToRef(manager, repo, mode); -} -/** Shared ref picker + confirmation + execution for every reset entry point. */ -async function resetToRef( - manager: RepositoryManager, - repo: Repository, - mode: ResetMode, -): Promise { const refs = [ "HEAD~1", ...repo.localBranches.map((b) => b.shortName), @@ -242,11 +244,11 @@ export function registerBranchExtraCommands( // ── Branch reset ────────────────────────────────────────────────────── reg("vsgit.branch.reset", (node) => doBranchReset(manager, node)); - reg("vsgit.repo.reset.soft", (node) => doResetWithMode(manager, node, "soft")); - reg("vsgit.repo.reset.mixed", (node) => doResetWithMode(manager, node, "mixed")); - reg("vsgit.repo.reset.hard", (node) => doResetWithMode(manager, node, "hard")); - reg("vsgit.repo.reset.keep", (node) => doResetWithMode(manager, node, "keep")); - reg("vsgit.repo.reset.merge", (node) => doResetWithMode(manager, node, "merge")); + reg("vsgit.repo.reset.soft", (node) => resetWithMode(manager, node, "soft")); + reg("vsgit.repo.reset.mixed", (node) => resetWithMode(manager, node, "mixed")); + reg("vsgit.repo.reset.hard", (node) => resetWithMode(manager, node, "hard")); + reg("vsgit.repo.reset.keep", (node) => resetWithMode(manager, node, "keep")); + reg("vsgit.repo.reset.merge", (node) => resetWithMode(manager, node, "merge")); // ── Compare branches ───────────────────────────────────────────────── @@ -331,12 +333,11 @@ export function registerBranchExtraCommands( } const [remoteName, ...branchParts] = n.ref.shortName.split("/"); const branchName = branchParts.join("/"); - const confirm = await vscode.window.showWarningMessage( - `Delete remote branch ${n.ref.shortName}? This cannot be undone.`, - { modal: true }, - "Delete", - ); - if (confirm !== "Delete") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_REMOTE_BRANCH, + message: `Delete remote branch ${n.ref.shortName}? This cannot be undone.`, + }); + if (!confirmed) return; await withProgress(manager, `Delete remote branch ${n.ref.shortName}`, () => n.repo.deleteRemoteBranch(remoteName, branchName), ); diff --git a/src/commands/clone.ts b/src/commands/clone.ts index 5700f61..fad0e40 100644 --- a/src/commands/clone.ts +++ b/src/commands/clone.ts @@ -1,6 +1,5 @@ import * as path from "node:path"; import * as vscode from "vscode"; -import { GitExecutor } from "../git/GitExecutor"; import { RepositoryManager } from "../git/RepositoryManager"; import { AskpassServer } from "../util/AskpassServer"; import { safeRef, safeRemoteUrl } from "../git/argGuard"; @@ -15,7 +14,7 @@ export function registerCloneCommands( context: vscode.ExtensionContext, manager: RepositoryManager, ): void { - const git = new GitExecutor(); + const git = manager.getGitExecutor(); const shimPath = path.join(context.extensionPath, "resources", "askpass.js"); context.subscriptions.push( diff --git a/src/commands/compare.ts b/src/commands/compare.ts index 55dd1d1..21c9b55 100644 --- a/src/commands/compare.ts +++ b/src/commands/compare.ts @@ -28,7 +28,7 @@ export function registerCompareCommands( return; } const fsPath = uri.fsPath; - const repo = manager.getAll().find((r) => fsPath.startsWith(r.root)); + const repo = manager.findByUri(uri); if (!repo) { vscode.window.showWarningMessage("File is not in a known repository."); return; @@ -37,7 +37,7 @@ export function registerCompareCommands( if (!ref) { return; } - const rel = fsPath.slice(repo.root.length + 1); + const rel = manager.relativePath(repo, uri); const left = GitContentProvider.uri(repo.root, rel, ref, fsPath); await vscode.commands.executeCommand( "vscode.diff", @@ -331,4 +331,3 @@ function fileStatusIcon(status: string): string { default: return "circle-outline"; } } - diff --git a/src/commands/fileContext.ts b/src/commands/fileContext.ts index 1b1def6..2bde500 100644 --- a/src/commands/fileContext.ts +++ b/src/commands/fileContext.ts @@ -1,5 +1,6 @@ import * as path from "node:path"; import * as vscode from "vscode"; +import { Repository } from "../git/Repository"; import { RepositoryManager } from "../git/RepositoryManager"; import { GitContentProvider } from "../git/GitContentProvider"; import { errMsg, withProgress } from "./shared"; @@ -26,7 +27,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "Stage", () => repo.stage(rels)); }); @@ -35,7 +36,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "Unstage", () => repo.unstage(rels)); }); @@ -44,7 +45,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); try { await repo.addToGitignore(rels); vscode.window.showInformationMessage(`Added ${rels.length} pattern(s) to .gitignore`); @@ -77,7 +78,7 @@ export function registerFileContextCommands( if (!uri) return; const repo = repoForUri(manager, uri); if (!repo) return; - const sha = await CommitPickerView.pick(repo); + const sha = await CommitPickerView.pick(repo, context.extensionUri); if (!sha) return; await compareFileWith(manager, uri, sha); }); @@ -180,7 +181,7 @@ export function registerFileContextCommands( if (!uri) return; const repo = repoForUri(manager, uri); if (!repo) return; - const rel = path.relative(repo.root, uri.fsPath); + const rel = manager.relativePath(repo, uri); await vscode.commands.executeCommand("vsgit.history.show", { repoRoot: repo.root, file: rel, @@ -194,7 +195,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "Assume unchanged", () => repo.assumeUnchanged(rels, true), ); @@ -205,7 +206,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "No assume unchanged", () => repo.assumeUnchanged(rels, false), ); @@ -216,7 +217,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "Skip worktree", () => repo.skipWorktree(rels, true), ); @@ -227,7 +228,7 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); + const rels = relativePaths(manager, repo, uris); await withProgress(manager, "No skip worktree", () => repo.skipWorktree(rels, false), ); @@ -238,13 +239,13 @@ export function registerFileContextCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const rels = uris.map((u) => path.relative(repo.root, u.fsPath)); - const confirm = await vscode.window.showWarningMessage( - `Remove ${rels.length} file(s) from Git tracking (git rm --cached)?`, - { modal: true }, - "Untrack", - ); - if (confirm !== "Untrack") return; + const rels = relativePaths(manager, repo, uris); + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `Remove ${rels.length} file(s) from Git tracking (git rm --cached)?`, + items: rels, + }); + if (!confirmed) return; await withProgress(manager, "Untrack", () => repo.untrack(rels)); }); @@ -254,12 +255,12 @@ export function registerFileContextCommands( const uris = resolveUris(uriArg, allUris); const repo = uris.length > 0 ? repoForUri(manager, uris[0]) - : manager.getAll()[0]; + : manager.getActive(); if (!repo) return; const rels = uris - .filter((u) => u.fsPath.startsWith(repo.root)) - .map((u) => path.relative(repo.root, u.fsPath)); + .filter((u) => manager.uriBelongsTo(repo, u)) + .map((u) => manager.relativePath(repo, u)); const confirmed = await confirmDestructiveAction({ operation: DestructiveOperations.CLEAN_UNTRACKED, @@ -290,14 +291,23 @@ function resolveUris(uriArg: unknown, allUris: unknown): vscode.Uri[] { } function repoForUri(manager: RepositoryManager, uri: vscode.Uri) { - if (uri.scheme !== "file") return undefined; - const repo = manager.getAll().find((r) => uri.fsPath.startsWith(r.root)); + const repo = manager.findByUri(uri); if (!repo) { vscode.window.showWarningMessage("File is not in a known Git repository."); } return repo; } +function relativePaths( + manager: RepositoryManager, + repo: Repository, + uris: vscode.Uri[], +): string[] { + return uris + .filter((uri) => manager.uriBelongsTo(repo, uri)) + .map((uri) => manager.relativePath(repo, uri)); +} + async function compareFileWith( manager: RepositoryManager, uri: vscode.Uri | undefined, @@ -306,7 +316,7 @@ async function compareFileWith( if (!uri) return; const repo = repoForUri(manager, uri); if (!repo) return; - const rel = path.relative(repo.root, uri.fsPath); + const rel = manager.relativePath(repo, uri); const left = GitContentProvider.uri(repo.root, rel, ref, uri.fsPath); const label = `${path.basename(rel)} (${ref} ↔ working tree)`; try { @@ -323,7 +333,7 @@ async function compareFileWithIndex( if (!uri) return; const repo = repoForUri(manager, uri); if (!repo) return; - const rel = path.relative(repo.root, uri.fsPath); + const rel = manager.relativePath(repo, uri); // Use "" (empty string) as the ref to mean "index / staged" const left = GitContentProvider.uri(repo.root, rel, "", uri.fsPath); const label = `${path.basename(rel)} (Index ↔ working tree)`; @@ -342,7 +352,7 @@ async function compareIndexWithHead( if (!uri) return; const repo = repoForUri(manager, uri); if (!repo) return; - const rel = path.relative(repo.root, uri.fsPath); + const rel = manager.relativePath(repo, uri); const headUri = GitContentProvider.uri(repo.root, rel, "HEAD", uri.fsPath); const indexUri = GitContentProvider.uri(repo.root, rel, "", uri.fsPath); const label = `${path.basename(rel)} (HEAD ↔ Index)`; diff --git a/src/commands/history.ts b/src/commands/history.ts index 209b688..100ef7e 100644 --- a/src/commands/history.ts +++ b/src/commands/history.ts @@ -11,6 +11,7 @@ export function registerHistoryCommands( const view = new HistoryView(manager, context.extensionUri); context.subscriptions.push( + view, vscode.commands.registerCommand( "vsgit.history.show", async (node?: VsgitNode | { repoRoot: string; file?: string }) => { diff --git a/src/commands/lfs.ts b/src/commands/lfs.ts index 4bb4c0d..68936b0 100644 --- a/src/commands/lfs.ts +++ b/src/commands/lfs.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, errMsg, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Git LFS operations: track, lock, pull, prune, etc. */ export function registerLfsCommands( @@ -132,13 +133,14 @@ export function registerLfsCommands( if (!selected) return; const isOwner = selected.lock.owner.name === "you"; // Simplified check - const force = !isOwner - ? await vscode.window.showWarningMessage( - `${selected.lock.path} is locked by ${selected.lock.owner.name}. Force unlock?`, - { modal: true }, - "Force Unlock" - ) === "Force Unlock" - : false; + let force = false; + if (!isOwner) { + force = await confirmDestructiveAction({ + operation: DestructiveOperations.FORCE_CHECKOUT, + message: `${selected.lock.path} is locked by ${selected.lock.owner.name}. Force unlock?`, + }); + if (!force) return; + } try { await withProgress(manager, `Unlocking ${selected.lock.path}`, () => @@ -195,12 +197,11 @@ export function registerLfsCommands( const repo = await resolveRepo(manager, node as VsgitNode); if (!repo) return; - const confirm = await vscode.window.showWarningMessage( - "Prune old LFS objects? This will delete local LFS files that are not referenced by any commits.", - { modal: true }, - "Prune" - ); - if (confirm !== "Prune") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.CLEAN_UNTRACKED, + message: "Prune old LFS objects? This will delete local LFS files that are not referenced by any commits.", + }); + if (!confirmed) return; try { await withProgress(manager, "Pruning LFS objects", () => diff --git a/src/commands/maintenance.ts b/src/commands/maintenance.ts index b46ba7b..4807df5 100644 --- a/src/commands/maintenance.ts +++ b/src/commands/maintenance.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Repository maintenance: garbage collection, integrity check, object prune. */ export function registerMaintenanceCommands( @@ -65,12 +66,11 @@ export function registerMaintenanceCommands( if (!repo) { return; } - const confirm = await vscode.window.showWarningMessage( - "Prune all unreachable loose objects? Recently deleted commits not yet referenced may become unrecoverable.", - { modal: true }, - "Prune", - ); - if (confirm !== "Prune") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.CLEAN_UNTRACKED, + message: "Prune all unreachable loose objects? Recently deleted commits not yet referenced may become unrecoverable.", + }); + if (!confirmed) { return; } await withProgress(manager, "Prune objects", () => repo.pruneObjects()); diff --git a/src/commands/notes.ts b/src/commands/notes.ts index ea93060..358aac5 100644 --- a/src/commands/notes.ts +++ b/src/commands/notes.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, errMsg, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Git notes operations: add, edit, remove, show notes on commits. */ export function registerNotesCommands( @@ -82,12 +83,11 @@ export function registerNotesCommands( }); if (!ref) return; - const confirm = await vscode.window.showWarningMessage( - `Remove note from ${ref}?`, - { modal: true }, - "Remove" - ); - if (confirm !== "Remove") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `Remove note from ${ref}?`, + }); + if (!confirmed) return; try { await withProgress(manager, `Removing note from ${ref}`, () => diff --git a/src/commands/patch.ts b/src/commands/patch.ts index 427b0d6..e7c2dff 100644 --- a/src/commands/patch.ts +++ b/src/commands/patch.ts @@ -2,7 +2,6 @@ import * as path from "node:path"; import * as vscode from "vscode"; import * as fs from "node:fs/promises"; import { RepositoryManager } from "../git/RepositoryManager"; -import { GitExecutor } from "../git/GitExecutor"; import { errMsg, resolveRepo, withProgress } from "./shared"; import { VsgitNode } from "../views/RepositoriesProvider"; @@ -21,7 +20,7 @@ export function registerPatchCommands( reg("vsgit.patch.createFromStaged", async (node) => { const repo = await resolveRepo(manager, node as VsgitNode); if (!repo) return; - const git = new GitExecutor(); + const git = manager.getGitExecutor(); let diff: string; try { diff = await git.stdout(["diff", "--cached"], { cwd: repo.root }); @@ -59,7 +58,7 @@ export function registerPatchCommands( }); if (!nStr) return; const n = Number(nStr.trim()); - const git = new GitExecutor(); + const git = manager.getGitExecutor(); const folder = await vscode.window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, @@ -105,7 +104,7 @@ export function registerPatchCommands( vscode.window.showWarningMessage("No Git repositories found."); return; } - let repo = repos.find((r) => patchUri!.fsPath.startsWith(r.root)); + let repo = manager.findByUri(patchUri); if (!repo) { if (repos.length === 1) { repo = repos[0]; @@ -125,7 +124,7 @@ export function registerPatchCommands( ); if (!mode) return; - const git = new GitExecutor(); + const git = manager.getGitExecutor(); const args = ["apply"]; if (mode.startsWith("Apply (stage")) { args.push("--index"); diff --git a/src/commands/reflog.ts b/src/commands/reflog.ts index 33c2be4..ef5b512 100644 --- a/src/commands/reflog.ts +++ b/src/commands/reflog.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { ReflogNode, ReflogProvider } from "../views/ReflogProvider"; import { withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Reflog view commands: refresh, checkout an entry, reset HEAD to an entry. */ export function registerReflogCommands( @@ -37,12 +38,11 @@ export function registerReflogCommands( return; } if (mode === "hard") { - const confirm = await vscode.window.showWarningMessage( - `Hard reset to ${n.entry.shortSha}? Working tree changes will be lost.`, - { modal: true }, - "Reset Hard", - ); - if (confirm !== "Reset Hard") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.HARD_RESET, + message: `Hard reset to ${n.entry.shortSha}? Working tree changes will be lost.`, + }); + if (!confirmed) { return; } } diff --git a/src/commands/remote.ts b/src/commands/remote.ts index 2cdf6dd..f7a86d3 100644 --- a/src/commands/remote.ts +++ b/src/commands/remote.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Remote management: add, edit (rename/url), delete. */ export function registerRemoteCommands( @@ -37,12 +38,11 @@ export function registerRemoteCommands( if (!n || n.type !== "remote") { return; } - const confirm = await vscode.window.showWarningMessage( - `Remove remote ${n.remoteName}?`, - { modal: true }, - "Remove", - ); - if (confirm !== "Remove") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_REMOTE_BRANCH, + message: `Remove remote ${n.remoteName}?`, + }); + if (!confirmed) { return; } await withProgress(manager, `Remove remote ${n.remoteName}`, () => diff --git a/src/commands/replace.ts b/src/commands/replace.ts index faca5bc..fe3a641 100644 --- a/src/commands/replace.ts +++ b/src/commands/replace.ts @@ -1,9 +1,11 @@ import * as path from "node:path"; import * as vscode from "vscode"; +import { Repository } from "../git/Repository"; import { RepositoryManager } from "../git/RepositoryManager"; import { withProgress } from "./shared"; import { CommitPickerView } from "../webviews/CommitPickerView"; import { RefPickerView } from "../webviews/RefPickerView"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** * "Replace With" operations — restore a file's content from a known ref. @@ -49,7 +51,7 @@ export function registerReplaceCommands( if (uris.length === 0) return; const repo = repoForUri(manager, uris[0]); if (!repo) return; - const sha = await CommitPickerView.pick(repo); + const sha = await CommitPickerView.pick(repo, context.extensionUri); if (!sha) return; await replaceWith(manager, uris, sha); }); @@ -101,14 +103,23 @@ function resolveUris(uriArg: unknown, allUris: unknown): vscode.Uri[] { } function repoForUri(manager: RepositoryManager, uri: vscode.Uri) { - if (uri.scheme !== "file") return undefined; - const repo = manager.getAll().find((r) => uri.fsPath.startsWith(r.root)); + const repo = manager.findByUri(uri); if (!repo) { vscode.window.showWarningMessage("File is not in a known Git repository."); } return repo; } +function relativePaths( + manager: RepositoryManager, + repo: Repository, + uris: vscode.Uri[], +): string[] { + return uris + .filter((uri) => manager.uriBelongsTo(repo, uri)) + .map((uri) => manager.relativePath(repo, uri)); +} + async function replaceWith( manager: RepositoryManager, uris: vscode.Uri[], @@ -120,16 +131,16 @@ async function replaceWith( const label = uris.length === 1 ? `Replace with ${ref}: ${path.basename(uris[0].fsPath)}` : `Replace ${uris.length} file(s) with ${ref}`; - const confirm = await vscode.window.showWarningMessage( - `${label}? Local changes will be lost.`, - { modal: true }, - "Replace", - ); - if (confirm !== "Replace") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `${label}? Local changes will be lost.`, + items: relativePaths(manager, repo, uris), + }); + if (!confirmed) return; await withProgress(manager, label, async () => { for (const uri of uris) { - if (!uri.fsPath.startsWith(repo.root)) continue; - const rel = path.relative(repo.root, uri.fsPath); + if (!manager.uriBelongsTo(repo, uri)) continue; + const rel = manager.relativePath(repo, uri); await repo.replaceWithRef(rel, ref); } }); @@ -145,17 +156,16 @@ async function replaceWithIndex( const label = uris.length === 1 ? `Replace with Index: ${path.basename(uris[0].fsPath)}` : `Replace ${uris.length} file(s) with Index`; - const confirm = await vscode.window.showWarningMessage( - `${label}? Unstaged changes will be lost.`, - { modal: true }, - "Replace", - ); - if (confirm !== "Replace") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `${label}? Unstaged changes will be lost.`, + items: relativePaths(manager, repo, uris), + }); + if (!confirmed) return; await withProgress(manager, label, async () => { const rels = uris - .filter((u) => u.fsPath.startsWith(repo.root)) - .map((u) => path.relative(repo.root, u.fsPath)); + .filter((u) => manager.uriBelongsTo(repo, u)) + .map((u) => manager.relativePath(repo, u)); await repo.discard(rels, []); }); } - diff --git a/src/commands/scm.ts b/src/commands/scm.ts index 3aefda2..500f6c0 100644 --- a/src/commands/scm.ts +++ b/src/commands/scm.ts @@ -1,7 +1,9 @@ import * as vscode from "vscode"; import * as path from "node:path"; import { RepositoryManager } from "../git/RepositoryManager"; -import { GitContentProvider } from "../git/GitContentProvider"; +import { Repository } from "../git/Repository"; +import { GitContentProvider, VSGIT_EMPTY_REF } from "../git/GitContentProvider"; +import type { FileChange } from "../git/parsers/status"; import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; import { withProgress, errMsg } from "./shared"; @@ -17,56 +19,47 @@ export function registerSCMCommands( context.subscriptions.push(vscode.commands.registerCommand(id, fn)); // Stage selected file(s) - reg("vsgit.scm.stage", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.stage", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - for (const uri of uris) { - const rel = uri.fsPath.slice(repo.root.length + 1); - await repo.stage([rel]); - } + await withProgress(manager, `Stage ${uris.length} file(s)`, () => + repo.stage(relativePaths(manager, repo, uris)), + ); vscode.window.setStatusBarMessage("Files staged", 2000); }); // Unstage selected file(s) - reg("vsgit.scm.unstage", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.unstage", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - for (const uri of uris) { - const rel = uri.fsPath.slice(repo.root.length + 1); - await repo.unstage([rel]); - } + await withProgress(manager, `Unstage ${uris.length} file(s)`, () => + repo.unstage(relativePaths(manager, repo, uris)), + ); vscode.window.setStatusBarMessage("Files unstaged", 2000); }); // Discard changes in selected file(s) - reg("vsgit.scm.discard", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.discard", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - const files = uris.map((u) => u.fsPath.slice(repo.root.length + 1)); + const discard = discardPathSets(manager, repo, states); + const files = [...discard.tracked, ...discard.untracked]; + if (files.length === 0) return; const confirmed = await confirmDestructiveAction({ operation: DestructiveOperations.DISCARD_CHANGES, message: `Discard changes in ${files.length} file(s)? This cannot be undone.`, @@ -77,7 +70,7 @@ export function registerSCMCommands( try { await withProgress(manager, `Discarding ${files.length} file(s)`, async () => { - await repo.discard(files, []); + await repo.discard(discard.tracked, discard.untracked); }); vscode.window.showInformationMessage(`Discarded changes in ${files.length} file(s)`); } catch (e) { @@ -97,13 +90,25 @@ export function registerSCMCommands( const repo = findRepoForUri(manager, uri); if (!repo) return; - const rel = uri.fsPath.slice(repo.root.length + 1); - const left = GitContentProvider.uri(repo.root, rel, "HEAD", uri.fsPath); + const rel = manager.relativePath(repo, uri); + const group = resourceGroup(resourceState); + const change = resourceChange(resourceState); + const isIndex = group === "index"; + const left = isIndex + ? GitContentProvider.uri(repo.root, rel, "HEAD", uri.fsPath) + : GitContentProvider.uri(repo.root, rel, "~index", uri.fsPath); + const right = isIndex + ? GitContentProvider.uri(repo.root, rel, "~index", uri.fsPath) + : change?.worktreeState === "deleted" + ? GitContentProvider.uri(repo.root, rel, VSGIT_EMPTY_REF, uri.fsPath) + : uri; await vscode.commands.executeCommand( "vscode.diff", left, - uri, - `${path.basename(rel)} (HEAD ↔ Working Tree)`, + right, + isIndex + ? `${path.basename(rel)} (HEAD ↔ Index)` + : `${path.basename(rel)} (Index ↔ Working Tree)`, ); }); @@ -131,7 +136,7 @@ export function registerSCMCommands( const repo = findRepoForUri(manager, uri); if (!repo) return; - const rel = uri.fsPath.slice(repo.root.length + 1); + const rel = manager.relativePath(repo, uri); await vscode.commands.executeCommand("vsgit.history.show", { repoRoot: repo.root, file: rel, @@ -153,18 +158,15 @@ export function registerSCMCommands( }); // Replace with HEAD - reg("vsgit.scm.replaceWithHead", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.replaceWithHead", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - const files = uris.map((u) => u.fsPath.slice(repo.root.length + 1)); + const files = relativePaths(manager, repo, uris); const confirmed = await confirmDestructiveAction({ operation: DestructiveOperations.DISCARD_CHANGES, message: `Replace ${files.length} file(s) with HEAD version? Local changes will be lost.`, @@ -186,18 +188,15 @@ export function registerSCMCommands( }); // Rename/move a tracked file (git mv) - reg("vsgit.scm.rename", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.rename", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - const rel = uris[0].fsPath.slice(repo.root.length + 1); + const rel = manager.relativePath(repo, uris[0]); const newRel = await vscode.window.showInputBox({ prompt: "New path (relative to the repository root)", value: rel, @@ -220,18 +219,15 @@ export function registerSCMCommands( }); // Delete tracked file(s) from the working tree and index (git rm) - reg("vsgit.scm.delete", async (...resourceStates: unknown[]) => { - const uris = resourceStates - .filter((r): r is { resourceUri: vscode.Uri } => - typeof r === 'object' && r !== null && 'resourceUri' in r - ) - .map((r) => r.resourceUri); + reg("vsgit.scm.delete", async (...args: unknown[]) => { + const states = resourceStates(args); + const uris = states.map((state) => state.resourceUri); if (uris.length === 0) return; const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - const files = uris.map((u) => u.fsPath.slice(repo.root.length + 1)); + const files = relativePaths(manager, repo, uris); const confirmed = await confirmDestructiveAction({ operation: DestructiveOperations.DISCARD_CHANGES, message: `Delete ${files.length} file(s) from the working tree and index (git rm)? This cannot be undone.`, @@ -261,16 +257,13 @@ export function registerSCMCommands( (group as { resourceStates: unknown[] }).resourceStates.length === 0 ) return; - const uris = (group as { resourceStates: { resourceUri: vscode.Uri }[] }).resourceStates - .map((r) => r.resourceUri); + const states = groupResourceStates(group); + const uris = states.map((state) => state.resourceUri); const repo = findRepoForUri(manager, uris[0]); if (!repo) return; await withProgress(manager, "Staging all changes", async () => { - for (const uri of uris) { - const rel = uri.fsPath.slice(repo.root.length + 1); - await repo.stage([rel]); - } + await repo.stage(relativePaths(manager, repo, uris)); }); vscode.window.showInformationMessage("All changes staged"); }); @@ -285,16 +278,13 @@ export function registerSCMCommands( (group as { resourceStates: unknown[] }).resourceStates.length === 0 ) return; - const uris = (group as { resourceStates: { resourceUri: vscode.Uri }[] }).resourceStates - .map((r) => r.resourceUri); + const states = groupResourceStates(group); + const uris = states.map((state) => state.resourceUri); const repo = findRepoForUri(manager, uris[0]); if (!repo) return; await withProgress(manager, "Unstaging all changes", async () => { - for (const uri of uris) { - const rel = uri.fsPath.slice(repo.root.length + 1); - await repo.unstage([rel]); - } + await repo.unstage(relativePaths(manager, repo, uris)); }); vscode.window.showInformationMessage("All changes unstaged"); }); @@ -309,12 +299,14 @@ export function registerSCMCommands( (group as { resourceStates: unknown[] }).resourceStates.length === 0 ) return; - const uris = (group as { resourceStates: { resourceUri: vscode.Uri }[] }).resourceStates - .map((r) => r.resourceUri); + const states = groupResourceStates(group); + const uris = states.map((state) => state.resourceUri); const repo = findRepoForUri(manager, uris[0]); if (!repo) return; - const files = uris.map((u) => u.fsPath.slice(repo.root.length + 1)); + const discard = discardPathSets(manager, repo, states); + const files = [...discard.tracked, ...discard.untracked]; + if (files.length === 0) return; const confirmed = await confirmDestructiveAction({ operation: DestructiveOperations.DISCARD_ALL, message: `Discard all ${files.length} changes? This cannot be undone.`, @@ -325,7 +317,7 @@ export function registerSCMCommands( try { await withProgress(manager, `Discarding ${files.length} changes`, async () => { - await repo.discard(files, []); + await repo.discard(discard.tracked, discard.untracked); }); vscode.window.showInformationMessage(`Discarded all ${files.length} changes`); } catch (e) { @@ -334,6 +326,77 @@ export function registerSCMCommands( }); } -function findRepoForUri(manager: RepositoryManager, uri: vscode.Uri): any { - return manager.getAll().find((r) => uri.fsPath.startsWith(r.root)); +function findRepoForUri(manager: RepositoryManager, uri: vscode.Uri): Repository | undefined { + return manager.findByUri(uri); +} + +interface ScmResourceStateLike { + resourceUri: vscode.Uri; + vsgitGroup?: string; + vsgitChange?: FileChange; +} + +function resourceStates(args: unknown[]): ScmResourceStateLike[] { + const states = args.length === 1 && Array.isArray(args[0]) ? args[0] : args; + return states + .filter((r): r is ScmResourceStateLike => + typeof r === "object" && r !== null && "resourceUri" in r, + ); +} + +function groupResourceStates(group: unknown): ScmResourceStateLike[] { + if ( + typeof group !== "object" || + group === null || + !("resourceStates" in group) || + !Array.isArray((group as { resourceStates: unknown }).resourceStates) + ) { + return []; + } + return resourceStates((group as { resourceStates: unknown[] }).resourceStates); +} + +function relativePaths( + manager: RepositoryManager, + repo: Repository, + uris: vscode.Uri[], +): string[] { + return uris + .filter((uri) => manager.uriBelongsTo(repo, uri)) + .map((uri) => manager.relativePath(repo, uri)); +} + +function resourceGroup(resourceState: unknown): string | undefined { + if (typeof resourceState !== "object" || resourceState === null) { + return undefined; + } + return (resourceState as { vsgitGroup?: string }).vsgitGroup; +} + +function resourceChange(resourceState: unknown): FileChange | undefined { + if (typeof resourceState !== "object" || resourceState === null) { + return undefined; + } + return (resourceState as { vsgitChange?: FileChange }).vsgitChange; +} + +function discardPathSets( + manager: RepositoryManager, + repo: Repository, + states: ScmResourceStateLike[], +): { tracked: string[]; untracked: string[] } { + const tracked: string[] = []; + const untracked: string[] = []; + for (const state of states) { + if (!manager.uriBelongsTo(repo, state.resourceUri)) { + continue; + } + const rel = manager.relativePath(repo, state.resourceUri); + if (state.vsgitChange?.worktreeState === "untracked") { + untracked.push(rel); + } else { + tracked.push(rel); + } + } + return { tracked, untracked }; } diff --git a/src/commands/shared.ts b/src/commands/shared.ts index a9a05da..24d3f29 100644 --- a/src/commands/shared.ts +++ b/src/commands/shared.ts @@ -2,9 +2,13 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { VsgitNode } from "../views/RepositoriesProvider"; +import { GitCommandCancelled } from "../git/GitExecutor"; import { GitError } from "../git/GitError"; export function errMsg(err: unknown): string { + if (err instanceof GitCommandCancelled) { + return "Cancelled."; + } return err instanceof Error ? err.message : String(err); } @@ -14,6 +18,9 @@ export function errMsg(err: unknown): string { */ export function humanizeGitError(err: unknown): string { if (!(err instanceof GitError)) { + if (err instanceof GitCommandCancelled) { + return "Cancelled."; + } return errMsg(err); } const stderr = err.stderr ?? ""; @@ -41,14 +48,47 @@ export function humanizeGitError(err: unknown): string { return firstLine || errMsg(err); } -/** Resolve the repository for a node, or prompt when invoked from the palette. */ +/** + * Pull a `rootUri` out of an argument passed by VS Code's native Git SCM menus. + * `scm/sourceControl` items receive a `SourceControl` with a `.rootUri`; some + * native objects nest it under `.provider`. Returns undefined for shapes that + * carry no root (e.g. a `SourceControlResourceGroup`). + */ +function nativeScmRootUri(arg: unknown): vscode.Uri | undefined { + if (!arg || typeof arg !== "object") { + return undefined; + } + const candidate = + (arg as { rootUri?: unknown }).rootUri ?? + (arg as { provider?: { rootUri?: unknown } }).provider?.rootUri; + return candidate instanceof vscode.Uri ? candidate : undefined; +} + +/** + * Resolve the repository for a command invocation. + * + * Handles three argument sources: + * - VsGit tree nodes (carry a `.repo`). + * - VS Code native Git SCM menus (carry a `.rootUri`, mapped via the manager). + * - The command palette / native resource-group menus (no usable target): + * single repo, otherwise a quick-pick. + */ export async function resolveRepo( manager: RepositoryManager, - node?: VsgitNode, + node?: VsgitNode | unknown, ): Promise { - if (node && "repo" in node) { - return node.repo; + if (node && typeof node === "object" && "repo" in node) { + return (node as { repo: Repository }).repo; + } + + const rootUri = nativeScmRootUri(node); + if (rootUri) { + const byRoot = manager.get(rootUri.fsPath) ?? manager.findByUri(rootUri); + if (byRoot) { + return byRoot; + } } + const repos = manager.getAll(); if (repos.length === 0) { vscode.window.showWarningMessage("No Git repositories found."); @@ -57,6 +97,15 @@ export async function resolveRepo( if (repos.length === 1) { return repos[0]; } + + // No explicit target (native "Changes" group ellipsis or the palette): in a + // multi-repo workspace, prefer the repo for the active editor before asking. + const activeDoc = vscode.window.activeTextEditor?.document.uri; + const byActiveDoc = activeDoc ? manager.findByUri(activeDoc) : undefined; + if (byActiveDoc) { + return byActiveDoc; + } + const pick = await vscode.window.showQuickPick( repos.map((r) => ({ label: r.name, repo: r })), { placeHolder: "Select a repository" }, @@ -78,6 +127,10 @@ export async function withProgress( await manager.refreshAll(); return true; } catch (e) { + if (e instanceof GitCommandCancelled) { + await manager.refreshAll(); + return false; + } vscode.window.showErrorMessage(`${title} failed: ${humanizeGitError(e)}`); await manager.refreshAll(); return false; diff --git a/src/commands/staging.ts b/src/commands/staging.ts index 4c393d3..6086494 100644 --- a/src/commands/staging.ts +++ b/src/commands/staging.ts @@ -6,6 +6,7 @@ import { StagingNode, StagingProvider } from "../views/StagingProvider"; import { GitContentProvider } from "../git/GitContentProvider"; import { FileChange } from "../git/parsers/status"; import { parseUnifiedDiff, buildHunkPatch } from "../git/parsers/diff"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** * Staging view commands: stage/unstage (file + all + hunk), discard, open diff, @@ -86,12 +87,12 @@ export function registerStagingCommands( if (n?.type !== "file") { return; } - const confirm = await vscode.window.showWarningMessage( - `Discard changes in ${n.change.path}? This cannot be undone.`, - { modal: true }, - "Discard", - ); - if (confirm !== "Discard") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `Discard changes in ${n.change.path}? This cannot be undone.`, + items: [n.change.path], + }); + if (!confirmed) { return; } const isUntracked = n.change.worktreeState === "untracked"; diff --git a/src/commands/stash.ts b/src/commands/stash.ts index e9962f4..df01ebb 100644 --- a/src/commands/stash.ts +++ b/src/commands/stash.ts @@ -4,6 +4,7 @@ import { RepositoryManager } from "../git/RepositoryManager"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, withProgress } from "./shared"; import { GitContentProvider } from "../git/GitContentProvider"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Stash operations: create, apply, pop, drop, view contents. */ export function registerStashCommands( @@ -44,12 +45,11 @@ export function registerStashCommands( if (!n || n.type !== "stash") { return; } - const confirm = await vscode.window.showWarningMessage( - `Drop ${n.ref}? This cannot be undone.`, - { modal: true }, - "Drop", - ); - if (confirm !== "Drop") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `Drop ${n.ref}? This cannot be undone.`, + }); + if (!confirmed) { return; } await withProgress(manager, `Drop ${n.ref}`, () => n.repo.stashDrop(n.ref)); @@ -97,12 +97,11 @@ export function registerStashCommands( return; } const count = repo.stashes.length; - const confirm = await vscode.window.showWarningMessage( - `Drop all ${count} stash ${count === 1 ? "entry" : "entries"}? This cannot be undone.`, - { modal: true }, - "Clear All", - ); - if (confirm !== "Clear All") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_ALL, + message: `Drop all ${count} stash ${count === 1 ? "entry" : "entries"}? This cannot be undone.`, + }); + if (!confirmed) { return; } await withProgress(manager, "Clear all stashes", () => repo.stashClear()); diff --git a/src/commands/subtree.ts b/src/commands/subtree.ts index 9d63a36..81b50c9 100644 --- a/src/commands/subtree.ts +++ b/src/commands/subtree.ts @@ -37,10 +37,10 @@ export function registerSubtreeCommands( await withProgress( manager, `Adding subtree from ${repository}`, - () => repo.subtreeAdd(prefix, repository, ref || undefined) + () => repo.subtreeAdd(prefix, repository, ref || undefined), ); vscode.window.showInformationMessage( - `Subtree added at ${prefix} from ${repository}.` + `Subtree added at ${prefix} from ${repository}.`, ); } catch (e) { vscode.window.showErrorMessage(`Failed to add subtree: ${errMsg(e)}`); @@ -73,10 +73,10 @@ export function registerSubtreeCommands( await withProgress( manager, `Pulling subtree updates from ${repository}`, - () => repo.subtreePull(prefix, repository, ref || undefined) + () => repo.subtreePull(prefix, repository, ref || undefined), ); vscode.window.showInformationMessage( - `Subtree ${prefix} updated from ${repository}.` + `Subtree ${prefix} updated from ${repository}.`, ); } catch (e) { vscode.window.showErrorMessage(`Failed to pull subtree: ${errMsg(e)}`); @@ -108,7 +108,7 @@ export function registerSubtreeCommands( const confirm = await vscode.window.showWarningMessage( `Push subtree changes from ${prefix} to ${repository}?`, { modal: true }, - "Push" + "Push", ); if (confirm !== "Push") return; @@ -116,10 +116,10 @@ export function registerSubtreeCommands( await withProgress( manager, `Pushing subtree to ${repository}`, - () => repo.subtreePush(prefix, repository, ref || undefined) + () => repo.subtreePush(prefix, repository, ref || undefined), ); vscode.window.showInformationMessage( - `Subtree ${prefix} pushed to ${repository}.` + `Subtree ${prefix} pushed to ${repository}.`, ); } catch (e) { vscode.window.showErrorMessage(`Failed to push subtree: ${errMsg(e)}`); diff --git a/src/commands/tag.ts b/src/commands/tag.ts index c26b284..6c402de 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -4,6 +4,7 @@ import { Repository } from "../git/Repository"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, withProgress } from "./shared"; import { showCreateTagDialog } from "../webviews/CreateTagDialog"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Tag operations: create (lightweight/annotated/signed), delete, checkout, push. */ export function registerTagCommands( @@ -29,12 +30,11 @@ export function registerTagCommands( const message = request.message?.trim() || undefined; const exists = repo.tags.some((t) => t.shortName === name); if (exists && !request.force) { - const choice = await vscode.window.showWarningMessage( - `Tag '${name}' already exists. Force re-tag it to point at HEAD?`, - { modal: true }, - "Force Re-tag", - ); - if (choice !== "Force Re-tag") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_TAG, + message: `Tag '${name}' already exists. Force re-tag it to point at HEAD?`, + }); + if (!confirmed) { return; } } @@ -66,12 +66,11 @@ export function registerTagCommands( if (!n || n.type !== "tag") { return; } - const confirm = await vscode.window.showWarningMessage( - `Delete tag ${n.ref.shortName}?`, - { modal: true }, - "Delete", - ); - if (confirm !== "Delete") { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_TAG, + message: `Delete tag ${n.ref.shortName}?`, + }); + if (!confirmed) { return; } await withProgress(manager, `Delete tag ${n.ref.shortName}`, () => @@ -116,12 +115,11 @@ export function registerTagCommands( validateInput: (v) => (v.trim() === "" ? "Required" : undefined), }); if (!sha) return; - const confirm = await vscode.window.showWarningMessage( - `Force replace tag '${existing}' pointing to '${sha.trim()}'?`, - { modal: true }, - "Force Replace", - ); - if (confirm !== "Force Replace") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_TAG, + message: `Force replace tag '${existing}' pointing to '${sha.trim()}'?`, + }); + if (!confirmed) return; await withProgress(manager, `Force re-tag ${existing}`, () => n.repo.createTagAt(existing, sha.trim(), undefined, false, true), ); @@ -134,12 +132,11 @@ export function registerTagCommands( } const remote = await pickRemote(n.repo); if (!remote) return; - const confirm = await vscode.window.showWarningMessage( - `Delete remote tag '${n.ref.shortName}' from '${remote}'? This cannot be undone.`, - { modal: true }, - "Delete", - ); - if (confirm !== "Delete") return; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DELETE_TAG, + message: `Delete remote tag '${n.ref.shortName}' from '${remote}'? This cannot be undone.`, + }); + if (!confirmed) return; await withProgress(manager, `Delete remote tag ${n.ref.shortName}`, () => n.repo.deleteRemoteTag(remote, n.ref.shortName), ); diff --git a/src/commands/transport.ts b/src/commands/transport.ts index 5cd4546..e24c61f 100644 --- a/src/commands/transport.ts +++ b/src/commands/transport.ts @@ -4,6 +4,7 @@ import { Repository } from "../git/Repository"; import { VsgitNode } from "../views/RepositoriesProvider"; import { Credentials } from "../util/credentials"; import { resolveRepo, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; /** Fetch / Pull / Push (with a push dialog) plus merge and rebase. */ export function registerTransportCommands( @@ -40,15 +41,24 @@ export function registerTransportCommands( if (!repo) { return; } - const mode = await vscode.window.showQuickPick(["Merge", "Rebase"], { + const defaultMode = vscode.workspace + .getConfiguration("vsgit") + .get("defaultPullMode", "merge"); + const mode = await vscode.window.showQuickPick( + [ + { label: "Merge", picked: defaultMode !== "rebase" }, + { label: "Rebase", picked: defaultMode === "rebase" }, + ], + { placeHolder: "Pull strategy", - }); + }, + ); if (!mode) { return; } await withProgress(manager, "Pull", () => creds.withAskpass((env) => - repo.pull({ rebase: mode === "Rebase", env }), + repo.pull({ rebase: mode.label === "Rebase", env }), ), ); }); @@ -160,6 +170,17 @@ async function pushDialog( opts.tags = selected.has("tags"); opts.forceWithLease = selected.has("forceWithLease"); opts.force = selected.has("force"); + if (opts.forceWithLease || opts.force) { + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.FORCE_PUSH, + message: opts.forceWithLease + ? `Force push with lease to ${remote}?` + : `Force push to ${remote}? This can overwrite remote history.`, + }); + if (!confirmed) { + return; + } + } await withProgress(manager, `Push to ${remote}`, () => creds.withAskpass((env) => repo.push({ ...opts, env })), ); diff --git a/src/commands/worktree.ts b/src/commands/worktree.ts index 090618e..bcb9b95 100644 --- a/src/commands/worktree.ts +++ b/src/commands/worktree.ts @@ -3,8 +3,9 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { WorktreeInfo } from "../git/Repository"; import { errMsg, withProgress } from "./shared"; +import { confirmDestructiveAction, DestructiveOperations } from "../util/confirmation"; -type WorktreeNode = +export type WorktreeNode = | { type: "worktreeRoot"; repo_root: string; manager: RepositoryManager } | { type: "worktree"; info: WorktreeInfo; repo_root: string; manager: RepositoryManager }; @@ -106,14 +107,16 @@ export function registerWorktreeCommands( reg("vsgit.worktree.remove", async (node: unknown) => { const n = node as WorktreeNode | undefined; if (!n || n.type !== "worktree") return; - const confirm = await vscode.window.showWarningMessage( - `Remove worktree at ${n.info.path}?`, - { modal: true }, - "Remove", - "Force Remove", - ); - if (!confirm) return; - const force = confirm === "Force Remove"; + const forcePick = await vscode.window.showQuickPick(["Remove", "Force Remove"], { + placeHolder: `Remove worktree at ${n.info.path}`, + }); + if (!forcePick) return; + const force = forcePick === "Force Remove"; + const confirmed = await confirmDestructiveAction({ + operation: DestructiveOperations.DISCARD_CHANGES, + message: `${forcePick} worktree at ${n.info.path}?`, + }); + if (!confirmed) return; const repo = n.manager.getAll().find((r) => r.root === n.repo_root); if (!repo) return; try { diff --git a/src/decorations/BlameController.ts b/src/decorations/BlameController.ts index 0ee4a57..b3e6ebd 100644 --- a/src/decorations/BlameController.ts +++ b/src/decorations/BlameController.ts @@ -59,13 +59,11 @@ export class BlameController implements vscode.Disposable { if (this.cache.has(fsPath)) { return; } - const repo = this.manager - .getAll() - .find((r) => fsPath.startsWith(r.root + "/") || fsPath.startsWith(r.root)); + const repo = this.manager.findByUri(editor.document.uri); if (!repo) { return; } - const rel = fsPath.slice(repo.root.length + 1); + const rel = this.manager.relativePath(repo, editor.document.uri); try { this.cache.set(fsPath, await repo.blame(rel)); } catch { diff --git a/src/extension.ts b/src/extension.ts index 232bbcc..cb5473c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,3 @@ -/** - * Extension entry point: constructs the RepositoryManager, wires up every - * tree/webview view provider and command group, and registers them with - * VS Code's extension context for disposal on deactivation. - */ import * as vscode from "vscode"; import { RepositoryManager } from "./git/RepositoryManager"; import { RepositoriesProvider } from "./views/RepositoriesProvider"; @@ -20,7 +15,6 @@ import { ReflogProvider } from "./views/ReflogProvider"; import { registerReflogCommands } from "./commands/reflog"; import { VsgitFileDecorationProvider } from "./decorations/FileDecorations"; import { BlameController } from "./decorations/BlameController"; -import { VsgitQuickDiffProvider } from "./git/QuickDiffProvider"; import { registerCompareCommands } from "./commands/compare"; import { registerBlameCommands } from "./commands/blame"; import { SynchronizeProvider } from "./views/SynchronizeProvider"; @@ -52,6 +46,8 @@ import { GitWatcherService } from "./services/GitWatcherService"; import { GraphStatusBarService } from "./services/GraphStatusBarService"; import { registerAutoFetchCommands } from "./commands/autoFetch"; import { registerCommitOpsCommands } from "./commands/commitOps"; +import { VsgitScmProvider } from "./views/ScmProvider"; +import { DocumentationProvider } from "./webviews/documentation/DocumentationProvider"; export async function activate( context: vscode.ExtensionContext, @@ -75,6 +71,10 @@ export async function activate( if (e.affectsConfiguration("vsgit.showAdvancedViews")) { syncAdvancedViewsContext(); } + if (e.affectsConfiguration("vsgit.git.path")) { + manager.updateGitPathFromConfiguration(); + void manager.scan(); + } }), ); @@ -82,7 +82,7 @@ export async function activate( context.subscriptions.push( vscode.workspace.registerTextDocumentContentProvider( VSGIT_SCHEME, - new GitContentProvider(), + new GitContentProvider(manager.getGitExecutor()), ), ); @@ -97,6 +97,14 @@ export async function activate( }), ); + context.subscriptions.push( + vscode.commands.registerCommand("vsgit.repositories.setActive", (node) => { + if (node && typeof node === "object" && "repo" in node) { + manager.setActive((node as { repo: { root: string } }).repo.root); + } + }), + ); + // "Git Repositories": the full tree — each repo expands into Local Branches, // Remote Branches, Tags, Remotes, Stashes, and Submodules. const repositoriesProvider = new RepositoriesProvider(manager); @@ -124,12 +132,30 @@ export async function activate( stagingProvider, ); context.subscriptions.push( + commitProvider, vscode.window.registerWebviewViewProvider( CommitViewProvider.viewType, commitProvider, ), ); + // Searchable reference library: always available as the bottom sidebar view, + // with a command that opens the same content in a full editor panel. + const documentationProvider = new DocumentationProvider( + context.extensionUri, + context.extension.packageJSON, + ); + context.subscriptions.push( + documentationProvider, + vscode.window.registerWebviewViewProvider( + DocumentationProvider.viewType, + documentationProvider, + ), + vscode.commands.registerCommand("vsgit.documentation.open", () => + documentationProvider.openPanel(), + ), + ); + const reflogProvider = new ReflogProvider(manager); context.subscriptions.push( reflogProvider, @@ -163,10 +189,10 @@ export async function activate( registerReflogCommands(context, manager, reflogProvider); // Decorations, blame, quick-diff, compare/conflicts. + const fileDecorationProvider = new VsgitFileDecorationProvider(manager); context.subscriptions.push( - vscode.window.registerFileDecorationProvider( - new VsgitFileDecorationProvider(manager), - ), + fileDecorationProvider, + vscode.window.registerFileDecorationProvider(fileDecorationProvider), ); const blameController = new BlameController(manager); context.subscriptions.push(blameController); @@ -188,10 +214,7 @@ export async function activate( registerCompareCommands(context, manager, compareProvider); - const quickDiff = new VsgitQuickDiffProvider(manager); - const scm = vscode.scm.createSourceControl("vsgit", "VsGit"); - scm.quickDiffProvider = quickDiff; - context.subscriptions.push(scm); + context.subscriptions.push(new VsgitScmProvider(manager)); registerSyncCommands(context, syncProvider); registerConfigCommands(context, manager); diff --git a/src/git/GitContentProvider.ts b/src/git/GitContentProvider.ts index 03fee5b..910ebcd 100644 --- a/src/git/GitContentProvider.ts +++ b/src/git/GitContentProvider.ts @@ -3,28 +3,32 @@ import { GitExecutor } from "./GitExecutor"; import { isOptionLike } from "./argGuard"; export const VSGIT_SCHEME = "vsgit"; +export const VSGIT_EMPTY_REF = "~empty"; /** * Serves file contents at a given git revision (or the index) so VS Code's * native diff editor can render side-by-side comparisons. * - * URI shape: vsgit:?repo=&ref=&path= + * URI shape: vsgit:?repo=&ref=&path= * where ref is "~index" for the staged copy, or any revision (HEAD, sha, ...). */ export class GitContentProvider implements vscode.TextDocumentContentProvider { - private readonly git = new GitExecutor(); + constructor(private readonly git: GitExecutor) {} async provideTextDocumentContent(uri: vscode.Uri): Promise { const params = new URLSearchParams(uri.query); const repo = params.get("repo") ?? ""; const ref = params.get("ref") ?? "HEAD"; const relPath = params.get("path") ?? ""; + if (ref === VSGIT_EMPTY_REF) { + return ""; + } // `git show ` takes no `--` separator for an object spec, so guard // against a ref or path that git would otherwise parse as an option. if (isOptionLike(relPath) || (ref !== "~index" && isOptionLike(ref))) { return ""; } - const spec = ref === "~index" ? `:${relPath}` : `${ref}:${relPath}`; + const spec = ref === "~index" || ref === "" ? `:${relPath}` : `${ref}:${relPath}`; try { return await this.git.stdout(["show", spec], { cwd: repo }); } catch { @@ -40,8 +44,10 @@ export class GitContentProvider implements vscode.TextDocumentContentProvider { absPath: string, ): vscode.Uri { const query = new URLSearchParams({ repo: repoRoot, ref, path: relPath }); - return vscode.Uri.parse( - `${VSGIT_SCHEME}:${absPath}?${query.toString()}`, - ); + return vscode.Uri.from({ + scheme: VSGIT_SCHEME, + path: absPath, + query: query.toString(), + }); } } diff --git a/src/git/GitExecutor.test.ts b/src/git/GitExecutor.test.ts deleted file mode 100644 index 2c3d20e..0000000 --- a/src/git/GitExecutor.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert"; -import * as os from "node:os"; -import * as fs from "node:fs"; -import * as path from "node:path"; -import { GitExecutor } from "./GitExecutor"; -import { GitError } from "./GitError"; - -function tmpDir(): string { - return fs.mkdtempSync(path.join(os.tmpdir(), "vsgit-executor-test-")); -} - -test("run() returns stdout/stderr/exitCode on success", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - const result = await git.run(["--version"], { cwd }); - assert.strictEqual(result.exitCode, 0); - assert.match(result.stdout, /git version/); - assert.strictEqual(result.stderr, ""); -}); - -test("run() throws GitError on non-zero exit", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - await assert.rejects( - () => git.run(["not-a-real-subcommand"], { cwd }), - (err: unknown) => { - assert.ok(err instanceof GitError); - assert.notStrictEqual(err.exitCode, 0); - return true; - }, - ); -}); - -test("run() treats okCodes as success", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - fs.writeFileSync(path.join(cwd, "a.txt"), "a"); - await git.run(["init"], { cwd }); - // `git diff --no-index` exits 1 when files differ; allow it via okCodes. - const other = tmpDir(); - fs.writeFileSync(path.join(other, "b.txt"), "b"); - const result = await git.run( - ["diff", "--no-index", path.join(cwd, "a.txt"), path.join(other, "b.txt")], - { cwd, okCodes: [1] }, - ); - assert.strictEqual(result.exitCode, 1); -}); - -test("run() writes stdin to the process", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - const result = await git.run(["hash-object", "--stdin"], { cwd, stdin: "hello\n" }); - assert.strictEqual(result.exitCode, 0); - assert.match(result.stdout.trim(), /^[0-9a-f]{40}$/); -}); - -test("run() rejects with GitError after timeoutMs elapses", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - await assert.rejects( - () => git.run(["log"], { cwd, timeoutMs: 1 }), - (err: unknown) => { - assert.ok(err instanceof GitError); - assert.match(err.message, /timed out/); - return true; - }, - ); -}); - -test("stdout() returns only the stdout string", async () => { - const git = new GitExecutor(); - const cwd = tmpDir(); - const out = await git.stdout(["--version"], { cwd }); - assert.match(out, /git version/); -}); diff --git a/src/git/GitExecutor.ts b/src/git/GitExecutor.ts index de1d11c..4483a56 100644 --- a/src/git/GitExecutor.ts +++ b/src/git/GitExecutor.ts @@ -1,6 +1,12 @@ import { spawn } from "node:child_process"; import { GitError } from "./GitError"; +export type GitCommandPreview = ( + args: string[], + cwd: string, + gitPath: string, +) => Promise; + export interface GitRunOptions { /** Working directory the git command runs in. */ cwd: string; @@ -20,16 +26,36 @@ export interface GitResult { exitCode: number; } +export class GitCommandCancelled extends Error { + constructor(readonly args: string[]) { + super("Git command cancelled by user."); + this.name = "GitCommandCancelled"; + } +} + /** * Thin wrapper around spawning the `git` binary. Every higher-level operation * funnels through here so we have a single place for env, error handling and * stdin plumbing. Output is parsed by callers using machine-readable formats. */ export class GitExecutor { - constructor(private readonly gitPath: string = "git") {} + constructor( + private gitPath: string = "git", + private readonly preview?: GitCommandPreview, + ) {} + + setGitPath(gitPath: string): void { + this.gitPath = gitPath || "git"; + } /** Run git, throwing GitError on unexpected non-zero exit. */ async run(args: string[], options: GitRunOptions): Promise { + if (this.preview) { + const shouldRun = await this.preview(args, options.cwd, this.gitPath); + if (!shouldRun) { + throw new GitCommandCancelled(args); + } + } const result = await this.exec(args, options); const ok = result.exitCode === 0 || (options.okCodes ?? []).includes(result.exitCode); if (!ok) { diff --git a/src/git/QuickDiffProvider.ts b/src/git/QuickDiffProvider.ts index e9f6e5a..0e310ee 100644 --- a/src/git/QuickDiffProvider.ts +++ b/src/git/QuickDiffProvider.ts @@ -8,19 +8,23 @@ import { GitContentProvider } from "./GitContentProvider"; * Registered as an SCM quick-diff provider. */ export class VsgitQuickDiffProvider implements vscode.QuickDiffProvider { - constructor(private readonly manager: RepositoryManager) {} + constructor( + private readonly manager: RepositoryManager, + private readonly root?: string, + ) {} provideOriginalResource(uri: vscode.Uri): vscode.Uri | undefined { if (uri.scheme !== "file") { return undefined; } - const repo = this.manager - .getAll() - .find((r) => uri.fsPath.startsWith(r.root)); + const repo = this.manager.findByUri(uri); if (!repo) { return undefined; } - const rel = uri.fsPath.slice(repo.root.length + 1); + if (this.root && repo.root !== this.root) { + return undefined; + } + const rel = this.manager.relativePath(repo, uri); // Compare against the index so staged changes don't show as gutter diff. return GitContentProvider.uri(repo.root, rel, "~index", uri.fsPath); } diff --git a/src/git/Repository.ts b/src/git/Repository.ts index f2259d4..3ed65bc 100644 --- a/src/git/Repository.ts +++ b/src/git/Repository.ts @@ -1,10 +1,3 @@ -/** - * Repository wraps a single git working copy: it builds argv for every git - * subcommand we support (status, log, diff, branch, remote, stash, worktree, - * submodule, blame, reflog, config, etc.), runs it through GitExecutor, and - * parses the output into typed results. All ref/SHA/URL parameters are routed - * through argGuard so hostile input can't be smuggled into git's argv. - */ import * as path from "node:path"; import { GitExecutor } from "./GitExecutor"; import { safeRef, safeRemoteUrl } from "./argGuard"; @@ -36,6 +29,10 @@ export interface StashInfo { index: number; /** Ref name, e.g. stash@{0}. */ ref: string; + /** Commit object for the stash entry itself. */ + objectId?: string; + /** First parent of the stash commit: the commit the stash was based on. */ + baseObjectId?: string; message: string; } @@ -58,6 +55,8 @@ export class Repository { stashes: StashInfo[] = []; submodules: SubmoduleInfo[] = []; headName: string | undefined; + private submodulesLoaded = false; + private submodulesInFlight: Promise | undefined; constructor( readonly root: string, @@ -1036,10 +1035,7 @@ export class Repository { } } - async reset( - sha: string, - mode: "soft" | "mixed" | "hard" | "keep" | "merge", - ): Promise { + async reset(sha: string, mode: "soft" | "mixed" | "hard" | "keep" | "merge"): Promise { await this.git.run(["reset", `--${mode}`, safeRef(sha, "commit")], { cwd: this.root }); } @@ -1261,14 +1257,36 @@ export class Repository { } async refresh(): Promise { - await Promise.all([ + const tasks = [ this.refreshRefs(), this.refreshStatus(), this.refreshRemotes(), this.refreshStashes(), - this.refreshSubmodules(), this.refreshHead(), - ]); + ]; + // Submodule enumeration can recurse into nested repositories and is not + // needed for normal staging/commit workflows. Refresh it only after a view + // has requested submodule data at least once. + if (this.submodulesLoaded) { + tasks.push(this.refreshSubmodules()); + } + await Promise.all(tasks); + } + + async ensureSubmodules(): Promise { + if (this.submodulesLoaded) { + return; + } + if (!this.submodulesInFlight) { + this.submodulesInFlight = this.refreshSubmodules() + .then(() => { + this.submodulesLoaded = true; + }) + .finally(() => { + this.submodulesInFlight = undefined; + }); + } + return this.submodulesInFlight; } private async refreshRefs(): Promise { @@ -1313,7 +1331,7 @@ export class Repository { private async refreshStashes(): Promise { const out = await this.git.stdout( - ["stash", "list", "--format=%gd\x1f%gs"], + ["stash", "list", "--format=%gd\x1f%H\x1f%P\x1f%gs"], { cwd: this.root }, ); const stashes: StashInfo[] = []; @@ -1321,10 +1339,13 @@ export class Repository { if (line.trim() === "") { continue; } - const [ref, message] = line.split("\x1f"); + const [ref, objectId, parents, message] = line.split("\x1f"); + const baseObjectId = parents?.split(/\s+/).filter(Boolean)[0]; const m = /stash@\{(\d+)\}/.exec(ref); stashes.push({ ref, + objectId: objectId || undefined, + baseObjectId, message: message ?? "", index: m ? Number(m[1]) : stashes.length, }); diff --git a/src/git/RepositoryManager.ts b/src/git/RepositoryManager.ts index d35f5c6..6fbdf5b 100644 --- a/src/git/RepositoryManager.ts +++ b/src/git/RepositoryManager.ts @@ -3,19 +3,24 @@ import * as path from "node:path"; import * as vscode from "vscode"; import { GitExecutor } from "./GitExecutor"; import { Repository } from "./Repository"; +import { shouldRunGitCommand } from "../util/commandPreview"; /** * Discovers git repositories across the workspace, keeps a registry of - * Repository models, and watches each repo's .git directory so the views - * refresh after both in-extension and external git operations. + * Repository models, and watches each repo's Git administrative directories so + * the views refresh after both in-extension and external git operations. */ export class RepositoryManager implements vscode.Disposable { /** Top-level `.git` entries whose changes are pure noise (no state change). */ private static readonly IGNORED_GIT_FILES = /^(index|.*\.lock)$/i; private readonly repositories = new Map(); - private readonly watchers = new Map(); - private readonly git = new GitExecutor(); + private readonly watchers = new Map(); + private readonly git = new GitExecutor(configuredGitPath(), shouldRunGitCommand); + private activeRoot: string | undefined; + private scanInFlight: Promise | undefined; + private lastScanDurationMs = 0; + private lastScanFolderCount = 0; private readonly _onDidChange = new vscode.EventEmitter(); /** Fires whenever the set of repos or any repo's state may have changed. */ @@ -33,21 +38,99 @@ export class RepositoryManager implements vscode.Disposable { return this.repositories.get(root); } - /** Scan workspace folders for repositories and refresh everything. */ - async scan(): Promise { + getGitExecutor(): GitExecutor { + return this.git; + } + + getActive(): Repository | undefined { + if (this.activeRoot) { + const active = this.repositories.get(this.activeRoot); + if (active) { + return active; + } + } + return this.firstRepository(); + } + + setActive(root: string): void { + if (!this.repositories.has(root) || this.activeRoot === root) { + return; + } + this.activeRoot = root; + this._onDidChange.fire(); + } + + findByUri(uri: vscode.Uri): Repository | undefined { + if (uri.scheme !== "file") { + return undefined; + } + const target = path.resolve(uri.fsPath); + return this.getAll() + .filter((repo) => containsPath(repo.root, target)) + .sort((a, b) => b.root.length - a.root.length)[0]; + } + + relativePath(repo: Repository, uri: vscode.Uri): string { + return toGitPath(path.relative(repo.root, uri.fsPath)); + } + + uriBelongsTo(repo: Repository, uri: vscode.Uri): boolean { + return uri.scheme === "file" && containsPath(repo.root, uri.fsPath); + } + + updateGitPathFromConfiguration(): void { + this.git.setGitPath(configuredGitPath()); + } + + /** + * Scan workspace folders for repositories and refresh everything. + * + * Workspace roots are discovered concurrently. Repeated activation/config + * events share the same in-flight scan instead of spawning duplicate git + * processes against every folder. + */ + scan(): Promise { + if (this.scanInFlight) { + return this.scanInFlight; + } + this.scanInFlight = this.performScan().finally(() => { + this.scanInFlight = undefined; + }); + return this.scanInFlight; + } + + getPerformanceSnapshot(): { + lastScanDurationMs: number; + workspaceFolderCount: number; + repositoryCount: number; + } { + return { + lastScanDurationMs: this.lastScanDurationMs, + workspaceFolderCount: this.lastScanFolderCount, + repositoryCount: this.repositories.size, + }; + } + + private async performScan(): Promise { + const startedAt = performance.now(); const folders = vscode.workspace.workspaceFolders ?? []; const found = new Set(); + this.lastScanFolderCount = folders.length; - for (const folder of folders) { - const root = await this.discoverRoot(folder.uri.fsPath); + const discoveredRoots = await Promise.all( + folders.map((folder) => this.discoverRoot(folder.uri.fsPath)), + ); + const newRoots: string[] = []; + for (const root of discoveredRoots) { if (root) { found.add(root); if (!this.repositories.has(root)) { this.repositories.set(root, new Repository(root, this.git)); - this.watch(root); + newRoots.push(root); } } } + await Promise.all(newRoots.map((root) => this.watch(root))); // Drop repositories no longer present. for (const root of [...this.repositories.keys()]) { @@ -57,7 +140,12 @@ export class RepositoryManager implements vscode.Disposable { } } + if (!this.activeRoot || !this.repositories.has(this.activeRoot)) { + this.activeRoot = this.firstRepository()?.root; + } + await this.refreshAll(); + this.lastScanDurationMs = performance.now() - startedAt; } async refreshAll(): Promise { @@ -84,37 +172,24 @@ export class RepositoryManager implements vscode.Disposable { } } - private watch(root: string): void { - const gitDir = path.join(root, ".git"); - try { - const watcher = fs.watch( - gitDir, - { recursive: false }, - (_event, filename) => { - // Ignore index/lock churn. `git status` (run on every refresh) - // opportunistically rewrites `.git/index`, and git briefly creates - // `*.lock` files; reacting to those would bounce straight back into - // another refresh, producing a continuous refresh loop ("twitching"). - const name = - typeof filename === "string" - ? filename - : filename - ? Buffer.from(filename).toString() - : ""; - if (name && RepositoryManager.IGNORED_GIT_FILES.test(name)) { - return; - } - this.scheduleRefresh(); - }, - ); - this.watchers.set(root, watcher); - } catch (err) { - console.error(`vsgit: cannot watch ${gitDir}`, err); + private async watch(root: string): Promise { + const watchPaths = await this.gitWatchPaths(root); + const watchers: fs.FSWatcher[] = []; + for (const watchPath of watchPaths) { + const watcher = this.watchGitPath(root, watchPath); + if (watcher) { + watchers.push(watcher); + } + } + if (watchers.length > 0) { + this.watchers.set(root, watchers); } } private unwatch(root: string): void { - this.watchers.get(root)?.close(); + for (const watcher of this.watchers.get(root) ?? []) { + watcher.close(); + } this.watchers.delete(root); } @@ -139,8 +214,10 @@ export class RepositoryManager implements vscode.Disposable { } dispose(): void { - for (const watcher of this.watchers.values()) { - watcher.close(); + for (const watchers of this.watchers.values()) { + for (const watcher of watchers) { + watcher.close(); + } } this.watchers.clear(); this._onDidChange.dispose(); @@ -148,4 +225,90 @@ export class RepositoryManager implements vscode.Disposable { clearTimeout(this.refreshTimer); } } + + private firstRepository(): Repository | undefined { + const [first] = this.getAll(); + return first; + } + + private async gitWatchPaths(root: string): Promise { + const gitDir = await this.git + .stdout(["rev-parse", "--absolute-git-dir"], { cwd: root }) + .then((out) => out.trim()) + .catch(() => path.join(root, ".git")); + const commonDir = await this.git + .stdout(["rev-parse", "--git-common-dir"], { cwd: root }) + .then((out) => resolveGitPath(root, out.trim())) + .catch(() => gitDir); + + return uniqueExistingPaths([ + gitDir, + commonDir, + path.join(commonDir, "refs"), + path.join(commonDir, "packed-refs"), + ]); + } + + private watchGitPath(root: string, watchPath: string): fs.FSWatcher | undefined { + const onChange = (_event: string, filename: string | Buffer | null) => { + // Ignore index/lock churn. `git status` (run on every refresh) + // opportunistically rewrites `.git/index`, and git briefly creates + // `*.lock` files; reacting to those would bounce straight back into + // another refresh, producing a continuous refresh loop ("twitching"). + const name = + typeof filename === "string" + ? filename + : filename + ? Buffer.from(filename).toString() + : ""; + if (name && RepositoryManager.IGNORED_GIT_FILES.test(path.basename(name))) { + return; + } + this.scheduleRefresh(); + }; + try { + return fs.watch(watchPath, { recursive: true }, onChange); + } catch { + try { + return fs.watch(watchPath, { recursive: false }, onChange); + } catch (err) { + console.error(`vsgit: cannot watch ${watchPath} for ${root}`, err); + return undefined; + } + } + } +} + +function configuredGitPath(): string { + return vscode.workspace + .getConfiguration("vsgit") + .get("git.path", "") + .trim() || "git"; +} + +function containsPath(root: string, candidate: string): boolean { + const rel = path.relative(path.resolve(root), path.resolve(candidate)); + return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel)); +} + +function toGitPath(value: string): string { + return value.split(path.sep).join("/"); +} + +function resolveGitPath(root: string, value: string): string { + if (value === "") { + return path.join(root, ".git"); + } + return path.isAbsolute(value) ? value : path.resolve(root, value); +} + +function uniqueExistingPaths(values: string[]): string[] { + const seen = new Set(); + return values.filter((value) => { + if (seen.has(value) || !fs.existsSync(value)) { + return false; + } + seen.add(value); + return true; + }); } diff --git a/src/services/AutoFetchService.ts b/src/services/AutoFetchService.ts index 9b02ea5..24651ac 100644 --- a/src/services/AutoFetchService.ts +++ b/src/services/AutoFetchService.ts @@ -1,8 +1,3 @@ -/** - * Background service that periodically fetches all remotes across known - * repositories and reflects status (last fetch time, behind count) in a - * status bar item. Restarts its timer when the autoFetch configuration changes. - */ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Credentials } from "../util/credentials"; diff --git a/src/test/extension.integration.test.ts b/src/test/extension.integration.test.ts new file mode 100644 index 0000000..e336dd4 --- /dev/null +++ b/src/test/extension.integration.test.ts @@ -0,0 +1,45 @@ +import * as assert from "node:assert"; +import * as vscode from "vscode"; + +const extensionId = "framedparadox.vsgit-vscode"; +let extension: vscode.Extension; + +suite("VsGit Extension Host", () => { + suiteSetup(async () => { + const candidate = vscode.extensions.getExtension(extensionId); + assert.ok(candidate, `${extensionId} is installed in the test host`); + extension = candidate; + await extension.activate(); + }); + + suiteTeardown(async () => { + await vscode.commands.executeCommand("workbench.action.closeAllEditors"); + }); + + test("activates successfully in a real Extension Host", () => { + assert.strictEqual(extension.isActive, true); + }); + + test("registers every contributed command", async () => { + const contributed = ( + extension.packageJSON.contributes?.commands as + | Array<{ command: string }> + | undefined + )?.map((entry) => entry.command) ?? []; + const registered = new Set(await vscode.commands.getCommands(true)); + const missing = contributed.filter((command) => !registered.has(command)); + + assert.ok(contributed.length > 150, "expected the full VsGit command surface"); + assert.deepStrictEqual(missing, []); + }); + + test("refreshes repository discovery without an activation error", async () => { + await vscode.commands.executeCommand("vsgit.repositories.refresh"); + }); + + test("opens the full documentation webview", async () => { + await vscode.commands.executeCommand("vsgit.documentation.open"); + await new Promise((resolve) => setTimeout(resolve, 100)); + await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + }); +}); diff --git a/src/util/commandPreview.ts b/src/util/commandPreview.ts new file mode 100644 index 0000000..fed4a59 --- /dev/null +++ b/src/util/commandPreview.ts @@ -0,0 +1,85 @@ +import * as vscode from "vscode"; + +export async function shouldRunGitCommand( + args: string[], + cwd: string, + gitPath = "git", +): Promise { + const enabled = vscode.workspace + .getConfiguration("vsgit") + .get("showCommandPreview", false); + if (!enabled) { + return true; + } + if (isReadOnlyGitCommand(args)) { + return true; + } + + const command = [gitPath || "git", ...args].map(quoteArg).join(" "); + const result = await vscode.window.showWarningMessage( + `Run this Git command?\n\n${command}\n\nWorking directory:\n${cwd}`, + { modal: true }, + "Execute", + "Cancel", + ); + return result === "Execute"; +} + +function isReadOnlyGitCommand(args: string[]): boolean { + const command = args[0]; + if (!command) { + return true; + } + if ( + [ + "rev-parse", + "status", + "for-each-ref", + "symbolic-ref", + "log", + "reflog", + "diff", + "show", + "describe", + "blame", + "fsck", + ].includes(command) + ) { + return true; + } + if (command === "remote" && args[1] === "-v") { + return true; + } + if (command === "branch" && (args.includes("--contains") || args.includes("-a"))) { + return true; + } + if (command === "tag" && args.some((arg) => arg.startsWith("--contains"))) { + return true; + } + if (command === "config" && args.includes("--list")) { + return true; + } + if (command === "stash" && (args[1] === "list" || args[1] === "show")) { + return true; + } + if (command === "submodule" && args[1] === "status") { + return true; + } + if (command === "worktree" && args[1] === "list") { + return true; + } + if (command === "notes" && args[1] === "show") { + return true; + } + if (command === "lfs" && ["ls-files", "locks"].includes(args[1] ?? "")) { + return true; + } + return false; +} + +function quoteArg(arg: string): string { + if (/^[A-Za-z0-9_./:=@{}~+,-]+$/.test(arg)) { + return arg; + } + return `'${arg.replace(/'/g, "'\\''")}'`; +} diff --git a/src/util/html.test.ts b/src/util/html.test.ts deleted file mode 100644 index 1ec418b..0000000 --- a/src/util/html.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert"; -import { escapeHtml } from "./html"; - -test("escapeHtml escapes all HTML-significant characters", () => { - assert.strictEqual( - escapeHtml(` & 'ok'`), - "<script>alert("x")</script> & 'ok'", - ); -}); - -test("escapeHtml neutralizes an attribute-breakout attempt", () => { - const malicious = `">`; - const escaped = escapeHtml(malicious); - assert.ok(!escaped.includes('"')); - assert.ok(!escaped.includes("<")); - assert.ok(!escaped.includes(">")); -}); - -test("escapeHtml leaves plain text untouched", () => { - assert.strictEqual(escapeHtml("v1.0.0"), "v1.0.0"); - assert.strictEqual(escapeHtml("abcdef1"), "abcdef1"); -}); - -test("escapeHtml handles empty string", () => { - assert.strictEqual(escapeHtml(""), ""); -}); diff --git a/src/util/html.ts b/src/util/html.ts deleted file mode 100644 index e189eed..0000000 --- a/src/util/html.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** Escapes text for safe interpolation into HTML markup (not attributes with single quotes). */ -export function escapeHtml(value: string): string { - return value - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """); -} diff --git a/src/views/CompareProvider.ts b/src/views/CompareProvider.ts index 87b738b..0819b90 100644 --- a/src/views/CompareProvider.ts +++ b/src/views/CompareProvider.ts @@ -1,12 +1,9 @@ -/** - * Tree view that compares two refs in a repository, showing the commits - * unique to each side plus the combined file diff between them. - */ import * as path from "node:path"; import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { Commit, CommitFile } from "../git/parsers/log"; +import { accessibleTreeItem } from "./treeAccessibility"; export type CompareTreeNode = | { type: "comparison"; repo: Repository; ref1: string; ref2: string } @@ -77,7 +74,10 @@ export class CompareProvider implements vscode.TreeDataProvider item.iconPath = new vscode.ThemeIcon("git-compare"); item.description = node.repo.name; item.contextValue = "vsgit.comparison"; - return item; + return accessibleTreeItem( + item, + `Comparing ${node.ref1} with ${node.ref2}, repository ${node.repo.name}`, + ); } if (node.type === "section") { @@ -95,7 +95,7 @@ export class CompareProvider implements vscode.TreeDataProvider item.iconPath = new vscode.ThemeIcon("files"); item.contextValue = "vsgit.comparison.section.files"; } - return item; + return accessibleTreeItem(item, `${node.label}, comparison section`); } if (node.type === "commit") { @@ -110,7 +110,10 @@ export class CompareProvider implements vscode.TreeDataProvider title: "Show Commit", arguments: [node.repo, c.sha], }; - return item; + return accessibleTreeItem( + item, + `${c.subject || "No message"}, commit ${c.sha.slice(0, 8)}, by ${c.authorName}`, + ); } // file node @@ -125,7 +128,7 @@ export class CompareProvider implements vscode.TreeDataProvider title: "Open Diff", arguments: [node.repo, f.path, node.ref1, node.ref2], }; - return item; + return accessibleTreeItem(item, `${f.path}, changed file`); } async getChildren(node?: CompareTreeNode): Promise { diff --git a/src/views/ConflictsProvider.ts b/src/views/ConflictsProvider.ts index a4038ac..426cf38 100644 --- a/src/views/ConflictsProvider.ts +++ b/src/views/ConflictsProvider.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { FileChange } from "../git/parsers/status"; +import { accessibleTreeItem } from "./treeAccessibility"; export type ConflictNode = | { type: "conflictRepo"; repo: Repository } @@ -36,7 +37,10 @@ export class ConflictsProvider implements vscode.TreeDataProvider, ); item.iconPath = new vscode.ThemeIcon("warning"); item.contextValue = "vsgit.conflictRepo"; - return item; + return accessibleTreeItem( + item, + `${label}, repository with conflicts`, + ); } const item = new vscode.TreeItem(node.change.path); @@ -49,7 +53,10 @@ export class ConflictsProvider implements vscode.TreeDataProvider, arguments: [node], }; item.tooltip = node.change.path; - return item; + return accessibleTreeItem( + item, + `${node.change.path}, ${conflictDescription(node.change)}, conflicted file`, + ); } getChildren(node?: ConflictNode): ConflictNode[] { diff --git a/src/views/ReflogProvider.ts b/src/views/ReflogProvider.ts index a7d1c18..2d40d47 100644 --- a/src/views/ReflogProvider.ts +++ b/src/views/ReflogProvider.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { ReflogEntry } from "../git/parsers/reflog"; +import { accessibleTreeItem } from "./treeAccessibility"; export type ReflogNode = | { type: "entry"; repo: Repository; entry: ReflogEntry } @@ -35,7 +36,7 @@ export class ReflogProvider implements vscode.TreeDataProvider, vsco } async refresh(): Promise { - this.repo = this.manager.getAll()[0]; + this.repo = this.manager.getActive(); if (this.repo) { try { this.entries = await this.repo.reflog("HEAD"); @@ -52,7 +53,7 @@ export class ReflogProvider implements vscode.TreeDataProvider, vsco if (node.type === "info") { const item = new vscode.TreeItem(node.label); item.description = "—"; - return item; + return accessibleTreeItem(item, node.label); } const e = node.entry; const item = new vscode.TreeItem( @@ -62,7 +63,10 @@ export class ReflogProvider implements vscode.TreeDataProvider, vsco item.iconPath = new vscode.ThemeIcon(actionIcon(e.action)); item.contextValue = "vsgit.reflogEntry"; item.tooltip = `${e.sha}\n${new Date(e.date * 1000).toLocaleString()}`; - return item; + return accessibleTreeItem( + item, + `${e.selector}, ${e.action}${e.message ? `, ${e.message}` : ""}, commit ${e.shortSha}`, + ); } getChildren(node?: ReflogNode): ReflogNode[] { diff --git a/src/views/RepositoriesProvider.ts b/src/views/RepositoriesProvider.ts index 90b1b57..d6dd151 100644 --- a/src/views/RepositoriesProvider.ts +++ b/src/views/RepositoriesProvider.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { RefInfo } from "../git/parsers/refs"; +import { accessibleTreeItem } from "./treeAccessibility"; /** * The kinds of nodes the Repositories tree renders. Each TreeItem carries one @@ -95,15 +96,28 @@ export class RepositoriesProvider : vscode.TreeItemCollapsibleState.Expanded, ); const ab = this.abCache.get(node.repo.root); + const active = this.manager.getActive()?.root === node.repo.root; const abParts: string[] = []; if (ab?.ahead) abParts.push(`↑${ab.ahead}`); if (ab?.behind) abParts.push(`↓${ab.behind}`); const abStr = abParts.join(" "); - item.description = [node.repo.headName, abStr].filter(Boolean).join(" "); - item.iconPath = new vscode.ThemeIcon("repo"); + item.description = [ + active ? "active" : undefined, + node.repo.headName, + abStr, + ].filter(Boolean).join(" "); + item.iconPath = new vscode.ThemeIcon(active ? "repo-force-push" : "repo"); item.contextValue = "vsgit.repo"; item.tooltip = node.repo.root; - return item; + item.command = { + command: "vsgit.repositories.setActive", + title: "Set Active Repository", + arguments: [node], + }; + return accessibleTreeItem( + item, + `${node.repo.name}, ${active ? "active " : ""}repository${node.repo.headName ? `, branch ${node.repo.headName}` : ""}${ab?.ahead ? `, ${ab.ahead} ahead` : ""}${ab?.behind ? `, ${ab.behind} behind` : ""}`, + ); } case "group": { const item = new vscode.TreeItem( @@ -115,7 +129,10 @@ export class RepositoriesProvider node.group === "localBranches" || node.group === "remoteBranches" ? "vsgit.branchesNode" : `vsgit.${node.group}Node`; - return item; + return accessibleTreeItem( + item, + `${GROUP_LABELS[node.group]}, group`, + ); } case "branch": { const item = new vscode.TreeItem(node.ref.shortName); @@ -134,43 +151,49 @@ export class RepositoriesProvider ? "vsgit.branch.local" : "vsgit.branch.remote"; item.tooltip = node.ref.objectId; - return item; + return accessibleTreeItem( + item, + `${node.ref.shortName}, ${node.ref.kind === "localBranch" ? "local" : "remote"} branch${node.ref.isHead ? ", current HEAD" : ""}${node.ref.upstream ? `, tracks ${node.ref.upstream}` : ""}`, + ); } case "tag": { const item = new vscode.TreeItem(node.ref.shortName); item.iconPath = new vscode.ThemeIcon("tag"); item.description = node.ref.subject; item.contextValue = "vsgit.tag"; - return item; + return accessibleTreeItem(item, `${node.ref.shortName}, tag`); } case "remote": { const item = new vscode.TreeItem(node.remoteName); item.iconPath = new vscode.ThemeIcon("cloud"); item.contextValue = "vsgit.remote"; - return item; + return accessibleTreeItem(item, `${node.remoteName}, remote`); } case "stash": { const item = new vscode.TreeItem(node.message || node.ref); item.iconPath = new vscode.ThemeIcon("archive"); item.description = node.ref; item.contextValue = "vsgit.stash"; - return item; + return accessibleTreeItem( + item, + `${node.message || node.ref}, stash ${node.ref}`, + ); } case "submodule": { const item = new vscode.TreeItem(node.path); item.iconPath = new vscode.ThemeIcon("file-submodule"); item.contextValue = "vsgit.submodule"; - return item; + return accessibleTreeItem(item, `${node.path}, submodule`); } case "info": { const item = new vscode.TreeItem(node.label); item.description = "—"; - return item; + return accessibleTreeItem(item, node.label); } } } - getChildren(node?: VsgitNode): VsgitNode[] { + async getChildren(node?: VsgitNode): Promise { if (!node) { const repos = this.manager.getAll(); if (repos.length === 0) { @@ -191,6 +214,13 @@ export class RepositoriesProvider } if (node.type === "group") { + if (node.group === "submodules") { + try { + await node.repo.ensureSubmodules(); + } catch { + return [{ type: "info", label: "(unable to load submodules)" }]; + } + } return this.groupChildren(node.repo, node.group); } diff --git a/src/views/ScmProvider.ts b/src/views/ScmProvider.ts new file mode 100644 index 0000000..8dbfea6 --- /dev/null +++ b/src/views/ScmProvider.ts @@ -0,0 +1,195 @@ +import * as path from "node:path"; +import * as vscode from "vscode"; +import { Repository } from "../git/Repository"; +import { RepositoryManager } from "../git/RepositoryManager"; +import { FileChange } from "../git/parsers/status"; +import { errMsg, withProgress } from "../commands/shared"; +import { VsgitQuickDiffProvider } from "../git/QuickDiffProvider"; + +type ScmGroupId = "index" | "workingTree" | "merge"; + +export interface VsgitScmResourceState extends vscode.SourceControlResourceState { + readonly vsgitGroup: ScmGroupId; + readonly vsgitChange: FileChange; +} + +interface RepoScm { + readonly scm: vscode.SourceControl; + readonly index: vscode.SourceControlResourceGroup; + readonly workingTree: vscode.SourceControlResourceGroup; + readonly merge: vscode.SourceControlResourceGroup; +} + +/** + * Bridges VsGit's repository model into VS Code's native Source Control view. + * Each repository gets real SCM resource groups so quick diff, SCM menus, and + * the commit input box work from the standard VS Code surface. + */ +export class VsgitScmProvider implements vscode.Disposable { + private readonly disposables: vscode.Disposable[] = []; + private readonly controls = new Map(); + + constructor(private readonly manager: RepositoryManager) { + this.disposables.push( + manager.onDidChange(() => this.sync()), + vscode.commands.registerCommand("vsgit.scm.commitInput", (root: string) => + this.commitInput(root), + ), + ); + this.sync(); + } + + dispose(): void { + for (const control of this.controls.values()) { + control.scm.dispose(); + } + this.controls.clear(); + for (const disposable of this.disposables) { + disposable.dispose(); + } + } + + private sync(): void { + const repos = this.manager.getAll(); + const liveRoots = new Set(repos.map((repo) => repo.root)); + + for (const [root, control] of this.controls) { + if (!liveRoots.has(root)) { + control.scm.dispose(); + this.controls.delete(root); + } + } + + for (const repo of repos) { + const control = this.controls.get(repo.root) ?? this.createControl(repo); + this.updateControl(repo, control); + } + } + + private createControl(repo: Repository): RepoScm { + const scm = vscode.scm.createSourceControl( + "vsgit", + `VsGit: ${repo.name}`, + vscode.Uri.file(repo.root), + ); + scm.quickDiffProvider = new VsgitQuickDiffProvider(this.manager, repo.root); + scm.acceptInputCommand = { + command: "vsgit.scm.commitInput", + title: "Commit", + arguments: [repo.root], + }; + // The Git Graph status-bar button is owned by GraphStatusBarService (a single, + // settings-gated $(git-branch) item). Don't also publish it as an SCM + // statusBarCommand, which would show a duplicate Graph button in the footer. + + const index = scm.createResourceGroup("index", "Staged Changes"); + const workingTree = scm.createResourceGroup("workingTree", "Changes"); + const merge = scm.createResourceGroup("merge", "Merge Changes"); + index.hideWhenEmpty = true; + workingTree.hideWhenEmpty = true; + merge.hideWhenEmpty = true; + + const control = { scm, index, workingTree, merge }; + this.controls.set(repo.root, control); + return control; + } + + private updateControl(repo: Repository, control: RepoScm): void { + const conflicted = uniqueChanges( + [...repo.stagedChanges, ...repo.unstagedChanges].filter((change) => change.conflicted), + ); + const staged = repo.stagedChanges.filter((change) => !change.conflicted); + const unstaged = repo.unstagedChanges.filter((change) => !change.conflicted); + + control.index.resourceStates = staged.map((change) => + this.resourceState(repo, change, "index"), + ); + control.workingTree.resourceStates = unstaged.map((change) => + this.resourceState(repo, change, "workingTree"), + ); + control.merge.resourceStates = conflicted.map((change) => + this.resourceState(repo, change, "merge"), + ); + control.scm.count = staged.length + unstaged.length + conflicted.length; + } + + private resourceState( + repo: Repository, + change: FileChange, + group: ScmGroupId, + ): VsgitScmResourceState { + const uri = vscode.Uri.file(path.join(repo.root, change.path)); + const state = { + resourceUri: uri, + vsgitGroup: group, + vsgitChange: change, + decorations: { + tooltip: `${change.path} - ${describeState(change, group)}`, + strikeThrough: change.indexState === "deleted" || change.worktreeState === "deleted", + } as vscode.SourceControlResourceDecorations, + } as VsgitScmResourceState; + return { + ...state, + command: { + command: "vsgit.scm.openDiff", + title: "Open Diff", + arguments: [state], + }, + }; + } + + private async commitInput(root: string): Promise { + const control = this.controls.get(root); + const repo = this.manager.get(root); + if (!control || !repo) { + return; + } + const message = control.scm.inputBox.value.trim(); + if (!message) { + vscode.window.showWarningMessage("Commit message cannot be empty."); + return; + } + if (repo.stagedChanges.length === 0) { + const choice = await vscode.window.showWarningMessage( + "No staged changes. Stage all changes and commit?", + "Stage All & Commit", + "Cancel", + ); + if (choice !== "Stage All & Commit") { + return; + } + await repo.stageAll(); + await repo.refresh(); + } + try { + const committed = await withProgress(this.manager, "Commit", () => + repo.commit(message), + ); + if (committed) { + control.scm.inputBox.value = ""; + } + } catch (e) { + vscode.window.showErrorMessage(`Commit failed: ${errMsg(e)}`); + } + } +} + +function uniqueChanges(changes: FileChange[]): FileChange[] { + const seen = new Set(); + return changes.filter((change) => { + if (seen.has(change.path)) { + return false; + } + seen.add(change.path); + return true; + }); +} + +function describeState(change: FileChange, group: ScmGroupId): string { + if (change.conflicted || group === "merge") { + return "conflicted"; + } + return group === "index" + ? (change.indexState ?? "modified") + : (change.worktreeState ?? "modified"); +} diff --git a/src/views/StagingProvider.ts b/src/views/StagingProvider.ts index 159f82f..4858977 100644 --- a/src/views/StagingProvider.ts +++ b/src/views/StagingProvider.ts @@ -3,6 +3,7 @@ import * as path from "node:path"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { FileChange } from "../git/parsers/status"; +import { accessibleTreeItem } from "./treeAccessibility"; export type StagingNode = | { type: "group"; group: "staged" | "unstaged" | "conflicted" } @@ -12,8 +13,8 @@ export type StagingNode = /** * Staging view: two top-level groups (Staged / Unstaged Changes) listing the * files in the active repository. Mirrors VsGit's Staging view layout. The - * "active" repository is the first discovered repo (multi-repo selection comes - * in a later phase). + * active repository is selected from the Repositories view and falls back to + * the first available repo when nothing has been selected yet. */ export class StagingProvider implements vscode.TreeDataProvider, vscode.Disposable { private readonly _onDidChangeTreeData = new vscode.EventEmitter< @@ -32,7 +33,7 @@ export class StagingProvider implements vscode.TreeDataProvider, vs } get activeRepo(): Repository | undefined { - return this.manager.getAll()[0]; + return this.manager.getActive(); } getTreeItem(node: StagingNode): vscode.TreeItem { @@ -58,12 +59,12 @@ export class StagingProvider implements vscode.TreeDataProvider, vs const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.Expanded); item.contextValue = contextValue; item.iconPath = new vscode.ThemeIcon(icon); - return item; + return accessibleTreeItem(item, `${label}, group`); } if (node.type === "info") { const item = new vscode.TreeItem(node.label); item.description = "—"; - return item; + return accessibleTreeItem(item, node.label); } const change = node.change; @@ -94,7 +95,10 @@ export class StagingProvider implements vscode.TreeDataProvider, vs title: "Open Diff", arguments: [node], }; - return item; + return accessibleTreeItem( + item, + `${change.path}, ${describeState(change, node.group)}`, + ); } getChildren(node?: StagingNode): StagingNode[] { @@ -138,7 +142,10 @@ export class StagingProvider implements vscode.TreeDataProvider, vs } } -function describeState(change: FileChange, group: "staged" | "unstaged"): string { +function describeState( + change: FileChange, + group: "staged" | "unstaged" | "conflicted", +): string { if (change.conflicted) { return "conflicted"; } diff --git a/src/views/SynchronizeProvider.ts b/src/views/SynchronizeProvider.ts index e8e4d9f..a2ac4e8 100644 --- a/src/views/SynchronizeProvider.ts +++ b/src/views/SynchronizeProvider.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { Commit } from "../git/parsers/log"; +import { accessibleTreeItem } from "./treeAccessibility"; export type SyncNode = | { type: "group"; repo: Repository; direction: "incoming" | "outgoing" } @@ -34,7 +35,7 @@ export class SynchronizeProvider implements vscode.TreeDataProvider, v } async refresh(): Promise { - this.repo = this.manager.getAll()[0]; + this.repo = this.manager.getActive(); if (this.repo) { const ab = await this.repo.aheadBehind(); this.hasUpstream = ab !== undefined; @@ -52,7 +53,7 @@ export class SynchronizeProvider implements vscode.TreeDataProvider, v if (node.type === "info") { const item = new vscode.TreeItem(node.label); item.description = "—"; - return item; + return accessibleTreeItem(item, node.label); } if (node.type === "group") { const count = @@ -66,7 +67,10 @@ export class SynchronizeProvider implements vscode.TreeDataProvider, v item.iconPath = new vscode.ThemeIcon( node.direction === "incoming" ? "arrow-down" : "arrow-up", ); - return item; + return accessibleTreeItem( + item, + `${node.direction}, ${count} commit${count === 1 ? "" : "s"}`, + ); } const c = node.commit; const item = new vscode.TreeItem(c.subject); @@ -74,7 +78,10 @@ export class SynchronizeProvider implements vscode.TreeDataProvider, v item.iconPath = new vscode.ThemeIcon("git-commit"); item.tooltip = `${c.sha}\n${c.authorName} <${c.authorEmail}>`; item.contextValue = "vsgit.syncCommit"; - return item; + return accessibleTreeItem( + item, + `${c.subject}, ${node.direction}, commit ${c.shortSha}, by ${c.authorName}`, + ); } getChildren(node?: SyncNode): SyncNode[] { diff --git a/src/views/WorktreesProvider.ts b/src/views/WorktreesProvider.ts index 3d3293c..8868b53 100644 --- a/src/views/WorktreesProvider.ts +++ b/src/views/WorktreesProvider.ts @@ -1,11 +1,8 @@ -/** - * Tree view listing each repository's git worktrees, with commands to - * create, open, and remove them. - */ import * as path from "node:path"; import * as vscode from "vscode"; import { RepositoryManager } from "../git/RepositoryManager"; import { Repository, WorktreeInfo } from "../git/Repository"; +import { accessibleTreeItem } from "./treeAccessibility"; export type WorktreeTreeNode = | { type: "repoWorktrees"; repo: Repository } @@ -41,7 +38,10 @@ export class WorktreesProvider implements vscode.TreeDataProvider { diff --git a/src/views/treeAccessibility.ts b/src/views/treeAccessibility.ts new file mode 100644 index 0000000..dcc0a7f --- /dev/null +++ b/src/views/treeAccessibility.ts @@ -0,0 +1,13 @@ +import * as vscode from "vscode"; + +/** + * Give screen readers a complete label instead of relying on visual + * decorations, icons, abbreviated status letters, or secondary descriptions. + */ +export function accessibleTreeItem( + item: vscode.TreeItem, + label: string, +): vscode.TreeItem { + item.accessibilityInformation = { label }; + return item; +} diff --git a/src/webviews/CommitPickerView.ts b/src/webviews/CommitPickerView.ts index 63261b1..28edfab 100644 --- a/src/webviews/CommitPickerView.ts +++ b/src/webviews/CommitPickerView.ts @@ -1,6 +1,7 @@ import * as crypto from "node:crypto"; import * as vscode from "vscode"; import { Repository } from "../git/Repository"; +import type { Commit } from "../git/parsers/log"; import { commitPickerHtml } from "./commitPickerHtml"; /** @@ -8,10 +9,13 @@ import { commitPickerHtml } from "./commitPickerHtml"; * commit SHA, or undefined if the user cancelled. * * Usage: - * const sha = await CommitPickerView.pick(repo); + * const sha = await CommitPickerView.pick(repo, context.extensionUri); */ export class CommitPickerView { - static async pick(repo: Repository): Promise { + static async pick( + repo: Repository, + extensionUri: vscode.Uri, + ): Promise { return new Promise((resolve) => { const panel = vscode.window.createWebviewPanel( "vsgit.commitPicker", @@ -20,12 +24,19 @@ export class CommitPickerView { { enableScripts: true, retainContextWhenHidden: false, - localResourceRoots: [], + localResourceRoots: [vscode.Uri.joinPath(extensionUri, "resources")], }, ); const nonce = crypto.randomBytes(16).toString("hex"); - panel.webview.html = commitPickerHtml(nonce, panel.webview.cspSource); + const codiconCssUri = panel.webview.asWebviewUri( + vscode.Uri.joinPath(extensionUri, "resources", "codicon.css"), + ); + panel.webview.html = commitPickerHtml( + nonce, + panel.webview.cspSource, + codiconCssUri.toString(), + ); let resolved = false; @@ -62,8 +73,7 @@ async function loadCommits( ): Promise { try { const commits = await repo.log({ limit: 500 }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const data = commits.map((c: any) => ({ + const data = commits.map((c: Commit) => ({ sha: c.sha, shortSha: c.shortSha, parents: c.parents, diff --git a/src/webviews/CreateTagDialog.ts b/src/webviews/CreateTagDialog.ts index a31bb1a..a6f2b39 100644 --- a/src/webviews/CreateTagDialog.ts +++ b/src/webviews/CreateTagDialog.ts @@ -1,10 +1,4 @@ -/** - * Webview-backed dialog for creating a tag: collects name, message, and - * annotate/sign/force/push options, then resolves with the user's choices - * (or undefined if cancelled). - */ import * as vscode from "vscode"; -import { escapeHtml } from "../util/html"; export interface CreateTagDialogResult { name: string; @@ -181,13 +175,19 @@ function createTagHtml(nonce: string, cspSource: string, shaLabel: string): stri background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); } + .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; + overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } + button:focus-visible, input:focus-visible, textarea:focus-visible { + outline: 1px solid var(--vscode-focusBorder); outline-offset: 2px; + } + @media (forced-colors: active) { button, input, textarea, form { forced-color-adjust: auto; } }
-
+
-

Create Tag

+

Create Tag

@@ -224,6 +224,7 @@ function createTagHtml(nonce: string, cspSource: string, shaLabel: string): stri +
+ `; @@ -260,6 +348,13 @@ export class CommitViewProvider implements vscode.WebviewViewProvider { type StagingGroup = "staged" | "unstaged" | "conflicted"; +/** What to do after a successful commit, mirroring VS Code's commit dropdown. */ +type CommitMode = "commit" | "push" | "sync"; + +function isCommitMode(value: unknown): value is CommitMode { + return value === "commit" || value === "push" || value === "sync"; +} + function isStagingGroup(value: unknown): value is StagingGroup { return value === "staged" || value === "unstaged" || value === "conflicted"; } diff --git a/src/webviews/commitPickerHtml.ts b/src/webviews/commitPickerHtml.ts index c4f1a80..f54d675 100644 --- a/src/webviews/commitPickerHtml.ts +++ b/src/webviews/commitPickerHtml.ts @@ -8,13 +8,18 @@ * - Keyboard navigation (↑/↓/Enter), double-click to confirm * - Posts { command:"pick", sha } back to the extension on confirm */ -export function commitPickerHtml(nonce: string, _cspSource: string): string { +export function commitPickerHtml( + nonce: string, + cspSource: string, + codiconCssUri: string, +): string { return /* html */ ` + content="default-src 'none'; style-src ${cspSource} 'nonce-${nonce}'; font-src ${cspSource}; script-src 'nonce-${nonce}';" /> + +
- +
@@ -203,6 +216,11 @@ export function commitPickerHtml(nonce: string, _cspSource: string): string { + +`; + } + + private disposeViewListeners(): void { + for (const disposable of this.viewDisposables) disposable.dispose(); + this.viewDisposables = []; + } + + private disposePanelListeners(): void { + for (const disposable of this.panelDisposables) disposable.dispose(); + this.panelDisposables = []; + } +} + +function getNonce(): string { + const alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let value = ""; + for (let index = 0; index < 32; index++) { + value += alphabet.charAt(Math.floor(Math.random() * alphabet.length)); + } + return value; +} diff --git a/src/webviews/documentation/documentationContent.test.ts b/src/webviews/documentation/documentationContent.test.ts new file mode 100644 index 0000000..b81db38 --- /dev/null +++ b/src/webviews/documentation/documentationContent.test.ts @@ -0,0 +1,68 @@ +import { test } from "node:test"; +import * as assert from "node:assert"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { + buildDocumentationData, + DocumentationManifest, +} from "./documentationContent"; + +const manifest = JSON.parse( + fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"), +) as DocumentationManifest; + +test("documentation catalog covers every contributed operation exactly once", () => { + const data = buildDocumentationData(manifest); + const documented = data.operationCategories.flatMap((category) => + category.operations.map((operation) => operation.command), + ); + const contributed = (manifest.contributes?.commands ?? []).map( + (command) => command.command, + ); + + assert.deepStrictEqual([...documented].sort(), [...contributed].sort()); + assert.strictEqual(new Set(documented).size, documented.length); + assert.strictEqual(data.operationCount, contributed.length); +}); + +test("documentation entries include definitions, purposes, and usage guidance", () => { + const data = buildDocumentationData(manifest); + + assert.ok(data.components.length >= 15); + assert.ok(data.glossary.length >= 60); + for (const entry of [...data.components, ...data.glossary]) { + assert.ok(entry.name); + assert.ok(entry.definition); + assert.ok(entry.purpose); + assert.ok(entry.use); + } + for (const category of data.operationCategories) { + assert.ok(category.purpose); + assert.ok(category.workflow); + for (const operation of category.operations) { + assert.ok(operation.title); + assert.ok(operation.purpose); + assert.ok(operation.use); + } + } +}); + +test("context-only operations are not presented as directly runnable", () => { + const data = buildDocumentationData(manifest); + const hidden = new Set( + (manifest.contributes?.menus?.commandPalette ?? []) + .filter((entry) => entry.when === "false") + .map((entry) => entry.command), + ); + const operations = data.operationCategories.flatMap( + (category) => category.operations, + ); + + assert.strictEqual( + data.paletteOperationCount, + data.operationCount - hidden.size, + ); + for (const operation of operations) { + assert.strictEqual(operation.runnable, !hidden.has(operation.command)); + } +}); diff --git a/src/webviews/documentation/documentationContent.ts b/src/webviews/documentation/documentationContent.ts new file mode 100644 index 0000000..b63ac23 --- /dev/null +++ b/src/webviews/documentation/documentationContent.ts @@ -0,0 +1,639 @@ +/** + * Curated documentation for the in-extension reference library. + * + * The operation list itself is built from package.json so every contributed + * command is documented automatically. The curated material explains the + * extension surfaces and the Git concepts behind those operations. + */ + +export interface DocumentationManifest { + version?: string; + contributes?: { + commands?: Array<{ + command: string; + title: string; + category?: string; + }>; + menus?: { + commandPalette?: Array<{ + command: string; + when?: string; + }>; + }; + }; +} + +export interface DocumentationEntry { + name: string; + definition: string; + purpose: string; + use: string; + keywords?: string; +} + +export interface OperationEntry { + command: string; + title: string; + purpose: string; + use: string; + runnable: boolean; +} + +export interface OperationCategory { + id: string; + name: string; + purpose: string; + workflow: string; + caution?: string; + operations: OperationEntry[]; +} + +export interface DocumentationData { + version: string; + components: DocumentationEntry[]; + glossary: DocumentationEntry[]; + operationCategories: OperationCategory[]; + operationCount: number; + paletteOperationCount: number; + added: string[]; + pending: string[]; +} + +const components: DocumentationEntry[] = [ + { + name: "VsGit activity-bar container", + definition: + "The dedicated VsGit icon and sidebar container that groups the extension's repository tools.", + purpose: + "Keeps repository discovery, committing, advanced Git views, and documentation in one predictable location.", + use: + "Select the VsGit icon in the Activity Bar, then expand the view needed for the current task.", + keywords: "sidebar container navigation activity bar", + }, + { + name: "Repositories", + definition: + "A compact, multi-root list of Git repositories detected in the open VS Code workspace.", + purpose: + "Selects the active repository and exposes frequent fetch, pull, push, history, clone, and refresh actions.", + use: + "Select a repository to make every other VsGit surface follow it. Use the title actions for common transport operations.", + keywords: "active repository multi-root fetch pull push", + }, + { + name: "Commit", + definition: + "A persistent Source-Control-style webview containing the commit message, changed-file groups, and commit actions.", + purpose: + "Supports staging, hunk operations, amend, sign-off, GPG signing, Commit & Push, and Commit & Sync without leaving the sidebar.", + use: + "Stage the intended changes, enter a message, choose optional commit settings, then use the split commit button.", + keywords: "staging message amend signoff gpg commit push sync", + }, + { + name: "Git Repositories", + definition: + "A detailed repository tree containing local and remote branches, tags, remotes, stashes, submodules, and worktrees.", + purpose: + "Provides object-oriented navigation and context menus for repository administration.", + use: + "Expand a repository and right-click the relevant object. Selecting a repository also changes the active repository.", + keywords: "tree branches tags remotes stashes submodules", + }, + { + name: "Staging", + definition: + "An optional advanced tree that separates staged, unstaged, and conflicted files.", + purpose: + "Provides a compact alternative to the Commit webview for managing the index and reviewing diffs.", + use: + "Enable vsgit.showAdvancedViews, then use inline or context actions to stage, unstage, discard, diff, or commit.", + keywords: "index stage unstage diff advanced", + }, + { + name: "Synchronize", + definition: + "An optional advanced view of commits ahead of and behind the configured upstream branch.", + purpose: + "Explains what will be pushed or pulled before synchronizing and provides commit-level actions.", + use: + "Configure an upstream branch, expand Incoming or Outgoing, and inspect or act on a commit.", + keywords: "ahead behind upstream incoming outgoing", + }, + { + name: "Conflicts", + definition: + "An optional advanced list of files with unresolved merge states.", + purpose: + "Centralizes Use Ours, Use Theirs, Open Merge Editor, and Mark Resolved actions.", + use: + "Open the view during a merge, rebase, cherry-pick, or revert; resolve each file and continue the operation.", + keywords: "merge conflict ours theirs resolve", + }, + { + name: "Reflog", + definition: + "An optional advanced view of local HEAD and reference movements recorded by Git.", + purpose: + "Recovers commits or branch positions that no longer appear in normal history.", + use: + "Find the last known good entry, inspect it, then checkout it or reset HEAD after confirming the target.", + keywords: "recovery reset lost commit head", + }, + { + name: "Worktrees", + definition: + "An optional advanced view of linked working directories that share one repository.", + purpose: + "Lets multiple branches be checked out simultaneously without cloning the repository again.", + use: + "Create a worktree for a branch, open it in a window, and lock, move, remove, or prune it when needed.", + keywords: "linked worktree branch directory", + }, + { + name: "Compare", + definition: + "An optional advanced tree showing commits and changed files between two refs.", + purpose: + "Makes branch, tag, or commit differences inspectable before merge, rebase, review, or release.", + use: + "Start a comparison, select both refs, then open commit details or file diffs; switch sides to reverse the perspective.", + keywords: "diff refs branches symmetric comparison", + }, + { + name: "Git Graph", + definition: + "A full editor panel that renders the commit DAG, branch lanes, ref labels, metadata, changed files, and commit actions.", + purpose: + "Provides visual history exploration, ancestry tracing, tracking, searching, filtering, and ref or commit operations.", + use: + "Run VsGit: Show Git Graph. Select a row for details, Ctrl/Cmd-click two commits to compare, or right-click for actions.", + keywords: "dag history graph lanes trace tracking", + }, + { + name: "History", + definition: + "A paginated commit log with graph lanes, filters, date ranges, and per-commit context menus.", + purpose: + "Supports focused history investigation when a full repository graph is not required.", + use: + "Run VsGit: Show History, choose a branch or filters, then inspect or act on matching commits.", + keywords: "log commits author message date filter", + }, + { + name: "Native Source Control integration", + definition: + "VsGit-backed staged, working-tree, and merge resource groups published through VS Code's Source Control API.", + purpose: + "Makes VsGit diffs, quick-diff gutters, commit input, and resource actions available in the built-in Source Control panel.", + use: + "Open Source Control and use the VsGit repository groups or their file/group context menus.", + keywords: "scm source control quick diff resource groups", + }, + { + name: "Git Config editor", + definition: + "A graphical editor for local, global, and read-only system Git configuration.", + purpose: + "Shows configuration origin and scope while avoiding manual key/value command entry for routine changes.", + use: + "Run VsGit: Open Git Config Panel, select a scope, then add, edit, or unset supported values.", + keywords: "configuration local global system scope", + }, + { + name: "Inline blame", + definition: + "Editor decorations that identify the most recent commit responsible for the active line.", + purpose: + "Provides authorship and change context without opening a separate history panel.", + use: + "Run VsGit: Toggle Inline Blame or use the configured keyboard shortcut while a tracked file is open.", + keywords: "author line annotation provenance", + }, + { + name: "Background services", + definition: + "Repository file watching, optional automatic fetch, notifications, and the Git Graph status-bar entry.", + purpose: + "Keeps views current when Git changes inside or outside VS Code and can discover remote updates periodically.", + use: + "Configure vsgit.autoRefresh, vsgit.autoFetch.*, and vsgit.graph.showStatusBarItem in Settings.", + keywords: "watch auto fetch refresh status bar notification", + }, + { + name: "Documentation library", + definition: + "This searchable sidebar view and full-screen reference for VsGit components, Git terms, and every contributed operation.", + purpose: + "Explains what features do, why Git concepts matter, and where each operation is available.", + use: + "Open Documentation at the bottom of the VsGit sidebar, search by concept or command ID, or choose Open Full Library.", + keywords: "help reference glossary operations library", + }, +]; + +const glossary: DocumentationEntry[] = [ + term("Repository", "A Git database plus its checked-out project files and configuration.", "Stores project history, refs, and collaboration settings.", "Open its folder in VS Code or clone an existing remote."), + term("Working tree", "The checked-out files you edit on disk.", "Represents the current editable project state before staging.", "Edit files normally; inspect Changes to see differences from the index."), + term("Worktree", "An additional working directory linked to the same repository database.", "Allows simultaneous branch checkouts without another clone.", "Create one from the Worktrees view, then open it in another window."), + term("Index / staging area", "The proposed snapshot Git will use for the next commit.", "Separates reviewed changes from other working-tree edits.", "Stage files or hunks, verify the staged diff, then commit."), + term("Tracked file", "A file already known to Git through a commit or the index.", "Participates in status, diff, and commit workflows.", "Modify, stage, restore, or remove it through VsGit file actions."), + term("Untracked file", "A working-tree file Git is not currently tracking.", "Keeps new content outside history until explicitly staged.", "Stage it to track it, ignore it, or clean it if it is disposable."), + term("Ignored file", "A file matched by .gitignore, an exclude file, or a global ignore rule.", "Prevents generated, local, or sensitive files from appearing as normal changes.", "Add an appropriate pattern to .gitignore; never rely on ignore rules for an already tracked secret."), + term("Modified", "A tracked file whose content differs from the index or HEAD.", "Signals that a file has uncommitted changes.", "Open its diff, then stage, discard, or continue editing."), + term("Staged", "A change currently recorded in the index.", "Marks exactly what the next non-amend commit will include.", "Review Staged Changes and unstage anything that should not be committed."), + term("Commit", "An immutable snapshot with metadata and one or more parent commits.", "Records a meaningful project change in history.", "Stage changes, write an explanatory message, then commit."), + term("Amend", "Replacement of the current tip commit with a new commit.", "Corrects the last message or snapshot before sharing it.", "Enable Amend in the Commit view; avoid amending a shared commit unless history rewriting is coordinated."), + term("HEAD", "The symbolic pointer to the currently checked-out branch or a directly checked-out commit.", "Defines the current history position and default comparison base.", "Checkout a branch to attach HEAD, or a commit for a detached inspection."), + term("Detached HEAD", "A state where HEAD points directly to a commit instead of a branch.", "Supports temporary inspection or testing without moving a branch.", "Create a branch before committing work you want to retain."), + term("Branch", "A movable ref that identifies the latest commit in a line of work.", "Provides a named place for independent development.", "Create or checkout a branch, commit work, then merge or rebase it."), + term("Local branch", "A branch stored in the local repository.", "Tracks local work and may be connected to an upstream branch.", "Manage it in Git Repositories or the Git Graph."), + term("Remote-tracking branch", "A local record such as origin/main of a branch last observed on a remote.", "Shows remote state without directly checking out the remote ref.", "Fetch to update it; create a local tracking branch to work on it."), + term("Upstream", "The remote branch associated with a local branch for pull, push, ahead, and behind calculations.", "Enables concise synchronization and status reporting.", "Use Configure Upstream or set it on the first push."), + term("Ahead / behind", "Counts of commits present only locally or only on the upstream.", "Summarizes synchronization state.", "Inspect Synchronize, pull behind commits, and push ahead commits."), + term("Ref", "A named pointer to a commit, including branches, tags, and remote-tracking branches.", "Makes commits addressable with stable names.", "Select refs in history, compare, reset, archive, and graph operations."), + term("Revision", "Any expression Git can resolve to an object, such as HEAD, a branch, a tag, a SHA, or HEAD~2.", "Provides a general way to identify historical states.", "Enter or select one when an operation asks for a ref or revision."), + term("SHA / commit hash", "The hexadecimal object identifier for a commit.", "Uniquely identifies an exact historical object.", "Copy it from History or Graph and use it in compare, checkout, or recovery actions."), + term("Tag", "A named ref commonly used to mark a release or milestone.", "Makes an important commit easy to find and share.", "Create it at the intended commit and push it if collaborators need it."), + term("Lightweight tag", "A tag that is only a name pointing to a commit.", "Provides a simple local or shared marker.", "Create a tag without annotation or signing options."), + term("Annotated tag", "A tag object with a message, tagger identity, date, and optional signature.", "Provides release metadata and supports verification.", "Enable annotation in the Create Tag dialog and provide a message."), + term("Remote", "A named URL used to exchange refs and objects with another repository.", "Connects local history to GitHub, GitLab, Gerrit, or another Git server.", "Add, edit, remove, or prune remotes through VsGit."), + term("Origin", "The conventional default remote name created by clone.", "Provides the usual fetch and push destination.", "Treat it as a convention, not a special Git keyword; other remote names work too."), + term("Clone", "Creation of a new local repository from a remote repository.", "Copies history, configures a remote, and checks out an initial branch.", "Run VsGit: Clone Repository and choose a URL and destination."), + term("Initialize", "Creation of a new .git database in an existing folder.", "Starts version control for a local project.", "Run VsGit: Initialize Repository, then stage and commit the initial files."), + term("Stage / add", "Copy selected working-tree content into the index.", "Builds the next commit snapshot.", "Stage files, groups, or individual hunks from Commit, Staging, Explorer, or Source Control."), + term("Unstage", "Remove selected changes from the index while normally preserving working-tree edits.", "Refines the next commit without losing work.", "Use Unstage on a file, group, or hunk."), + term("Diff", "A line-level comparison between two file or tree states.", "Explains exactly what changed.", "Open a diff from a file row or compare refs, commits, the index, and HEAD."), + term("Fetch", "Download remote objects and update remote-tracking refs without changing the working tree.", "Refreshes knowledge of remote history safely.", "Fetch before reviewing incoming work or comparing with a remote branch."), + term("Pull", "Fetch followed by integration into the current branch.", "Brings upstream commits into local history.", "Choose merge or rebase strategy, resolve conflicts if any, then continue."), + term("Push", "Send local objects and update refs on a remote.", "Publishes commits and tags for collaboration.", "Confirm the destination and upstream; use force only when coordinated."), + term("Force push", "A push that permits replacing remote history.", "Publishes intentionally rewritten history.", "Use only after checking the expected remote tip and coordinating with collaborators."), + term("Fast-forward", "A ref update where the old tip is an ancestor of the new tip.", "Integrates history without creating a merge commit or rewriting commits.", "Pull or merge when histories have not diverged."), + term("Merge", "Combine another history line into the current branch.", "Preserves existing commits and records convergence when required.", "Select a source branch, review the result, resolve conflicts, and commit if Git does not fast-forward."), + term("Merge commit", "A commit with two or more parents.", "Records that separate histories were combined.", "Created by a non-fast-forward merge after conflicts and staged results are complete."), + term("Merge base", "A best common ancestor of two commits.", "Provides the baseline for three-way merge and many comparisons.", "Use branch comparison to understand changes since histories diverged."), + term("Conflict", "A change Git cannot combine automatically.", "Requires a human decision about the final content.", "Use the Merge Editor or ours/theirs actions, stage the resolution, and continue."), + term("Ours / theirs", "Labels for the two sides of a conflict relative to the operation being performed.", "Provides whole-file resolution shortcuts.", "Verify the current operation because the meaning can differ during rebase; then choose and review the result."), + term("Rebase", "Replay commits onto a different base, creating new commit IDs.", "Produces a linearized history or updates a branch before integration.", "Select a new base, resolve conflicts per commit, and continue, skip, or abort."), + term("Interactive rebase", "A rebase with an editable todo list of commit actions.", "Reorders, combines, edits, or removes local commits.", "Open Interactive Rebase, arrange actions, save the plan, and resolve any stops."), + term("Pick", "The default interactive-rebase action that keeps a commit.", "Replays a commit unchanged in intent.", "Leave a todo item as pick when it should remain separate."), + term("Reword", "An interactive-rebase action that changes a commit message.", "Improves history without changing the snapshot.", "Mark the commit reword and provide the replacement message when prompted."), + term("Squash", "Combine a commit with the previous commit and edit the combined message.", "Consolidates related work into one history unit.", "Place it after the commit it should join in the rebase todo."), + term("Fixup", "Combine a commit with the previous commit while discarding the fixup message.", "Absorbs small correction commits cleanly.", "Use it for a commit whose separate message is not useful."), + term("Drop", "Remove a commit while rewriting history.", "Deletes an unwanted local history change.", "Use only after checking dependent commits and unshared-history assumptions."), + term("Cherry-pick", "Apply the change introduced by selected commit(s) onto the current branch.", "Transfers specific work without merging the entire source branch.", "Choose commits in order, resolve conflicts, and continue or abort."), + term("Revert", "Create a new commit that inverses an earlier commit.", "Safely undoes shared history without rewriting it.", "Select the commit, review the inverse changes, resolve conflicts, and commit."), + term("Reset", "Move HEAD and optionally reset the index and working tree to another revision.", "Repositions a branch or discards selected state depending on mode.", "Choose soft, mixed, hard, keep, or merge only after understanding what each mode preserves."), + term("Soft reset", "Move HEAD while leaving the index and working tree unchanged.", "Turns commits back into staged changes.", "Use when rebuilding recent commits without losing their content."), + term("Mixed reset", "Move HEAD and reset the index while leaving working-tree files.", "Turns commits or staged changes back into unstaged edits.", "Use for recommitting with a different staging selection."), + term("Hard reset", "Move HEAD and make the index and tracked working-tree files match the target.", "Discards tracked local changes and commits from the branch tip.", "Review status and reflog recovery options before confirming."), + term("Restore / replace", "Make a file match a selected Git state such as HEAD, the index, or another ref.", "Discards or substitutes file content without moving the branch.", "Open the file's Replace With menu and verify the source revision."), + term("Stash", "A commit-like saved snapshot of working-tree and index changes outside normal branch history.", "Temporarily clears work while preserving unfinished changes.", "Create a named stash, then apply, pop, inspect, branch from, or drop it."), + term("Apply vs pop", "Apply restores stash changes and keeps the stash; pop restores them and removes it after success.", "Controls whether the saved fallback remains available.", "Use apply when testing or when extra recovery safety is useful."), + term("Reflog", "A local log of recent ref and HEAD values.", "Recovers commits after reset, rebase, amend, or branch deletion.", "Find the old SHA in Reflog and create a branch or reset to it."), + term("Blame", "A line-by-line attribution to the last modifying commit.", "Finds change provenance and relevant history.", "Toggle inline blame and inspect the referenced commit rather than treating attribution as ownership."), + term("Bisect", "A binary search across commits using good and bad markers.", "Finds the first commit that introduced a reproducible problem.", "Start with known endpoints, test each checkout, mark it good or bad, then reset when finished."), + term("Patch", "A portable text representation of changes or commits.", "Transfers or reviews changes outside normal remote exchange.", "Create from staged changes or commits, or apply a trusted patch file."), + term("Archive", "A snapshot of tracked files from a ref without repository history.", "Creates release or distribution zip/tar files.", "Choose a ref, format, optional prefix, and output path."), + term("Submodule", "A repository recorded as a specific commit inside another repository.", "Pins an independently versioned dependency or component.", "Add, initialize, update, and sync it while committing both the gitlink and .gitmodules changes."), + term("Subtree", "Another repository's content integrated into a subdirectory of the main history.", "Vendors or exchanges a component without requiring submodule checkout behavior.", "Add it at a prefix, then pull, push, or split its history."), + term("Git LFS", "An extension that stores pointer files in Git and large content in separate object storage.", "Reduces repository object growth for large binary files.", "Install git-lfs, track patterns, commit .gitattributes, and use lock/pull/prune operations."), + term("Git notes", "Metadata attached to objects without changing commit IDs.", "Adds review, build, or contextual information outside commit messages.", "Add, show, edit, or remove a note and configure note sharing when collaboration requires it."), + term("Hook", "An executable Git runs at defined client or server events.", "Automates validation, message changes, or workflow integration.", "Keep hooks trusted and executable; VsGit invokes the real Git CLI, so normal hooks run."), + term(".gitignore", "A versioned pattern file controlling which untracked paths Git normally ignores.", "Keeps build products and machine-local files out of status and commits.", "Add narrow patterns and commit the file when the rules should be shared."), + term("Assume unchanged", "An index performance hint telling Git to avoid checking a tracked path for worktree changes.", "Can reduce stat work for files expected not to change; it is not an ignore mechanism.", "Use sparingly and clear it with No Assume Unchanged before expecting normal status behavior."), + term("Skip worktree", "An index flag used by sparse-checkout-style workflows to treat a tracked path as absent or unchanged locally.", "Supports partial working trees; it is not a safe general-purpose local-ignore feature.", "Use only when you understand sparse checkout, and clear it with No Skip Worktree."), + term("Clean", "Permanent removal of untracked files, and optionally directories or ignored files.", "Returns the working tree to a known state.", "Preview what will be removed and confirm that no untracked work is needed."), + term("Prune", "Removal of stale references or unreachable/obsolete data, depending on the command.", "Cleans remote-tracking refs, worktree metadata, LFS objects, or Git objects.", "Confirm which prune operation is selected; their scopes and recovery properties differ."), + term("Garbage collection (gc)", "Repository maintenance that packs and optimizes objects and may expire unreachable data.", "Improves storage and access efficiency.", "Run during maintenance windows and avoid aggressive expiry when recovery may be needed."), + term("fsck", "Git's object connectivity and validity check.", "Diagnoses missing, corrupt, dangling, or unreachable objects.", "Run Check Integrity and inspect the report before attempting repair."), + term("Gerrit", "A code-review system where commits are pushed to special refs for review.", "Supports review-first integration controlled by Gerrit.", "Install the Change-Id hook when required, commit, then Push for Review."), + term("Change-Id", "A Gerrit commit-message footer that links patch-set revisions to one review.", "Keeps amended or rebased versions associated with the same Gerrit change.", "Install the hook before committing or preserve the existing footer when amending."), + term("Pull-request ref", "A hosting-provider ref exposing a pull request head outside normal branches.", "Allows local inspection of proposed changes.", "Use Fetch GitHub Pull Requests to create local refs for available PR heads."), + term("Refspec", "A mapping describing which source refs are fetched or pushed to which destination refs.", "Controls remote namespace exchange.", "Use standard remote operations unless a workflow explicitly requires a custom refspec."), + term("DAG", "The directed acyclic graph formed by commits and parent links.", "Models branching, merging, ancestry, and reachability.", "Use Git Graph trace and tracking controls to inspect paths through the DAG."), + term("Author vs committer", "The author originally wrote a change; the committer created the current commit object.", "Explains why rebased or applied commits can have different identities and dates.", "Toggle both columns in Git Graph when auditing rewritten or imported history."), + term("Signed-off-by / DCO", "A commit-message trailer asserting contribution under a project's Developer Certificate of Origin process.", "Supports policy attestation; it is not a cryptographic signature.", "Enable Sign off before committing when the project requires it."), + term("GPG signature", "A cryptographic signature attached to a commit or annotated tag.", "Lets others verify signer identity and object integrity when trust is configured.", "Configure Git signing, enable GPG, and use Verify GPG Signature on commits."), +]; + +function term( + name: string, + definition: string, + purpose: string, + use: string, +): DocumentationEntry { + return { name, definition, purpose, use }; +} + +const categoryDefinitions = [ + category( + "repository", + "Repository setup & discovery", + "Create, clone, initialize, select, and refresh repositories.", + "Start here when bringing a project under Git or choosing the active repository.", + ), + category( + "transport", + "Remotes & synchronization", + "Exchange refs and objects with remotes and inspect synchronization state.", + "Fetch first when you only need remote awareness; pull integrates; push publishes.", + "Push, remote removal, and cleaning stale refs can affect shared workflows.", + ), + category( + "changes", + "Staging & working-tree changes", + "Move changes between the working tree and index, inspect diffs, and discard or ignore content.", + "Review a diff, stage the intended files or hunks, and verify the index before committing.", + "Discard, delete, clean, and replace operations can remove local work.", + ), + category( + "commits", + "Commits & commit-level actions", + "Create, amend, inspect, verify, transfer, undo, or combine commits.", + "Use immutable follow-up operations such as revert for shared history; rewrite only local history.", + "Amend, squash, and some cherry-pick workflows create new commit IDs.", + ), + category( + "refs", + "Branches & tags", + "Create and manage movable branches, remote-tracking branches, upstreams, and release tags.", + "Choose a ref in the repository tree or graph, then use its context actions.", + "Deleting refs or force-replacing tags can make commits harder to find.", + ), + category( + "history", + "History, graph & comparison", + "Explore ancestry, search commits, compare refs or files, inspect reflog entries, and attribute lines.", + "Open Graph for repository topology, History for focused logs, and Compare for two-ref differences.", + ), + category( + "integration", + "Merge, rebase & conflicts", + "Integrate histories and complete or abort in-progress sequencer operations.", + "Inspect source and target refs, integrate, resolve every conflict, stage resolutions, then continue.", + "Rebase rewrites commit IDs; abort or reflog can recover an unwanted result.", + ), + category( + "stash", + "Stashes", + "Temporarily save unfinished index and working-tree changes outside branch history.", + "Create a stash before switching tasks, inspect it, then apply, pop, or create a branch from it.", + "Dropping or clearing stashes removes their normal recovery entry.", + ), + category( + "worktrees", + "Worktrees", + "Manage multiple linked working directories for one repository.", + "Create a worktree per simultaneous branch task and remove it after the work is integrated.", + "Removing a worktree with uncommitted changes can lose work.", + ), + category( + "recovery", + "Recovery, diagnosis & maintenance", + "Find regressions, reset repository state, inspect integrity, and optimize object storage.", + "Prefer read-only logs and checks first; capture a recovery ref before destructive maintenance.", + "Hard reset, prune, and aggressive maintenance can discard recoverable state.", + ), + category( + "exchange", + "Patches & archives", + "Export changes or tracked snapshots to files and apply portable patches.", + "Use patches for change exchange and archives for history-free release artifacts.", + "Review patch contents and paths before applying files from another source.", + ), + category( + "composition", + "LFS, submodules & subtrees", + "Manage large-file storage and repositories composed from other repositories.", + "Choose the model that matches the project, then commit its metadata alongside content changes.", + "These workflows may require extra tools, server support, or coordinated remote configuration.", + ), + category( + "collaboration", + "Notes & Gerrit", + "Attach metadata to commits and publish Gerrit review changes.", + "Use notes for separate metadata and Gerrit Change-Ids for review patch-set continuity.", + ), + category( + "configuration", + "Configuration", + "Inspect or edit Git configuration at local, global, and system scopes.", + "Prefer local scope for repository-specific policy and global scope for personal defaults.", + "Configuration can change authentication, signing, transport, merge, and hook behavior.", + ), +] as const; + +function category( + id: string, + name: string, + purpose: string, + workflow: string, + caution?: string, +) { + return { id, name, purpose, workflow, caution }; +} + +const namespaceCategory: Record = { + archive: "exchange", + autoFetch: "transport", + bisect: "recovery", + blame: "history", + branch: "refs", + commit: "commits", + compare: "history", + config: "configuration", + conflict: "integration", + file: "changes", + gerrit: "collaboration", + graph: "history", + history: "history", + lfs: "composition", + maintenance: "recovery", + merge: "integration", + notes: "collaboration", + patch: "exchange", + rebase: "integration", + reflog: "history", + remote: "transport", + remoteBranch: "refs", + replace: "changes", + repo: "recovery", + repositories: "repository", + scm: "changes", + staging: "changes", + stash: "stash", + submodule: "composition", + subtree: "composition", + sync: "transport", + tag: "refs", + worktree: "worktrees", +}; + +const exactCategory: Record = { + "vsgit.clone": "repository", + "vsgit.init": "repository", + "vsgit.clean": "changes", + "vsgit.fetch": "transport", + "vsgit.pull": "transport", + "vsgit.push": "transport", + "vsgit.fetchGithubPrs": "transport", + "vsgit.switchTo": "refs", + "vsgit.merge": "integration", + "vsgit.rebase": "integration", + "vsgit.mergeTool": "integration", + "vsgit.showCommitDetails": "commits", +}; + +const exactPurpose: Record = { + "vsgit.file.assumeUnchanged": + "Set Git's assume-unchanged performance hint for the selected tracked file.", + "vsgit.file.noAssumeUnchanged": + "Clear the assume-unchanged flag so normal status detection resumes.", + "vsgit.file.skipWorktree": + "Set the skip-worktree index flag used by partial working-tree workflows.", + "vsgit.file.noSkipWorktree": + "Clear the skip-worktree flag so the path participates normally in the working tree.", + "vsgit.compare.withClipboard": + "Compare the active editor content with text currently stored on the clipboard.", + "vsgit.compare.withLocalHistory": + "Compare a file with a snapshot maintained by VS Code Local History.", + "vsgit.repo.reset.soft": + "Move HEAD to a selected ref while preserving both the index and working tree.", + "vsgit.repo.reset.mixed": + "Move HEAD and reset the index while preserving working-tree content.", + "vsgit.repo.reset.hard": + "Move HEAD and make the index and tracked working tree match the selected ref.", + "vsgit.repo.reset.keep": + "Move HEAD while preserving compatible local working-tree changes.", + "vsgit.repo.reset.merge": + "Reset using merge-style index handling while preserving qualifying unmerged changes.", + "vsgit.stash.apply": + "Restore a stash into the working tree while keeping the stash entry.", + "vsgit.stash.pop": + "Restore a stash and remove its entry after a successful application.", + "vsgit.conflict.useOurs": + "Replace the conflicted file with the current operation's ours side.", + "vsgit.conflict.useTheirs": + "Replace the conflicted file with the current operation's theirs side.", + "vsgit.gerrit.installHook": + "Install Gerrit's commit-msg hook so new commits receive a Change-Id footer.", + "vsgit.lfs.prune": + "Remove old local Git LFS objects that are no longer required by recent refs.", + "vsgit.maintenance.fsck": + "Check object validity and repository connectivity with git fsck.", +}; + +export function buildDocumentationData( + manifest: DocumentationManifest, +): DocumentationData { + const commands = manifest.contributes?.commands ?? []; + const hidden = new Set( + (manifest.contributes?.menus?.commandPalette ?? []) + .filter((entry) => entry.when === "false") + .map((entry) => entry.command), + ); + + const operationCategories: OperationCategory[] = categoryDefinitions.map( + (definition) => ({ + ...definition, + operations: [], + }), + ); + const categoryMap = new Map( + operationCategories.map((entry) => [entry.id, entry]), + ); + + for (const command of commands) { + const namespace = command.command.replace(/^vsgit\./, "").split(".")[0]; + const categoryId = + exactCategory[command.command] ?? namespaceCategory[namespace] ?? "repository"; + const destination = categoryMap.get(categoryId); + if (!destination) continue; + const runnable = !hidden.has(command.command); + destination.operations.push({ + command: command.command, + title: command.title, + purpose: + exactPurpose[command.command] ?? + inferPurpose(command.command, command.title, destination.name), + use: runnable + ? `Open the Command Palette and run “${command.category ?? "VsGit"}: ${command.title}”, then follow any picker or confirmation.` + : "Use this contextual action from the relevant repository, file, group, branch, tag, commit, graph, or history menu.", + runnable, + }); + } + + for (const operationCategory of operationCategories) { + operationCategory.operations.sort((a, b) => + a.title.localeCompare(b.title), + ); + } + + return { + version: manifest.version ?? "development", + components, + glossary, + operationCategories: operationCategories.filter( + (entry) => entry.operations.length > 0, + ), + operationCount: commands.length, + paletteOperationCount: commands.length - hidden.size, + added: [ + "Searchable Documentation view at the bottom of the VsGit sidebar.", + "Full-screen documentation library opened from the sidebar or Command Palette.", + "Detailed guide to every VsGit component and when to use it.", + "Git glossary covering daily workflows, history rewriting, recovery, collaboration, and repository composition.", + "Manifest-driven operation catalog that includes every contributed command and identifies contextual actions.", + "Phase 10 accessibility semantics, high-contrast support, performance instrumentation, Extension Host tests, coverage thresholds, CI gates, and verified VSIX packaging.", + ], + pending: [ + "Multi-platform Extension Host runs beyond the current macOS development and Ubuntu CI coverage.", + "Larger synthetic-repository benchmarks and further on-demand loading for rarely used metadata.", + "Localization of documentation and command descriptions.", + "Current live workflow recordings for the Marketplace listing.", + "Signed release provenance and automated post-publish Marketplace smoke tests.", + ], + }; +} + +function inferPurpose( + command: string, + title: string, + categoryName: string, +): string { + const action = command.split(".").at(-1) ?? ""; + const object = title.replace(/\.\.\.$/, ""); + const purposes: Record = { + refresh: `Reload ${categoryName.toLowerCase()} data from the active repository.`, + show: `Display ${object.toLowerCase()} for the selected repository object.`, + open: `Open ${object.toLowerCase()} in the appropriate VS Code surface.`, + create: `Create ${object.toLowerCase()} after collecting the required Git inputs.`, + add: `Add ${object.toLowerCase()} to the active repository workflow.`, + edit: `Update ${object.toLowerCase()} after showing its current value.`, + remove: `Remove ${object.toLowerCase()} after confirmation where the action is destructive.`, + delete: `Delete ${object.toLowerCase()} after confirming the selected target.`, + checkout: `Check out the selected target and update HEAD or the working tree as appropriate.`, + stage: "Copy the selected working-tree change into the Git index.", + unstage: "Remove the selected change from the index while preserving working-tree content.", + stageAll: "Stage all eligible changes in the active repository.", + unstageAll: "Remove all staged changes from the index while preserving working-tree content.", + discard: "Discard the selected local working-tree changes after confirmation.", + discardAll: "Discard all eligible local working-tree changes after confirmation.", + commit: "Create a commit from the current index using the supplied message and options.", + commitAmend: "Replace the current tip commit with an updated snapshot or message.", + fetch: "Download remote objects and refresh remote-tracking refs without integrating them.", + pull: "Fetch and integrate the configured upstream into the current branch.", + push: "Publish local refs and objects to the selected remote.", + reset: `Reset ${categoryName.toLowerCase()} state for the selected target.`, + prune: `Remove stale or obsolete ${categoryName.toLowerCase()} data for the selected scope.`, + apply: `Apply ${object.toLowerCase()} to the active repository.`, + lock: `Lock ${object.toLowerCase()} to prevent conflicting or automatic changes.`, + unlock: `Unlock ${object.toLowerCase()} so normal changes can resume.`, + toggle: `Toggle ${object.toLowerCase()} for the active editor or repository.`, + }; + return ( + purposes[action] ?? + `Perform “${object}” as part of the ${categoryName.toLowerCase()} workflow.` + ); +} diff --git a/src/webviews/editTextHtml.ts b/src/webviews/editTextHtml.ts index 2397b8d..d117c2c 100644 --- a/src/webviews/editTextHtml.ts +++ b/src/webviews/editTextHtml.ts @@ -13,6 +13,8 @@ export function editTextHtml(nonce: string, cspSource: string): string { +

Commit Message

- +

Press Control or Command Enter to save and continue. Press Escape to abort.

+
@@ -34,9 +40,23 @@ export function editTextHtml(nonce: string, cspSource: string): string { diff --git a/src/webviews/graph/GraphPanel.ts b/src/webviews/graph/GraphPanel.ts index 2c86119..65fb6fb 100644 --- a/src/webviews/graph/GraphPanel.ts +++ b/src/webviews/graph/GraphPanel.ts @@ -1,13 +1,9 @@ -/** - * Manages the commit graph webview panel: renders the repository's commit - * graph and handles graph-originated requests (checkout, tag, diff, etc.) - * by routing them back through Repository. - */ import * as vscode from "vscode"; import * as path from "node:path"; import { Repository } from "../../git/Repository"; import { RepositoryManager } from "../../git/RepositoryManager"; import { GitContentProvider } from "../../git/GitContentProvider"; +import { confirmDestructiveAction } from "../../util/confirmation"; type RefType = "head" | "localBranch" | "remoteBranch" | "tag" | "stash"; @@ -70,7 +66,7 @@ export class GraphPanel { this.panel = panel; this.manager = manager; this.extensionUri = extensionUri; - this.activeRepo = initialRepo ?? manager.getAll()[0]; + this.activeRepo = initialRepo ?? manager.getActive(); this.panel.webview.html = this.getHtmlForWebview(); this.panel.onDidDispose(() => this.dispose(), null, this.disposables); this.panel.webview.onDidReceiveMessage( @@ -145,7 +141,7 @@ export class GraphPanel { return live; } } - this.activeRepo = this.manager.getAll()[0]; + this.activeRepo = this.manager.getActive(); return this.activeRepo; } @@ -211,7 +207,9 @@ export class GraphPanel { // Attach stash badges to their base commit (first cut of stash topology). for (const stash of repo.stashes) { - const target = commits.find((c) => c.sha === stash.ref) ?? commits[0]; + const target = + commits.find((c) => c.sha === stash.baseObjectId) ?? + commits.find((c) => c.sha === stash.objectId); if (target) target.refs.push({ name: stash.ref, type: "stash" }); } @@ -823,8 +821,10 @@ export class GraphPanel { } private async confirm(message: string, action: string): Promise { - const choice = await vscode.window.showWarningMessage(message, { modal: true }, action); - return choice === action; + return confirmDestructiveAction({ + operation: action, + message, + }); } private notify(message: string): void { @@ -838,6 +838,15 @@ export class GraphPanel { const cssUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, "resources", "graph.css"), ); + const codiconCssUri = webview.asWebviewUri( + vscode.Uri.joinPath(this.extensionUri, "resources", "codicon.css"), + ); + const setiCssUri = webview.asWebviewUri( + vscode.Uri.joinPath(this.extensionUri, "resources", "seti.css"), + ); + const setiJsUri = webview.asWebviewUri( + vscode.Uri.joinPath(this.extensionUri, "resources", "setiIcons.js"), + ); const jsUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, "resources", "graph.js"), ); @@ -847,6 +856,7 @@ export class GraphPanel { const csp = [ "default-src 'none'", `style-src ${webview.cspSource}`, + `font-src ${webview.cspSource}`, `script-src 'nonce-${nonce}'`, `img-src ${webview.cspSource} data:`, ].join("; "); @@ -857,10 +867,13 @@ export class GraphPanel { + + VsGit Graph +
-
+
@@ -947,12 +960,12 @@ export class GraphPanel { -
+